diff --git a/Makefile b/Makefile index defd6a7..33ba581 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -CPU_OBJ = $(addprefix build/cpu_obj/, ipu_host.o ipu_transfer.o i_system.o d_mode.o i_main.o m_argv.o m_misc.o net_common.o net_dedicated.o net_io.o net_packet.o net_query.o net_sdl.o net_server.o net_structrw.o aes_prng.o d_event.o d_iwad.o d_loop.o gusconf.o i_cdmus.o i_input.o i_joystick.o i_sdlmusic.o i_sdlsound.o i_sound.o i_timer.o i_video.o i_videohr.o midifile.o mus2mid.o m_bbox.o m_cheat.o m_config.o m_controls.o m_fixed.o net_client.o sha1.o memio.o tables.o v_video.o w_checksum.o w_main.o w_wad.o w_file.o w_file_stdc.o w_file_posix.o w_merge.o z_zone.o net_loop.o am_map.o d_items.o d_main.o d_net.o doomdef.o doomstat.o dstrings.o f_wipe.o g_game.o hu_lib.o hu_stuff.o info.o m_menu.o m_random.o p_ceilng.o p_doors.o p_enemy.o p_floor.o p_inter.o p_lights.o p_map.o p_maputl.o p_mobj.o p_plats.o p_pspr.o p_saveg.o p_setup.o p_sight.o p_spec.o p_switch.o p_telept.o p_tick.o p_user.o r_bsp.o r_data.o r_draw.o r_main.o r_plane.o r_segs.o r_sky.o r_things.o s_sound.o sounds.o st_lib.o st_stuff.o statdump.o wi_stuff.o) +CPU_OBJ = $(addprefix build/cpu_obj/, streaming.o ipu_host.o ipu_transfer.o i_system.o d_mode.o i_main.o m_argv.o m_misc.o net_common.o net_dedicated.o net_io.o net_packet.o net_query.o net_sdl.o net_server.o net_structrw.o aes_prng.o d_event.o d_iwad.o d_loop.o gusconf.o i_cdmus.o i_input.o i_joystick.o i_sdlmusic.o i_sdlsound.o i_sound.o i_timer.o i_video.o i_videohr.o midifile.o mus2mid.o m_bbox.o m_cheat.o m_config.o m_controls.o m_fixed.o net_client.o sha1.o memio.o tables.o v_video.o w_checksum.o w_main.o w_wad.o w_file.o w_file_stdc.o w_file_posix.o w_merge.o z_zone.o net_loop.o am_map.o d_items.o d_main.o d_net.o doomdef.o doomstat.o dstrings.o f_wipe.o g_game.o hu_lib.o hu_stuff.o info.o m_menu.o m_random.o p_ceilng.o p_doors.o p_enemy.o p_floor.o p_inter.o p_lights.o p_map.o p_maputl.o p_mobj.o p_plats.o p_pspr.o p_saveg.o p_setup.o p_sight.o p_spec.o p_switch.o p_telept.o p_tick.o p_user.o r_bsp.o r_data.o r_draw.o r_main.o r_plane.o r_segs.o r_sky.o r_things.o s_sound.o sounds.o st_lib.o st_stuff.o statdump.o wi_stuff.o) IPU_OBJ = $(addprefix build/ipu_obj/, \ ipu_vertices.gp \ ipu_print.gp \ diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 0000000..33ba581 --- /dev/null +++ b/client/Makefile @@ -0,0 +1,58 @@ + + +CPU_OBJ = $(addprefix build/cpu_obj/, streaming.o ipu_host.o ipu_transfer.o i_system.o d_mode.o i_main.o m_argv.o m_misc.o net_common.o net_dedicated.o net_io.o net_packet.o net_query.o net_sdl.o net_server.o net_structrw.o aes_prng.o d_event.o d_iwad.o d_loop.o gusconf.o i_cdmus.o i_input.o i_joystick.o i_sdlmusic.o i_sdlsound.o i_sound.o i_timer.o i_video.o i_videohr.o midifile.o mus2mid.o m_bbox.o m_cheat.o m_config.o m_controls.o m_fixed.o net_client.o sha1.o memio.o tables.o v_video.o w_checksum.o w_main.o w_wad.o w_file.o w_file_stdc.o w_file_posix.o w_merge.o z_zone.o net_loop.o am_map.o d_items.o d_main.o d_net.o doomdef.o doomstat.o dstrings.o f_wipe.o g_game.o hu_lib.o hu_stuff.o info.o m_menu.o m_random.o p_ceilng.o p_doors.o p_enemy.o p_floor.o p_inter.o p_lights.o p_map.o p_maputl.o p_mobj.o p_plats.o p_pspr.o p_saveg.o p_setup.o p_sight.o p_spec.o p_switch.o p_telept.o p_tick.o p_user.o r_bsp.o r_data.o r_draw.o r_main.o r_plane.o r_segs.o r_sky.o r_things.o s_sound.o sounds.o st_lib.o st_stuff.o statdump.o wi_stuff.o) +IPU_OBJ = $(addprefix build/ipu_obj/, \ + ipu_vertices.gp \ + ipu_print.gp \ + ipu_transfer.gp \ + ipu_malloc.gp \ + i_video.gp \ + g_game.gp \ + g_game_codelets.gp \ + am_map.gp \ + m_fixed.gp \ + m_controls.gp \ + p_setup.gp \ + p_setup_codelets.gp \ + p_tick.gp \ + tables.gp \ + v_video.gp \ +) + + +CPU_FLAGS = -I src -I /usr/local/include/SDL2 -I/usr/include/libpng16 -I/opt/poplar/include \ + -D_REENTRANT -lSDL2 -lSDL2_mixer -lSDL2_net -lpng16 -lz -lpoplar \ + -Wall -Werror \ + -O2 + +IPU_FLAGS = -I src/ipu \ + -Wall -Werror -Wno-unused-variable \ + -O2 + + +all: build build/doom build/ipu_rt.gp + +build/doom: $(CPU_OBJ) + g++ $^ $(CPU_FLAGS) -o build/doom + +build/cpu_obj/%.o: src/%.c #src/*.h + gcc src/$*.c $(CPU_FLAGS) -c -o $@ + +build/cpu_obj/%.o: src/%.cpp #src/*.h + gcc src/$*.cpp $(CPU_FLAGS) -c -o $@ + +build/ipu_rt.gp: $(IPU_OBJ) + popc $^ $(IPU_FLAGS) -o $@ + +build/ipu_obj/%.gp: src/ipu/%.c src/ipu/*.h + popc $< $(IPU_FLAGS) -o $@ + +build/ipu_obj/%.gp: src/ipu/%.cpp src/*.h + popc $< $(IPU_FLAGS) -o $@ + + +build: + mkdir -p build/cpu_obj build/ipu_obj build/pop_obj + +clean: + rm -r build/ \ No newline at end of file diff --git a/client/src/aes_prng.c b/client/src/aes_prng.c new file mode 100644 index 0000000..e89223a --- /dev/null +++ b/client/src/aes_prng.c @@ -0,0 +1,956 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// +// This implements a cryptographically secure pseudorandom number +// generator for implementing secure demos. The approach taken is to +// use the AES (Rijndael) stream cipher in "counter" mode, encrypting +// an incrementing counter. The cipher key acts as the random seed. +// Cryptanalysis of AES used in this way has shown it to be an +// effective PRNG (see: Empirical Evidence concerning AES, Hellekalek +// & Wegenkittl, 2003). +// +// AES implementation is taken from the Linux kernel's AES +// implementation, found in crypto/aes_generic.c. It has been hacked +// up to work independently. +// + +#include + +#include "SDL2/SDL_endian.h" +#include "aes_prng.h" +#include "doomtype.h" + +/* + * Cryptographic API. + * + * AES Cipher Algorithm. + * + * Based on Brian Gladman's code. + * + * Linux developers: + * Alexander Kjeldaas + * Herbert Valerio Riedel + * Kyle McMartin + * Adam J. Richter (conversion to 2.5 API). + * + * 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. + * + * --------------------------------------------------------------------------- + * Copyright (c) 2002, Dr Brian Gladman , Worcester, UK. + * All rights reserved. + * + * LICENSE TERMS + * + * The free distribution and use of this software in both source and binary + * form is allowed (with or without changes) provided that: + * + * 1. distributions of this source code include the above copyright + * notice, this list of conditions and the following disclaimer; + * + * 2. distributions in binary form include the above copyright + * notice, this list of conditions and the following disclaimer + * in the documentation and/or other associated materials; + * + * 3. the copyright holder's name is not used to endorse products + * built using this software without specific written permission. + * + * ALTERNATIVELY, provided that this notice is retained in full, this product + * may be distributed under the terms of the GNU General Public License (GPL), + * in which case the provisions of the GPL apply INSTEAD OF those given above. + * + * DISCLAIMER + * + * This software is provided 'as is' with no explicit or implied warranties + * in respect of its properties, including, but not limited to, correctness + * and/or fitness for purpose. + * --------------------------------------------------------------------------- + */ + +#define AES_MIN_KEY_SIZE 16 +#define AES_MAX_KEY_SIZE 32 +#define AES_KEYSIZE_128 16 +#define AES_KEYSIZE_192 24 +#define AES_KEYSIZE_256 32 +#define AES_BLOCK_SIZE 16 +#define AES_MAX_KEYLENGTH (15 * 16) +#define AES_MAX_KEYLENGTH_U32 (AES_MAX_KEYLENGTH / sizeof(uint32_t)) + +/* + * Please ensure that the first two fields are 16-byte aligned + * relative to the start of the structure, i.e., don't move them! + */ +typedef struct +{ + uint32_t key_enc[AES_MAX_KEYLENGTH_U32]; + uint32_t key_dec[AES_MAX_KEYLENGTH_U32]; + uint32_t key_length; +} aes_context_t; + +static inline uint8_t get_byte(const uint32_t x, const unsigned n) +{ + return x >> (n << 3); +} + +static const uint32_t rco_tab[10] = { 1, 2, 4, 8, 16, 32, 64, 128, 27, 54 }; + +static const uint32_t crypto_ft_tab[4][256] = { + { + 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, + 0x0df2f2ff, 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, + 0x50303060, 0x03010102, 0xa96767ce, 0x7d2b2b56, + 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, 0x9a7676ec, + 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, + 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, + 0xecadad41, 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, + 0xbf9c9c23, 0xf7a4a453, 0x967272e4, 0x5bc0c09b, + 0xc2b7b775, 0x1cfdfde1, 0xae93933d, 0x6a26264c, + 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, + 0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, + 0x937171e2, 0x73d8d8ab, 0x53313162, 0x3f15152a, + 0x0c040408, 0x52c7c795, 0x65232346, 0x5ec3c39d, + 0x28181830, 0xa1969637, 0x0f05050a, 0xb59a9a2f, + 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, + 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, + 0x1b090912, 0x9e83831d, 0x742c2c58, 0x2e1a1a34, + 0x2d1b1b36, 0xb26e6edc, 0xee5a5ab4, 0xfba0a05b, + 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, 0xceb3b37d, + 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413, + 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, + 0x60202040, 0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, + 0xbe6a6ad4, 0x46cbcb8d, 0xd9bebe67, 0x4b393972, + 0xde4a4a94, 0xd44c4c98, 0xe85858b0, 0x4acfcf85, + 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, + 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, + 0xcf45458a, 0x10f9f9e9, 0x06020204, 0x817f7ffe, + 0xf05050a0, 0x443c3c78, 0xba9f9f25, 0xe3a8a84b, + 0xf35151a2, 0xfea3a35d, 0xc0404080, 0x8a8f8f05, + 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, + 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, + 0x30101020, 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, + 0x4ccdcd81, 0x140c0c18, 0x35131326, 0x2fececc3, + 0xe15f5fbe, 0xa2979735, 0xcc444488, 0x3917172e, + 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, + 0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, + 0xa06060c0, 0x98818119, 0xd14f4f9e, 0x7fdcdca3, + 0x66222244, 0x7e2a2a54, 0xab90903b, 0x8388880b, + 0xca46468c, 0x29eeeec7, 0xd3b8b86b, 0x3c141428, + 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad, + 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, + 0xdb494992, 0x0a06060c, 0x6c242448, 0xe45c5cb8, + 0x5dc2c29f, 0x6ed3d3bd, 0xefacac43, 0xa66262c4, + 0xa8919139, 0xa4959531, 0x37e4e4d3, 0x8b7979f2, + 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda, + 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, + 0xb46c6cd8, 0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, + 0xaf6565ca, 0x8e7a7af4, 0xe9aeae47, 0x18080810, + 0xd5baba6f, 0x887878f0, 0x6f25254a, 0x722e2e5c, + 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, + 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, + 0xdd4b4b96, 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, + 0x907070e0, 0x423e3e7c, 0xc4b5b571, 0xaa6666cc, + 0xd8484890, 0x05030306, 0x01f6f6f7, 0x120e0e1c, + 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, + 0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, + 0x38e1e1d9, 0x13f8f8eb, 0xb398982b, 0x33111122, + 0xbb6969d2, 0x70d9d9a9, 0x898e8e07, 0xa7949433, + 0xb69b9b2d, 0x221e1e3c, 0x92878715, 0x20e9e9c9, + 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, + 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, + 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0, + 0xc3414182, 0xb0999929, 0x772d2d5a, 0x110f0f1e, + 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c, + }, { + 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, + 0xf2f2ff0d, 0x6b6bd6bd, 0x6f6fdeb1, 0xc5c59154, + 0x30306050, 0x01010203, 0x6767cea9, 0x2b2b567d, + 0xfefee719, 0xd7d7b562, 0xabab4de6, 0x7676ec9a, + 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, + 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, + 0xadad41ec, 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, + 0x9c9c23bf, 0xa4a453f7, 0x7272e496, 0xc0c09b5b, + 0xb7b775c2, 0xfdfde11c, 0x93933dae, 0x26264c6a, + 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, + 0x3434685c, 0xa5a551f4, 0xe5e5d134, 0xf1f1f908, + 0x7171e293, 0xd8d8ab73, 0x31316253, 0x15152a3f, + 0x0404080c, 0xc7c79552, 0x23234665, 0xc3c39d5e, + 0x18183028, 0x969637a1, 0x05050a0f, 0x9a9a2fb5, + 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, + 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, + 0x0909121b, 0x83831d9e, 0x2c2c5874, 0x1a1a342e, + 0x1b1b362d, 0x6e6edcb2, 0x5a5ab4ee, 0xa0a05bfb, + 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, 0xb3b37dce, + 0x2929527b, 0xe3e3dd3e, 0x2f2f5e71, 0x84841397, + 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, + 0x20204060, 0xfcfce31f, 0xb1b179c8, 0x5b5bb6ed, + 0x6a6ad4be, 0xcbcb8d46, 0xbebe67d9, 0x3939724b, + 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, 0xcfcf854a, + 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, + 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, + 0x45458acf, 0xf9f9e910, 0x02020406, 0x7f7ffe81, + 0x5050a0f0, 0x3c3c7844, 0x9f9f25ba, 0xa8a84be3, + 0x5151a2f3, 0xa3a35dfe, 0x404080c0, 0x8f8f058a, + 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, + 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, 0x21214263, + 0x10102030, 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, + 0xcdcd814c, 0x0c0c1814, 0x13132635, 0xececc32f, + 0x5f5fbee1, 0x979735a2, 0x444488cc, 0x17172e39, + 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, + 0x6464c8ac, 0x5d5dbae7, 0x1919322b, 0x7373e695, + 0x6060c0a0, 0x81811998, 0x4f4f9ed1, 0xdcdca37f, + 0x22224466, 0x2a2a547e, 0x90903bab, 0x88880b83, + 0x46468cca, 0xeeeec729, 0xb8b86bd3, 0x1414283c, + 0xdedea779, 0x5e5ebce2, 0x0b0b161d, 0xdbdbad76, + 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, + 0x494992db, 0x06060c0a, 0x2424486c, 0x5c5cb8e4, + 0xc2c29f5d, 0xd3d3bd6e, 0xacac43ef, 0x6262c4a6, + 0x919139a8, 0x959531a4, 0xe4e4d337, 0x7979f28b, + 0xe7e7d532, 0xc8c88b43, 0x37376e59, 0x6d6ddab7, + 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, + 0x6c6cd8b4, 0x5656acfa, 0xf4f4f307, 0xeaeacf25, + 0x6565caaf, 0x7a7af48e, 0xaeae47e9, 0x08081018, + 0xbaba6fd5, 0x7878f088, 0x25254a6f, 0x2e2e5c72, + 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, + 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, + 0x4b4b96dd, 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, + 0x7070e090, 0x3e3e7c42, 0xb5b571c4, 0x6666ccaa, + 0x484890d8, 0x03030605, 0xf6f6f701, 0x0e0e1c12, + 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, + 0x86861791, 0xc1c19958, 0x1d1d3a27, 0x9e9e27b9, + 0xe1e1d938, 0xf8f8eb13, 0x98982bb3, 0x11112233, + 0x6969d2bb, 0xd9d9a970, 0x8e8e0789, 0x949433a7, + 0x9b9b2db6, 0x1e1e3c22, 0x87871592, 0xe9e9c920, + 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, + 0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, + 0xbfbf65da, 0xe6e6d731, 0x424284c6, 0x6868d0b8, + 0x414182c3, 0x999929b0, 0x2d2d5a77, 0x0f0f1e11, + 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, 0x16162c3a, + }, { + 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, + 0xf2ff0df2, 0x6bd6bd6b, 0x6fdeb16f, 0xc59154c5, + 0x30605030, 0x01020301, 0x67cea967, 0x2b567d2b, + 0xfee719fe, 0xd7b562d7, 0xab4de6ab, 0x76ec9a76, + 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, + 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, + 0xad41ecad, 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, + 0x9c23bf9c, 0xa453f7a4, 0x72e49672, 0xc09b5bc0, + 0xb775c2b7, 0xfde11cfd, 0x933dae93, 0x264c6a26, + 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, + 0x34685c34, 0xa551f4a5, 0xe5d134e5, 0xf1f908f1, + 0x71e29371, 0xd8ab73d8, 0x31625331, 0x152a3f15, + 0x04080c04, 0xc79552c7, 0x23466523, 0xc39d5ec3, + 0x18302818, 0x9637a196, 0x050a0f05, 0x9a2fb59a, + 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, + 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, + 0x09121b09, 0x831d9e83, 0x2c58742c, 0x1a342e1a, + 0x1b362d1b, 0x6edcb26e, 0x5ab4ee5a, 0xa05bfba0, + 0x52a4f652, 0x3b764d3b, 0xd6b761d6, 0xb37dceb3, + 0x29527b29, 0xe3dd3ee3, 0x2f5e712f, 0x84139784, + 0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, + 0x20406020, 0xfce31ffc, 0xb179c8b1, 0x5bb6ed5b, + 0x6ad4be6a, 0xcb8d46cb, 0xbe67d9be, 0x39724b39, + 0x4a94de4a, 0x4c98d44c, 0x58b0e858, 0xcf854acf, + 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, + 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, + 0x458acf45, 0xf9e910f9, 0x02040602, 0x7ffe817f, + 0x50a0f050, 0x3c78443c, 0x9f25ba9f, 0xa84be3a8, + 0x51a2f351, 0xa35dfea3, 0x4080c040, 0x8f058a8f, + 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, + 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, 0x21426321, + 0x10203010, 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, + 0xcd814ccd, 0x0c18140c, 0x13263513, 0xecc32fec, + 0x5fbee15f, 0x9735a297, 0x4488cc44, 0x172e3917, + 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, + 0x64c8ac64, 0x5dbae75d, 0x19322b19, 0x73e69573, + 0x60c0a060, 0x81199881, 0x4f9ed14f, 0xdca37fdc, + 0x22446622, 0x2a547e2a, 0x903bab90, 0x880b8388, + 0x468cca46, 0xeec729ee, 0xb86bd3b8, 0x14283c14, + 0xdea779de, 0x5ebce25e, 0x0b161d0b, 0xdbad76db, + 0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, + 0x4992db49, 0x060c0a06, 0x24486c24, 0x5cb8e45c, + 0xc29f5dc2, 0xd3bd6ed3, 0xac43efac, 0x62c4a662, + 0x9139a891, 0x9531a495, 0xe4d337e4, 0x79f28b79, + 0xe7d532e7, 0xc88b43c8, 0x376e5937, 0x6ddab76d, + 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, + 0x6cd8b46c, 0x56acfa56, 0xf4f307f4, 0xeacf25ea, + 0x65caaf65, 0x7af48e7a, 0xae47e9ae, 0x08101808, + 0xba6fd5ba, 0x78f08878, 0x254a6f25, 0x2e5c722e, + 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, + 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, + 0x4b96dd4b, 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, + 0x70e09070, 0x3e7c423e, 0xb571c4b5, 0x66ccaa66, + 0x4890d848, 0x03060503, 0xf6f701f6, 0x0e1c120e, + 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, + 0x86179186, 0xc19958c1, 0x1d3a271d, 0x9e27b99e, + 0xe1d938e1, 0xf8eb13f8, 0x982bb398, 0x11223311, + 0x69d2bb69, 0xd9a970d9, 0x8e07898e, 0x9433a794, + 0x9b2db69b, 0x1e3c221e, 0x87159287, 0xe9c920e9, + 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, + 0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, + 0xbf65dabf, 0xe6d731e6, 0x4284c642, 0x68d0b868, + 0x4182c341, 0x9929b099, 0x2d5a772d, 0x0f1e110f, + 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, 0x162c3a16, + }, { + 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, + 0xff0df2f2, 0xd6bd6b6b, 0xdeb16f6f, 0x9154c5c5, + 0x60503030, 0x02030101, 0xcea96767, 0x567d2b2b, + 0xe719fefe, 0xb562d7d7, 0x4de6abab, 0xec9a7676, + 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, + 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, + 0x41ecadad, 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, + 0x23bf9c9c, 0x53f7a4a4, 0xe4967272, 0x9b5bc0c0, + 0x75c2b7b7, 0xe11cfdfd, 0x3dae9393, 0x4c6a2626, + 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, + 0x685c3434, 0x51f4a5a5, 0xd134e5e5, 0xf908f1f1, + 0xe2937171, 0xab73d8d8, 0x62533131, 0x2a3f1515, + 0x080c0404, 0x9552c7c7, 0x46652323, 0x9d5ec3c3, + 0x30281818, 0x37a19696, 0x0a0f0505, 0x2fb59a9a, + 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, + 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, + 0x121b0909, 0x1d9e8383, 0x58742c2c, 0x342e1a1a, + 0x362d1b1b, 0xdcb26e6e, 0xb4ee5a5a, 0x5bfba0a0, + 0xa4f65252, 0x764d3b3b, 0xb761d6d6, 0x7dceb3b3, + 0x527b2929, 0xdd3ee3e3, 0x5e712f2f, 0x13978484, + 0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, + 0x40602020, 0xe31ffcfc, 0x79c8b1b1, 0xb6ed5b5b, + 0xd4be6a6a, 0x8d46cbcb, 0x67d9bebe, 0x724b3939, + 0x94de4a4a, 0x98d44c4c, 0xb0e85858, 0x854acfcf, + 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, + 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, + 0x8acf4545, 0xe910f9f9, 0x04060202, 0xfe817f7f, + 0xa0f05050, 0x78443c3c, 0x25ba9f9f, 0x4be3a8a8, + 0xa2f35151, 0x5dfea3a3, 0x80c04040, 0x058a8f8f, + 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, + 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, 0x42632121, + 0x20301010, 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, + 0x814ccdcd, 0x18140c0c, 0x26351313, 0xc32fecec, + 0xbee15f5f, 0x35a29797, 0x88cc4444, 0x2e391717, + 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, + 0xc8ac6464, 0xbae75d5d, 0x322b1919, 0xe6957373, + 0xc0a06060, 0x19988181, 0x9ed14f4f, 0xa37fdcdc, + 0x44662222, 0x547e2a2a, 0x3bab9090, 0x0b838888, + 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, 0x283c1414, + 0xa779dede, 0xbce25e5e, 0x161d0b0b, 0xad76dbdb, + 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, + 0x92db4949, 0x0c0a0606, 0x486c2424, 0xb8e45c5c, + 0x9f5dc2c2, 0xbd6ed3d3, 0x43efacac, 0xc4a66262, + 0x39a89191, 0x31a49595, 0xd337e4e4, 0xf28b7979, + 0xd532e7e7, 0x8b43c8c8, 0x6e593737, 0xdab76d6d, + 0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, + 0xd8b46c6c, 0xacfa5656, 0xf307f4f4, 0xcf25eaea, + 0xcaaf6565, 0xf48e7a7a, 0x47e9aeae, 0x10180808, + 0x6fd5baba, 0xf0887878, 0x4a6f2525, 0x5c722e2e, + 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, + 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, + 0x96dd4b4b, 0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, + 0xe0907070, 0x7c423e3e, 0x71c4b5b5, 0xccaa6666, + 0x90d84848, 0x06050303, 0xf701f6f6, 0x1c120e0e, + 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, + 0x17918686, 0x9958c1c1, 0x3a271d1d, 0x27b99e9e, + 0xd938e1e1, 0xeb13f8f8, 0x2bb39898, 0x22331111, + 0xd2bb6969, 0xa970d9d9, 0x07898e8e, 0x33a79494, + 0x2db69b9b, 0x3c221e1e, 0x15928787, 0xc920e9e9, + 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, + 0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, + 0x65dabfbf, 0xd731e6e6, 0x84c64242, 0xd0b86868, + 0x82c34141, 0x29b09999, 0x5a772d2d, 0x1e110f0f, + 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, 0x2c3a1616, + } +}; + +static const uint32_t crypto_fl_tab[4][256] = { + { + 0x00000063, 0x0000007c, 0x00000077, 0x0000007b, + 0x000000f2, 0x0000006b, 0x0000006f, 0x000000c5, + 0x00000030, 0x00000001, 0x00000067, 0x0000002b, + 0x000000fe, 0x000000d7, 0x000000ab, 0x00000076, + 0x000000ca, 0x00000082, 0x000000c9, 0x0000007d, + 0x000000fa, 0x00000059, 0x00000047, 0x000000f0, + 0x000000ad, 0x000000d4, 0x000000a2, 0x000000af, + 0x0000009c, 0x000000a4, 0x00000072, 0x000000c0, + 0x000000b7, 0x000000fd, 0x00000093, 0x00000026, + 0x00000036, 0x0000003f, 0x000000f7, 0x000000cc, + 0x00000034, 0x000000a5, 0x000000e5, 0x000000f1, + 0x00000071, 0x000000d8, 0x00000031, 0x00000015, + 0x00000004, 0x000000c7, 0x00000023, 0x000000c3, + 0x00000018, 0x00000096, 0x00000005, 0x0000009a, + 0x00000007, 0x00000012, 0x00000080, 0x000000e2, + 0x000000eb, 0x00000027, 0x000000b2, 0x00000075, + 0x00000009, 0x00000083, 0x0000002c, 0x0000001a, + 0x0000001b, 0x0000006e, 0x0000005a, 0x000000a0, + 0x00000052, 0x0000003b, 0x000000d6, 0x000000b3, + 0x00000029, 0x000000e3, 0x0000002f, 0x00000084, + 0x00000053, 0x000000d1, 0x00000000, 0x000000ed, + 0x00000020, 0x000000fc, 0x000000b1, 0x0000005b, + 0x0000006a, 0x000000cb, 0x000000be, 0x00000039, + 0x0000004a, 0x0000004c, 0x00000058, 0x000000cf, + 0x000000d0, 0x000000ef, 0x000000aa, 0x000000fb, + 0x00000043, 0x0000004d, 0x00000033, 0x00000085, + 0x00000045, 0x000000f9, 0x00000002, 0x0000007f, + 0x00000050, 0x0000003c, 0x0000009f, 0x000000a8, + 0x00000051, 0x000000a3, 0x00000040, 0x0000008f, + 0x00000092, 0x0000009d, 0x00000038, 0x000000f5, + 0x000000bc, 0x000000b6, 0x000000da, 0x00000021, + 0x00000010, 0x000000ff, 0x000000f3, 0x000000d2, + 0x000000cd, 0x0000000c, 0x00000013, 0x000000ec, + 0x0000005f, 0x00000097, 0x00000044, 0x00000017, + 0x000000c4, 0x000000a7, 0x0000007e, 0x0000003d, + 0x00000064, 0x0000005d, 0x00000019, 0x00000073, + 0x00000060, 0x00000081, 0x0000004f, 0x000000dc, + 0x00000022, 0x0000002a, 0x00000090, 0x00000088, + 0x00000046, 0x000000ee, 0x000000b8, 0x00000014, + 0x000000de, 0x0000005e, 0x0000000b, 0x000000db, + 0x000000e0, 0x00000032, 0x0000003a, 0x0000000a, + 0x00000049, 0x00000006, 0x00000024, 0x0000005c, + 0x000000c2, 0x000000d3, 0x000000ac, 0x00000062, + 0x00000091, 0x00000095, 0x000000e4, 0x00000079, + 0x000000e7, 0x000000c8, 0x00000037, 0x0000006d, + 0x0000008d, 0x000000d5, 0x0000004e, 0x000000a9, + 0x0000006c, 0x00000056, 0x000000f4, 0x000000ea, + 0x00000065, 0x0000007a, 0x000000ae, 0x00000008, + 0x000000ba, 0x00000078, 0x00000025, 0x0000002e, + 0x0000001c, 0x000000a6, 0x000000b4, 0x000000c6, + 0x000000e8, 0x000000dd, 0x00000074, 0x0000001f, + 0x0000004b, 0x000000bd, 0x0000008b, 0x0000008a, + 0x00000070, 0x0000003e, 0x000000b5, 0x00000066, + 0x00000048, 0x00000003, 0x000000f6, 0x0000000e, + 0x00000061, 0x00000035, 0x00000057, 0x000000b9, + 0x00000086, 0x000000c1, 0x0000001d, 0x0000009e, + 0x000000e1, 0x000000f8, 0x00000098, 0x00000011, + 0x00000069, 0x000000d9, 0x0000008e, 0x00000094, + 0x0000009b, 0x0000001e, 0x00000087, 0x000000e9, + 0x000000ce, 0x00000055, 0x00000028, 0x000000df, + 0x0000008c, 0x000000a1, 0x00000089, 0x0000000d, + 0x000000bf, 0x000000e6, 0x00000042, 0x00000068, + 0x00000041, 0x00000099, 0x0000002d, 0x0000000f, + 0x000000b0, 0x00000054, 0x000000bb, 0x00000016, + }, { + 0x00006300, 0x00007c00, 0x00007700, 0x00007b00, + 0x0000f200, 0x00006b00, 0x00006f00, 0x0000c500, + 0x00003000, 0x00000100, 0x00006700, 0x00002b00, + 0x0000fe00, 0x0000d700, 0x0000ab00, 0x00007600, + 0x0000ca00, 0x00008200, 0x0000c900, 0x00007d00, + 0x0000fa00, 0x00005900, 0x00004700, 0x0000f000, + 0x0000ad00, 0x0000d400, 0x0000a200, 0x0000af00, + 0x00009c00, 0x0000a400, 0x00007200, 0x0000c000, + 0x0000b700, 0x0000fd00, 0x00009300, 0x00002600, + 0x00003600, 0x00003f00, 0x0000f700, 0x0000cc00, + 0x00003400, 0x0000a500, 0x0000e500, 0x0000f100, + 0x00007100, 0x0000d800, 0x00003100, 0x00001500, + 0x00000400, 0x0000c700, 0x00002300, 0x0000c300, + 0x00001800, 0x00009600, 0x00000500, 0x00009a00, + 0x00000700, 0x00001200, 0x00008000, 0x0000e200, + 0x0000eb00, 0x00002700, 0x0000b200, 0x00007500, + 0x00000900, 0x00008300, 0x00002c00, 0x00001a00, + 0x00001b00, 0x00006e00, 0x00005a00, 0x0000a000, + 0x00005200, 0x00003b00, 0x0000d600, 0x0000b300, + 0x00002900, 0x0000e300, 0x00002f00, 0x00008400, + 0x00005300, 0x0000d100, 0x00000000, 0x0000ed00, + 0x00002000, 0x0000fc00, 0x0000b100, 0x00005b00, + 0x00006a00, 0x0000cb00, 0x0000be00, 0x00003900, + 0x00004a00, 0x00004c00, 0x00005800, 0x0000cf00, + 0x0000d000, 0x0000ef00, 0x0000aa00, 0x0000fb00, + 0x00004300, 0x00004d00, 0x00003300, 0x00008500, + 0x00004500, 0x0000f900, 0x00000200, 0x00007f00, + 0x00005000, 0x00003c00, 0x00009f00, 0x0000a800, + 0x00005100, 0x0000a300, 0x00004000, 0x00008f00, + 0x00009200, 0x00009d00, 0x00003800, 0x0000f500, + 0x0000bc00, 0x0000b600, 0x0000da00, 0x00002100, + 0x00001000, 0x0000ff00, 0x0000f300, 0x0000d200, + 0x0000cd00, 0x00000c00, 0x00001300, 0x0000ec00, + 0x00005f00, 0x00009700, 0x00004400, 0x00001700, + 0x0000c400, 0x0000a700, 0x00007e00, 0x00003d00, + 0x00006400, 0x00005d00, 0x00001900, 0x00007300, + 0x00006000, 0x00008100, 0x00004f00, 0x0000dc00, + 0x00002200, 0x00002a00, 0x00009000, 0x00008800, + 0x00004600, 0x0000ee00, 0x0000b800, 0x00001400, + 0x0000de00, 0x00005e00, 0x00000b00, 0x0000db00, + 0x0000e000, 0x00003200, 0x00003a00, 0x00000a00, + 0x00004900, 0x00000600, 0x00002400, 0x00005c00, + 0x0000c200, 0x0000d300, 0x0000ac00, 0x00006200, + 0x00009100, 0x00009500, 0x0000e400, 0x00007900, + 0x0000e700, 0x0000c800, 0x00003700, 0x00006d00, + 0x00008d00, 0x0000d500, 0x00004e00, 0x0000a900, + 0x00006c00, 0x00005600, 0x0000f400, 0x0000ea00, + 0x00006500, 0x00007a00, 0x0000ae00, 0x00000800, + 0x0000ba00, 0x00007800, 0x00002500, 0x00002e00, + 0x00001c00, 0x0000a600, 0x0000b400, 0x0000c600, + 0x0000e800, 0x0000dd00, 0x00007400, 0x00001f00, + 0x00004b00, 0x0000bd00, 0x00008b00, 0x00008a00, + 0x00007000, 0x00003e00, 0x0000b500, 0x00006600, + 0x00004800, 0x00000300, 0x0000f600, 0x00000e00, + 0x00006100, 0x00003500, 0x00005700, 0x0000b900, + 0x00008600, 0x0000c100, 0x00001d00, 0x00009e00, + 0x0000e100, 0x0000f800, 0x00009800, 0x00001100, + 0x00006900, 0x0000d900, 0x00008e00, 0x00009400, + 0x00009b00, 0x00001e00, 0x00008700, 0x0000e900, + 0x0000ce00, 0x00005500, 0x00002800, 0x0000df00, + 0x00008c00, 0x0000a100, 0x00008900, 0x00000d00, + 0x0000bf00, 0x0000e600, 0x00004200, 0x00006800, + 0x00004100, 0x00009900, 0x00002d00, 0x00000f00, + 0x0000b000, 0x00005400, 0x0000bb00, 0x00001600, + }, { + 0x00630000, 0x007c0000, 0x00770000, 0x007b0000, + 0x00f20000, 0x006b0000, 0x006f0000, 0x00c50000, + 0x00300000, 0x00010000, 0x00670000, 0x002b0000, + 0x00fe0000, 0x00d70000, 0x00ab0000, 0x00760000, + 0x00ca0000, 0x00820000, 0x00c90000, 0x007d0000, + 0x00fa0000, 0x00590000, 0x00470000, 0x00f00000, + 0x00ad0000, 0x00d40000, 0x00a20000, 0x00af0000, + 0x009c0000, 0x00a40000, 0x00720000, 0x00c00000, + 0x00b70000, 0x00fd0000, 0x00930000, 0x00260000, + 0x00360000, 0x003f0000, 0x00f70000, 0x00cc0000, + 0x00340000, 0x00a50000, 0x00e50000, 0x00f10000, + 0x00710000, 0x00d80000, 0x00310000, 0x00150000, + 0x00040000, 0x00c70000, 0x00230000, 0x00c30000, + 0x00180000, 0x00960000, 0x00050000, 0x009a0000, + 0x00070000, 0x00120000, 0x00800000, 0x00e20000, + 0x00eb0000, 0x00270000, 0x00b20000, 0x00750000, + 0x00090000, 0x00830000, 0x002c0000, 0x001a0000, + 0x001b0000, 0x006e0000, 0x005a0000, 0x00a00000, + 0x00520000, 0x003b0000, 0x00d60000, 0x00b30000, + 0x00290000, 0x00e30000, 0x002f0000, 0x00840000, + 0x00530000, 0x00d10000, 0x00000000, 0x00ed0000, + 0x00200000, 0x00fc0000, 0x00b10000, 0x005b0000, + 0x006a0000, 0x00cb0000, 0x00be0000, 0x00390000, + 0x004a0000, 0x004c0000, 0x00580000, 0x00cf0000, + 0x00d00000, 0x00ef0000, 0x00aa0000, 0x00fb0000, + 0x00430000, 0x004d0000, 0x00330000, 0x00850000, + 0x00450000, 0x00f90000, 0x00020000, 0x007f0000, + 0x00500000, 0x003c0000, 0x009f0000, 0x00a80000, + 0x00510000, 0x00a30000, 0x00400000, 0x008f0000, + 0x00920000, 0x009d0000, 0x00380000, 0x00f50000, + 0x00bc0000, 0x00b60000, 0x00da0000, 0x00210000, + 0x00100000, 0x00ff0000, 0x00f30000, 0x00d20000, + 0x00cd0000, 0x000c0000, 0x00130000, 0x00ec0000, + 0x005f0000, 0x00970000, 0x00440000, 0x00170000, + 0x00c40000, 0x00a70000, 0x007e0000, 0x003d0000, + 0x00640000, 0x005d0000, 0x00190000, 0x00730000, + 0x00600000, 0x00810000, 0x004f0000, 0x00dc0000, + 0x00220000, 0x002a0000, 0x00900000, 0x00880000, + 0x00460000, 0x00ee0000, 0x00b80000, 0x00140000, + 0x00de0000, 0x005e0000, 0x000b0000, 0x00db0000, + 0x00e00000, 0x00320000, 0x003a0000, 0x000a0000, + 0x00490000, 0x00060000, 0x00240000, 0x005c0000, + 0x00c20000, 0x00d30000, 0x00ac0000, 0x00620000, + 0x00910000, 0x00950000, 0x00e40000, 0x00790000, + 0x00e70000, 0x00c80000, 0x00370000, 0x006d0000, + 0x008d0000, 0x00d50000, 0x004e0000, 0x00a90000, + 0x006c0000, 0x00560000, 0x00f40000, 0x00ea0000, + 0x00650000, 0x007a0000, 0x00ae0000, 0x00080000, + 0x00ba0000, 0x00780000, 0x00250000, 0x002e0000, + 0x001c0000, 0x00a60000, 0x00b40000, 0x00c60000, + 0x00e80000, 0x00dd0000, 0x00740000, 0x001f0000, + 0x004b0000, 0x00bd0000, 0x008b0000, 0x008a0000, + 0x00700000, 0x003e0000, 0x00b50000, 0x00660000, + 0x00480000, 0x00030000, 0x00f60000, 0x000e0000, + 0x00610000, 0x00350000, 0x00570000, 0x00b90000, + 0x00860000, 0x00c10000, 0x001d0000, 0x009e0000, + 0x00e10000, 0x00f80000, 0x00980000, 0x00110000, + 0x00690000, 0x00d90000, 0x008e0000, 0x00940000, + 0x009b0000, 0x001e0000, 0x00870000, 0x00e90000, + 0x00ce0000, 0x00550000, 0x00280000, 0x00df0000, + 0x008c0000, 0x00a10000, 0x00890000, 0x000d0000, + 0x00bf0000, 0x00e60000, 0x00420000, 0x00680000, + 0x00410000, 0x00990000, 0x002d0000, 0x000f0000, + 0x00b00000, 0x00540000, 0x00bb0000, 0x00160000, + }, { + 0x63000000, 0x7c000000, 0x77000000, 0x7b000000, + 0xf2000000, 0x6b000000, 0x6f000000, 0xc5000000, + 0x30000000, 0x01000000, 0x67000000, 0x2b000000, + 0xfe000000, 0xd7000000, 0xab000000, 0x76000000, + 0xca000000, 0x82000000, 0xc9000000, 0x7d000000, + 0xfa000000, 0x59000000, 0x47000000, 0xf0000000, + 0xad000000, 0xd4000000, 0xa2000000, 0xaf000000, + 0x9c000000, 0xa4000000, 0x72000000, 0xc0000000, + 0xb7000000, 0xfd000000, 0x93000000, 0x26000000, + 0x36000000, 0x3f000000, 0xf7000000, 0xcc000000, + 0x34000000, 0xa5000000, 0xe5000000, 0xf1000000, + 0x71000000, 0xd8000000, 0x31000000, 0x15000000, + 0x04000000, 0xc7000000, 0x23000000, 0xc3000000, + 0x18000000, 0x96000000, 0x05000000, 0x9a000000, + 0x07000000, 0x12000000, 0x80000000, 0xe2000000, + 0xeb000000, 0x27000000, 0xb2000000, 0x75000000, + 0x09000000, 0x83000000, 0x2c000000, 0x1a000000, + 0x1b000000, 0x6e000000, 0x5a000000, 0xa0000000, + 0x52000000, 0x3b000000, 0xd6000000, 0xb3000000, + 0x29000000, 0xe3000000, 0x2f000000, 0x84000000, + 0x53000000, 0xd1000000, 0x00000000, 0xed000000, + 0x20000000, 0xfc000000, 0xb1000000, 0x5b000000, + 0x6a000000, 0xcb000000, 0xbe000000, 0x39000000, + 0x4a000000, 0x4c000000, 0x58000000, 0xcf000000, + 0xd0000000, 0xef000000, 0xaa000000, 0xfb000000, + 0x43000000, 0x4d000000, 0x33000000, 0x85000000, + 0x45000000, 0xf9000000, 0x02000000, 0x7f000000, + 0x50000000, 0x3c000000, 0x9f000000, 0xa8000000, + 0x51000000, 0xa3000000, 0x40000000, 0x8f000000, + 0x92000000, 0x9d000000, 0x38000000, 0xf5000000, + 0xbc000000, 0xb6000000, 0xda000000, 0x21000000, + 0x10000000, 0xff000000, 0xf3000000, 0xd2000000, + 0xcd000000, 0x0c000000, 0x13000000, 0xec000000, + 0x5f000000, 0x97000000, 0x44000000, 0x17000000, + 0xc4000000, 0xa7000000, 0x7e000000, 0x3d000000, + 0x64000000, 0x5d000000, 0x19000000, 0x73000000, + 0x60000000, 0x81000000, 0x4f000000, 0xdc000000, + 0x22000000, 0x2a000000, 0x90000000, 0x88000000, + 0x46000000, 0xee000000, 0xb8000000, 0x14000000, + 0xde000000, 0x5e000000, 0x0b000000, 0xdb000000, + 0xe0000000, 0x32000000, 0x3a000000, 0x0a000000, + 0x49000000, 0x06000000, 0x24000000, 0x5c000000, + 0xc2000000, 0xd3000000, 0xac000000, 0x62000000, + 0x91000000, 0x95000000, 0xe4000000, 0x79000000, + 0xe7000000, 0xc8000000, 0x37000000, 0x6d000000, + 0x8d000000, 0xd5000000, 0x4e000000, 0xa9000000, + 0x6c000000, 0x56000000, 0xf4000000, 0xea000000, + 0x65000000, 0x7a000000, 0xae000000, 0x08000000, + 0xba000000, 0x78000000, 0x25000000, 0x2e000000, + 0x1c000000, 0xa6000000, 0xb4000000, 0xc6000000, + 0xe8000000, 0xdd000000, 0x74000000, 0x1f000000, + 0x4b000000, 0xbd000000, 0x8b000000, 0x8a000000, + 0x70000000, 0x3e000000, 0xb5000000, 0x66000000, + 0x48000000, 0x03000000, 0xf6000000, 0x0e000000, + 0x61000000, 0x35000000, 0x57000000, 0xb9000000, + 0x86000000, 0xc1000000, 0x1d000000, 0x9e000000, + 0xe1000000, 0xf8000000, 0x98000000, 0x11000000, + 0x69000000, 0xd9000000, 0x8e000000, 0x94000000, + 0x9b000000, 0x1e000000, 0x87000000, 0xe9000000, + 0xce000000, 0x55000000, 0x28000000, 0xdf000000, + 0x8c000000, 0xa1000000, 0x89000000, 0x0d000000, + 0xbf000000, 0xe6000000, 0x42000000, 0x68000000, + 0x41000000, 0x99000000, 0x2d000000, 0x0f000000, + 0xb0000000, 0x54000000, 0xbb000000, 0x16000000, + } +}; + +/* initialise the key schedule from the user supplied key */ + +static uint32_t aes_ror32(uint32_t word, unsigned int shift) +{ + return (word >> shift) | (word << (32 - shift)); +} + +#define cpu_to_le32(x) SDL_SwapLE32(x) +#define le32_to_cpu(x) SDL_SwapLE32(x) + +#define star_x(x) (((x) & 0x7f7f7f7f) << 1) ^ ((((x) & 0x80808080) >> 7) * 0x1b) + +#define imix_col(y, x) do { \ + u = star_x(x); \ + v = star_x(u); \ + w = star_x(v); \ + t = w ^ (x); \ + (y) = u ^ v ^ w; \ + (y) ^= aes_ror32(u ^ t, 8) ^ \ + aes_ror32(v ^ t, 16) ^ \ + aes_ror32(t, 24); \ +} while (0) + +#define ls_box(x) \ + crypto_fl_tab[0][get_byte(x, 0)] ^ \ + crypto_fl_tab[1][get_byte(x, 1)] ^ \ + crypto_fl_tab[2][get_byte(x, 2)] ^ \ + crypto_fl_tab[3][get_byte(x, 3)] + +#define loop4(i) do { \ + t = aes_ror32(t, 8); \ + t = ls_box(t) ^ rco_tab[i]; \ + t ^= ctx->key_enc[4 * i]; \ + ctx->key_enc[4 * i + 4] = t; \ + t ^= ctx->key_enc[4 * i + 1]; \ + ctx->key_enc[4 * i + 5] = t; \ + t ^= ctx->key_enc[4 * i + 2]; \ + ctx->key_enc[4 * i + 6] = t; \ + t ^= ctx->key_enc[4 * i + 3]; \ + ctx->key_enc[4 * i + 7] = t; \ +} while (0) + +#define loop6(i) do { \ + t = aes_ror32(t, 8); \ + t = ls_box(t) ^ rco_tab[i]; \ + t ^= ctx->key_enc[6 * i]; \ + ctx->key_enc[6 * i + 6] = t; \ + t ^= ctx->key_enc[6 * i + 1]; \ + ctx->key_enc[6 * i + 7] = t; \ + t ^= ctx->key_enc[6 * i + 2]; \ + ctx->key_enc[6 * i + 8] = t; \ + t ^= ctx->key_enc[6 * i + 3]; \ + ctx->key_enc[6 * i + 9] = t; \ + t ^= ctx->key_enc[6 * i + 4]; \ + ctx->key_enc[6 * i + 10] = t; \ + t ^= ctx->key_enc[6 * i + 5]; \ + ctx->key_enc[6 * i + 11] = t; \ +} while (0) + +#define loop8tophalf(i) do { \ + t = aes_ror32(t, 8); \ + t = ls_box(t) ^ rco_tab[i]; \ + t ^= ctx->key_enc[8 * i]; \ + ctx->key_enc[8 * i + 8] = t; \ + t ^= ctx->key_enc[8 * i + 1]; \ + ctx->key_enc[8 * i + 9] = t; \ + t ^= ctx->key_enc[8 * i + 2]; \ + ctx->key_enc[8 * i + 10] = t; \ + t ^= ctx->key_enc[8 * i + 3]; \ + ctx->key_enc[8 * i + 11] = t; \ +} while (0) + +#define loop8(i) do { \ + loop8tophalf(i); \ + t = ctx->key_enc[8 * i + 4] ^ ls_box(t); \ + ctx->key_enc[8 * i + 12] = t; \ + t ^= ctx->key_enc[8 * i + 5]; \ + ctx->key_enc[8 * i + 13] = t; \ + t ^= ctx->key_enc[8 * i + 6]; \ + ctx->key_enc[8 * i + 14] = t; \ + t ^= ctx->key_enc[8 * i + 7]; \ + ctx->key_enc[8 * i + 15] = t; \ +} while (0) + +/** + * AES_ExpandKey - Expands the AES key as described in FIPS-197 + * @ctx: The location where the computed key will be stored. + * @in_key: The supplied key. + * @key_len: The length of the supplied key. + * + * Returns 0 on success. The function fails only if an invalid key size (or + * pointer) is supplied. + * The expanded key size is 240 bytes (max of 14 rounds with a unique 16 bytes + * key schedule plus a 16 bytes key which is used before the first round). + * The decryption key is prepared for the "Equivalent Inverse Cipher" as + * described in FIPS-197. The first slot (16 bytes) of each key (enc or dec) is + * for the initial combination, the second slot for the first round and so on. + */ +static int AES_ExpandKey(aes_context_t *ctx, const uint8_t *in_key, + unsigned int key_len) +{ + const uint32_t *key = (const uint32_t *)in_key; + uint32_t i, t, u, v, w, j; + + if (key_len != AES_KEYSIZE_128 && key_len != AES_KEYSIZE_192 && + key_len != AES_KEYSIZE_256) + return -1; + + ctx->key_length = key_len; + + ctx->key_dec[key_len + 24] = ctx->key_enc[0] = le32_to_cpu(key[0]); + ctx->key_dec[key_len + 25] = ctx->key_enc[1] = le32_to_cpu(key[1]); + ctx->key_dec[key_len + 26] = ctx->key_enc[2] = le32_to_cpu(key[2]); + ctx->key_dec[key_len + 27] = ctx->key_enc[3] = le32_to_cpu(key[3]); + + switch (key_len) { + case AES_KEYSIZE_128: + t = ctx->key_enc[3]; + for (i = 0; i < 10; ++i) + loop4(i); + break; + + case AES_KEYSIZE_192: + ctx->key_enc[4] = le32_to_cpu(key[4]); + t = ctx->key_enc[5] = le32_to_cpu(key[5]); + for (i = 0; i < 8; ++i) + loop6(i); + break; + + case AES_KEYSIZE_256: + ctx->key_enc[4] = le32_to_cpu(key[4]); + ctx->key_enc[5] = le32_to_cpu(key[5]); + ctx->key_enc[6] = le32_to_cpu(key[6]); + t = ctx->key_enc[7] = le32_to_cpu(key[7]); + for (i = 0; i < 6; ++i) + loop8(i); + loop8tophalf(i); + break; + } + + ctx->key_dec[0] = ctx->key_enc[key_len + 24]; + ctx->key_dec[1] = ctx->key_enc[key_len + 25]; + ctx->key_dec[2] = ctx->key_enc[key_len + 26]; + ctx->key_dec[3] = ctx->key_enc[key_len + 27]; + + for (i = 4; i < key_len + 24; ++i) { + j = key_len + 24 - (i & ~3) + (i & 3); + imix_col(ctx->key_dec[j], ctx->key_enc[i]); + } + return 0; +} + +/** + * AES_SetKey - Set the AES key. + * @ctx: AES context struct. + * @in_key: The input key. + * @key_len: The size of the key. + * + * Returns 0 on success, on failure -1 is returned. + * The function uses AES_ExpandKey() to expand the key. + */ +static int AES_SetKey(aes_context_t *ctx, const uint8_t *in_key, + unsigned int key_len) +{ + int ret; + + ret = AES_ExpandKey(ctx, in_key, key_len); + if (!ret) + return 0; + + return -1; +} + +/* encrypt a block of text */ + +#define f_rn(bo, bi, n, k) do { \ + bo[n] = crypto_ft_tab[0][get_byte(bi[n], 0)] ^ \ + crypto_ft_tab[1][get_byte(bi[(n + 1) & 3], 1)] ^ \ + crypto_ft_tab[2][get_byte(bi[(n + 2) & 3], 2)] ^ \ + crypto_ft_tab[3][get_byte(bi[(n + 3) & 3], 3)] ^ *(k + n); \ +} while (0) + +#define f_nround(bo, bi, k) do {\ + f_rn(bo, bi, 0, k); \ + f_rn(bo, bi, 1, k); \ + f_rn(bo, bi, 2, k); \ + f_rn(bo, bi, 3, k); \ + k += 4; \ +} while (0) + +#define f_rl(bo, bi, n, k) do { \ + bo[n] = crypto_fl_tab[0][get_byte(bi[n], 0)] ^ \ + crypto_fl_tab[1][get_byte(bi[(n + 1) & 3], 1)] ^ \ + crypto_fl_tab[2][get_byte(bi[(n + 2) & 3], 2)] ^ \ + crypto_fl_tab[3][get_byte(bi[(n + 3) & 3], 3)] ^ *(k + n); \ +} while (0) + +#define f_lround(bo, bi, k) do {\ + f_rl(bo, bi, 0, k); \ + f_rl(bo, bi, 1, k); \ + f_rl(bo, bi, 2, k); \ + f_rl(bo, bi, 3, k); \ +} while (0) + +static void AES_Encrypt(aes_context_t *ctx, uint8_t *out, + const uint8_t *in) +{ + const uint32_t *src = (const uint32_t *)in; + uint32_t *dst = (uint32_t *)out; + uint32_t b0[4], b1[4]; + const uint32_t *kp = ctx->key_enc + 4; + const int key_len = ctx->key_length; + + b0[0] = le32_to_cpu(src[0]) ^ ctx->key_enc[0]; + b0[1] = le32_to_cpu(src[1]) ^ ctx->key_enc[1]; + b0[2] = le32_to_cpu(src[2]) ^ ctx->key_enc[2]; + b0[3] = le32_to_cpu(src[3]) ^ ctx->key_enc[3]; + + if (key_len > 24) { + f_nround(b1, b0, kp); + f_nround(b0, b1, kp); + } + + if (key_len > 16) { + f_nround(b1, b0, kp); + f_nround(b0, b1, kp); + } + + f_nround(b1, b0, kp); + f_nround(b0, b1, kp); + f_nround(b1, b0, kp); + f_nround(b0, b1, kp); + f_nround(b1, b0, kp); + f_nround(b0, b1, kp); + f_nround(b1, b0, kp); + f_nround(b0, b1, kp); + f_nround(b1, b0, kp); + f_lround(b0, b1, kp); + + dst[0] = cpu_to_le32(b0[0]); + dst[1] = cpu_to_le32(b0[1]); + dst[2] = cpu_to_le32(b0[2]); + dst[3] = cpu_to_le32(b0[3]); +} + +static boolean prng_enabled = false; +static aes_context_t prng_context; +static uint32_t prng_input_counter; +static uint32_t prng_values[4]; +static unsigned int prng_value_index = 0; + +// Initialize Pseudo-RNG using the specified 128-bit key. + +void PRNG_Start(prng_seed_t key) +{ + AES_SetKey(&prng_context, key, sizeof(prng_seed_t)); + prng_value_index = 4; + prng_input_counter = 0; + prng_enabled = true; +} + +void PRNG_Stop(void) +{ + prng_enabled = false; +} + +// Generate a set of new PRNG values by encrypting a new block. + +static void PRNG_Generate(void) +{ + byte input[16], output[16]; + unsigned int i; + + // Input for the cipher is a consecutively increasing 32-bit counter. + + for (i = 0; i < 4; ++i) + { + input[4*i] = prng_input_counter & 0xff; + input[4*i + 1] = (prng_input_counter >> 8) & 0xff; + input[4*i + 2] = (prng_input_counter >> 16) & 0xff; + input[4*i + 3] = (prng_input_counter >> 24) & 0xff; + ++prng_input_counter; + } + + AES_Encrypt(&prng_context, output, input); + + for (i = 0; i < 4; ++i) + { + prng_values[i] = output[4*i] + | (output[4*i + 1] << 8) + | (output[4*i + 2] << 16) + | (output[4*i + 3] << 24); + } + + prng_value_index = 0; +} + +// Read a random 32-bit integer from the PRNG. + +unsigned int PRNG_Random(void) +{ + unsigned int result; + + if (!prng_enabled) + { + return 0; + } + + if (prng_value_index >= 4) + { + PRNG_Generate(); + } + + result = prng_values[prng_value_index]; + ++prng_value_index; + + return result; +} + diff --git a/client/src/aes_prng.h b/client/src/aes_prng.h new file mode 100644 index 0000000..f255db1 --- /dev/null +++ b/client/src/aes_prng.h @@ -0,0 +1,32 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Pseudo-random number generator for secure demos. +// + +#ifndef __AES_PRNG_H__ +#define __AES_PRNG_H__ + +#include "doomtype.h" + +// Nonce value used as random seed for secure demos. + +typedef byte prng_seed_t[16]; + +void PRNG_Start(prng_seed_t seed); +void PRNG_Stop(void); +unsigned int PRNG_Random(void); + +#endif /* #ifndef __AES_PRNG_H__ */ + diff --git a/client/src/am_map.c b/client/src/am_map.c new file mode 100644 index 0000000..cf03cc7 --- /dev/null +++ b/client/src/am_map.c @@ -0,0 +1,1177 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: the automap code +// + +#include +#include +#include + +#include "am_map.h" +#include "d_englsh.h" +#include "d_mode.h" +#include "d_player.h" +#include "doomdata.h" +#include "doomdef.h" +// State. +#include "doomstat.h" +#include "i_timer.h" +#include "i_video.h" +#include "m_cheat.h" +#include "m_controls.h" +#include "m_fixed.h" +#include "m_misc.h" +#include "p_local.h" +#include "p_mobj.h" +#include "r_defs.h" +#include "r_state.h" +#include "st_stuff.h" +#include "tables.h" +#include "v_patch.h" +// Needs access to LFB. +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +#include "ipu_host.h" + + +// For use if I do walls with outsides/insides +#define REDS (256 - 5 * 16) +#define REDRANGE 16 +#define BLUES (256 - 4 * 16 + 8) +#define BLUERANGE 8 +#define GREENS (7 * 16) +#define GREENRANGE 16 +#define GRAYS (6 * 16) +#define GRAYSRANGE 16 +#define BROWNS (4 * 16) +#define BROWNRANGE 16 +#define YELLOWS (256 - 32 + 7) +#define YELLOWRANGE 1 +#define BLACK 0 +#define WHITE (256 - 47) + +// Automap colors +#define BACKGROUND BLACK +#define YOURCOLORS WHITE +#define YOURRANGE 0 +#define WALLCOLORS REDS +#define WALLRANGE REDRANGE +#define TSWALLCOLORS GRAYS +#define TSWALLRANGE GRAYSRANGE +#define FDWALLCOLORS BROWNS +#define FDWALLRANGE BROWNRANGE +#define CDWALLCOLORS YELLOWS +#define CDWALLRANGE YELLOWRANGE +#define THINGCOLORS GREENS +#define THINGRANGE GREENRANGE +#define SECRETWALLCOLORS WALLCOLORS +#define SECRETWALLRANGE WALLRANGE +#define GRIDCOLORS (GRAYS + GRAYSRANGE / 2) +#define GRIDRANGE 0 +#define XHAIRCOLORS GRAYS + +// drawing stuff + +#define AM_NUMMARKPOINTS 10 + +// 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)) + +// translates between frame-buffer and map distances +#define FTOM(x) FixedMul(((x) << FRACBITS), scale_ftom) +#define MTOF(x) (FixedMul((x), scale_mtof) >> FRACBITS) +// 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))) + +// the following is crap +#define LINE_NEVERSEE ML_DONTDRAW + +typedef struct { int x, y; } fpoint_t; + +typedef struct { fpoint_t a, b; } fline_t; + +typedef struct { fixed_t x, y; } mpoint_t; + +typedef struct { mpoint_t a, b; } mline_t; + +typedef struct { fixed_t slp, islp; } islope_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 R ((8 * PLAYERRADIUS) / 7) +mline_t cheat_player_arrow[] = { + {{-R + R / 8, 0}, {R, 0}}, // ----- + {{R, 0}, {R - R / 2, R / 6}}, // -----> + {{R, 0}, {R - R / 2, -R / 6}}, + {{-R + R / 8, 0}, {-R - R / 8, R / 6}}, // >-----> + {{-R + R / 8, 0}, {-R - R / 8, -R / 6}}, + {{-R + 3 * R / 8, 0}, {-R + R / 8, R / 6}}, // >>-----> + {{-R + 3 * R / 8, 0}, {-R + R / 8, -R / 6}}, + {{-R / 2, 0}, {-R / 2, -R / 6}}, // >>-d---> + {{-R / 2, -R / 6}, {-R / 2 + R / 6, -R / 6}}, + {{-R / 2 + R / 6, -R / 6}, {-R / 2 + R / 6, R / 4}}, + {{-R / 6, 0}, {-R / 6, -R / 6}}, // >>-dd--> + {{-R / 6, -R / 6}, {0, -R / 6}}, + {{0, -R / 6}, {0, R / 4}}, + {{R / 6, R / 4}, {R / 6, -R / 7}}, // >>-ddt-> + {{R / 6, -R / 7}, {R / 6 + R / 32, -R / 7 - R / 32}}, + {{R / 6 + R / 32, -R / 7 - R / 32}, {R / 6 + R / 10, -R / 7}}}; +#undef R + +#define R (FRACUNIT) +mline_t triangle_guy[] = { + {{(fixed_t)(-.867 * R), (fixed_t)(-.5 * R)}, + {(fixed_t)(.867 * R), (fixed_t)(-.5 * R)}}, + {{(fixed_t)(.867 * R), (fixed_t)(-.5 * R)}, {(fixed_t)(0), (fixed_t)(R)}}, + {{(fixed_t)(0), (fixed_t)(R)}, {(fixed_t)(-.867 * R), (fixed_t)(-.5 * R)}}}; +#undef R + +#define R (FRACUNIT) +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 + +static int cheating = 0; +static int grid = 0; + +static int leveljuststarted = 1; // kluge until AM_LevelInit() is called + +boolean automapactive = false; +static int finit_width = SCREENWIDTH; +static int finit_height = SCREENHEIGHT - ST_HEIGHT; + +// 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 int lightlev; // used for funky strobing effect +static pixel_t *fb; // pseudo-frame buffer +static int amclock; + +static mpoint_t m_paninc; // how far the window pans each tic (map coords) +static fixed_t + mtof_zoommul; // how far the window zooms in each tic (map coords) +static fixed_t ftom_zoommul; // how far the window zooms in each tic (fb coords) + +static fixed_t m_x, m_y; // LL x,y where the window is on the map (map coords) +static fixed_t m_x2, m_y2; // UR x,y where the window is 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 + +static patch_t *marknums[10]; // numbers used for marking by the automap +static mpoint_t markpoints[AM_NUMMARKPOINTS]; // where the points are +static int markpointnum = 0; // next point to be assigned + +static int followplayer = 1; // specifies whether to follow the player around + +cheatseq_t cheat_amap = CHEAT("iddt", 0); + +static boolean stopped = true; + +// Calculates the slope and slope according to the x-axis of a line +// segment in map coordinates (with the upright y-axis n' all) so +// that it can be used with the brain-dead drawing stuff. + +void AM_getIslope(mline_t *ml, islope_t *is) { + int dx, dy; + + dy = ml->a.y - ml->b.y; + dx = ml->b.x - ml->a.x; + if (!dy) + is->islp = (dx < 0 ? -INT_MAX : INT_MAX); + else + is->islp = FixedDiv(dx, dy); + if (!dx) + is->slp = (dy < 0 ? -INT_MAX : INT_MAX); + else + is->slp = FixedDiv(dy, dx); +} + +// +// +// +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; +} + +// +// +// +void AM_saveScaleAndLoc(void) { + old_m_x = m_x; + old_m_y = m_y; + old_m_w = m_w; + old_m_h = m_h; +} + +// +// +// +void AM_restoreScaleAndLoc(void) { + + m_w = old_m_w; + m_h = old_m_h; + if (!followplayer) { + m_x = old_m_x; + m_y = old_m_y; + } else { + m_x = plr->mo->x - m_w / 2; + m_y = plr->mo->y - m_h / 2; + } + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + + // Change the scaling multipliers + scale_mtof = FixedDiv(f_w << FRACBITS, m_w); + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); +} + +// +// adds a marker at the current location +// +void AM_addMark(void) { + markpoints[markpointnum].x = m_x + m_w / 2; + markpoints[markpointnum].y = m_y + m_h / 2; + markpointnum = (markpointnum + 1) % AM_NUMMARKPOINTS; +} + +// +// Determines bounding box of all vertices, +// sets global variables controlling zoom range. +// +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 < numvertexes; i++) { + if (vertexes[i].x < min_x) + min_x = vertexes[i].x; + else if (vertexes[i].x > 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 - min_x; + max_h = max_y - min_y; + + min_w = 2 * PLAYERRADIUS; // const? never changed? + min_h = 2 * PLAYERRADIUS; + + a = FixedDiv(f_w << FRACBITS, max_w); + b = FixedDiv(f_h << FRACBITS, max_h); + + min_scale_mtof = a < b ? a : b; + max_scale_mtof = FixedDiv(f_h << FRACBITS, 2 * PLAYERRADIUS); +} + +// +// +// +void AM_changeWindowLoc(void) { + if (m_paninc.x || m_paninc.y) { + followplayer = 0; + f_oldloc.x = INT_MAX; + } + + m_x += m_paninc.x; + m_y += m_paninc.y; + + if (m_x + m_w / 2 > 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; +} + +// +// +// +void AM_initVariables(void) { + int pnum; + static event_t st_notify = {ev_keyup, AM_MSGENTERED, 0, 0}; + + automapactive = true; + fb = I_VideoBuffer; + + f_oldloc.x = INT_MAX; + amclock = 0; + lightlev = 0; + + 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[consoleplayer]) { + plr = &players[consoleplayer]; + } else { + plr = &players[0]; + + for (pnum = 0; pnum < MAXPLAYERS; pnum++) { + if (playeringame[pnum]) { + plr = &players[pnum]; + break; + } + } + } + + m_x = plr->mo->x - m_w / 2; + m_y = plr->mo->y - m_h / 2; + 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); +} + +// +// +// +void AM_loadPics(void) { + int i; + char namebuf[9]; + + for (i = 0; i < 10; i++) { + M_snprintf(namebuf, 9, "AMMNUM%d", i); + marknums[i] = W_CacheLumpName(namebuf, PU_STATIC); + } +} + +void AM_unloadPics(void) { + int i; + char namebuf[9]; + + for (i = 0; i < 10; i++) { + M_snprintf(namebuf, 9, "AMMNUM%d", i); + W_ReleaseLumpName(namebuf); + } +} + +void AM_clearMarks(void) { + int i; + + for (i = 0; i < AM_NUMMARKPOINTS; i++) + markpoints[i].x = -1; // means empty + markpointnum = 0; +} + +// +// should be called at the start of every level +// right now, i figure it out myself +// +void AM_LevelInit(void) { + leveljuststarted = 0; + + f_x = f_y = 0; + f_w = finit_width; + f_h = finit_height; + + AM_clearMarks(); + + 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); +} + +// +// +// +void AM_Stop(void) { + static event_t st_notify = {0, ev_keyup, AM_MSGEXITED, 0}; + + AM_unloadPics(); + automapactive = false; + ST_Responder(&st_notify); + stopped = true; +} + +// +// +// +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(); +} + +// +// set the window scale to the maximum size +// +void AM_minOutWindowScale(void) { + scale_mtof = min_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + AM_activateNewScale(); +} + +// +// set the window scale to the minimum size +// +void AM_maxOutWindowScale(void) { + scale_mtof = max_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + AM_activateNewScale(); +} + +// +// Handle events (user inputs) in automap mode +// +boolean AM_Responder(event_t *ev) { + + int rc; + static int bigstate = 0; + static char buffer[20]; + int key; + + rc = false; + + if (ev->type == ev_joystick && joybautomap >= 0 && + (ev->data1 & (1 << joybautomap)) != 0) { + joywait = I_GetTime() + 5; + + if (!automapactive) { + AM_Start(); + viewactive = false; + } else { + bigstate = 0; + viewactive = true; + AM_Stop(); + } + + return true; + } + + if (!automapactive) { + if (ev->type == ev_keydown && ev->data1 == key_map_toggle) { + AM_Start(); + viewactive = false; + rc = true; + } + } else if (ev->type == ev_keydown) { + rc = true; + key = ev->data1; + + if (key == key_map_east) // pan right + { + if (!followplayer) + m_paninc.x = FTOM(F_PANINC); + else + rc = false; + } else if (key == key_map_west) // pan left + { + if (!followplayer) + m_paninc.x = -FTOM(F_PANINC); + else + rc = false; + } else if (key == key_map_north) // pan up + { + if (!followplayer) + m_paninc.y = FTOM(F_PANINC); + else + rc = false; + } else if (key == key_map_south) // pan down + { + if (!followplayer) + m_paninc.y = -FTOM(F_PANINC); + else + rc = false; + } else if (key == key_map_zoomout) // zoom out + { + mtof_zoommul = M_ZOOMOUT; + ftom_zoommul = M_ZOOMIN; + } else if (key == key_map_zoomin) // zoom in + { + mtof_zoommul = M_ZOOMIN; + ftom_zoommul = M_ZOOMOUT; + } else if (key == key_map_toggle) { + bigstate = 0; + viewactive = true; + AM_Stop(); + } else if (key == key_map_maxzoom) { + bigstate = !bigstate; + if (bigstate) { + AM_saveScaleAndLoc(); + AM_minOutWindowScale(); + } else + AM_restoreScaleAndLoc(); + } else if (key == key_map_follow) { + followplayer = !followplayer; + f_oldloc.x = INT_MAX; + if (followplayer) + plr->message = (AMSTR_FOLLOWON); + else + plr->message = (AMSTR_FOLLOWOFF); + } else if (key == key_map_grid) { + grid = !grid; + if (grid) + plr->message = (AMSTR_GRIDON); + else + plr->message = (AMSTR_GRIDOFF); + } else if (key == key_map_mark) { + M_snprintf(buffer, sizeof(buffer), "%s %d", (AMSTR_MARKEDSPOT), + markpointnum); + plr->message = buffer; + AM_addMark(); + } else if (key == key_map_clearmark) { + AM_clearMarks(); + plr->message = (AMSTR_MARKSCLEARED); + } else { + rc = false; + } + + if ((!deathmatch || gameversion <= exe_doom_1_8) && + cht_CheckCheat(&cheat_amap, ev->data2)) { + rc = false; + cheating = (cheating + 1) % 3; + } + } else if (ev->type == ev_keyup) { + rc = false; + key = ev->data1; + + if (key == key_map_east) { + if (!followplayer) + m_paninc.x = 0; + } else if (key == key_map_west) { + if (!followplayer) + m_paninc.x = 0; + } else if (key == key_map_north) { + if (!followplayer) + m_paninc.y = 0; + } else if (key == key_map_south) { + if (!followplayer) + m_paninc.y = 0; + } else if (key == key_map_zoomout || key == key_map_zoomin) { + mtof_zoommul = FRACUNIT; + ftom_zoommul = FRACUNIT; + } + } + + return rc; +} + +// +// Zooming +// +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(); +} + +// +// +// +void AM_doFollowPlayer(void) { + + if (f_oldloc.x != plr->mo->x || f_oldloc.y != plr->mo->y) { + m_x = FTOM(MTOF(plr->mo->x)) - m_w / 2; + m_y = FTOM(MTOF(plr->mo->y)) - m_h / 2; + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + f_oldloc.x = plr->mo->x; + f_oldloc.y = plr->mo->y; + + // m_x = FTOM(MTOF(plr->mo->x - m_w/2)); + // m_y = FTOM(MTOF(plr->mo->y - m_h/2)); + // m_x = plr->mo->x - m_w/2; + // m_y = plr->mo->y - m_h/2; + } +} + +// +// +// +void AM_updateLightLev(void) { + static int nexttic = 0; + // static int litelevels[] = { 0, 3, 5, 6, 6, 7, 7, 7 }; + static int litelevels[] = {0, 4, 7, 10, 12, 14, 15, 15}; + static int litelevelscnt = 0; + + // Change light level + if (amclock > nexttic) { + lightlev = litelevels[litelevelscnt++]; + if (litelevelscnt == arrlen(litelevels)) + litelevelscnt = 0; + nexttic = amclock + 6 - (amclock % 6); + } +} + +// +// Updates on Game Tick +// +void AM_Ticker(void) { + + if (!automapactive) + return; + + amclock++; + + if (followplayer) + 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(); + + // Update light level + // AM_updateLightLev(); +} + +// +// Clear automap frame buffer. +// +void AM_clearFB(int color) { memset(fb, color, f_w * f_h * sizeof(*fb)); } + +// +// 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. +// +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; + } else { + tmp.x = 0; + tmp.y = 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 + +// +// Classic Bresenham w/ whatever optimizations needed for speed +// +void AM_drawFline(fline_t *fl, int color) { + register int x; + register int y; + register int dx; + register int dy; + register int sx; + register int sy; + register int ax; + register int ay; + register int d; + + static int fuck = 0; + + // For debugging only + if (fl->a.x < 0 || fl->a.x >= f_w || fl->a.y < 0 || fl->a.y >= f_h || + fl->b.x < 0 || fl->b.x >= f_w || fl->b.y < 0 || fl->b.y >= f_h) { + fprintf(stderr, "fuck %d \r", fuck++); + return; + } + +#define PUTDOT(xx, yy, cc) fb[(yy)*f_w + (xx)] = (cc) + + dx = fl->b.x - fl->a.x; + ax = 2 * (dx < 0 ? -dx : dx); + sx = dx < 0 ? -1 : 1; + + dy = fl->b.y - fl->a.y; + ay = 2 * (dy < 0 ? -dy : dy); + sy = dy < 0 ? -1 : 1; + + x = fl->a.x; + y = fl->a.y; + + if (ax > ay) { + d = ay - ax / 2; + while (1) { + PUTDOT(x, y, color); + if (x == fl->b.x) + return; + if (d >= 0) { + y += sy; + d -= ax; + } + x += sx; + d += ay; + } + } else { + d = ax - ay / 2; + while (1) { + PUTDOT(x, y, color); + if (y == fl->b.y) + return; + if (d >= 0) { + x += sx; + d -= ay; + } + y += sy; + d += ax; + } + } +} + +// +// Clip lines, draw visible part sof lines. +// +void AM_drawMline(mline_t *ml, int color) { + static fline_t fl; + + if (AM_clipMline(ml, &fl)) + AM_drawFline(&fl, color); // draws it on frame buffer using fb coords +} + +// +// Draws flat (floor/ceiling tile) aligned grid lines. +// +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 << FRACBITS)) + start += (MAPBLOCKUNITS << FRACBITS) - + ((start - bmaporgx) % (MAPBLOCKUNITS << FRACBITS)); + end = m_x + m_w; + + // draw vertical gridlines + ml.a.y = m_y; + ml.b.y = m_y + m_h; + for (x = start; x < end; x += (MAPBLOCKUNITS << FRACBITS)) { + ml.a.x = x; + ml.b.x = x; + AM_drawMline(&ml, color); + } + + // Figure out start of horizontal gridlines + start = m_y; + if ((start - bmaporgy) % (MAPBLOCKUNITS << FRACBITS)) + start += (MAPBLOCKUNITS << FRACBITS) - + ((start - bmaporgy) % (MAPBLOCKUNITS << FRACBITS)); + end = m_y + m_h; + + // draw horizontal gridlines + ml.a.x = m_x; + ml.b.x = m_x + m_w; + for (y = start; y < end; y += (MAPBLOCKUNITS << FRACBITS)) { + ml.a.y = y; + ml.b.y = y; + AM_drawMline(&ml, color); + } +} + +// +// Determines visible lines, draws them. +// This is LineDef based, not LineSeg based. +// +void AM_drawWalls(void) { + int i; + static mline_t l; + + for (i = 0; i < numlines; i++) { + l.a.x = lines[i].v1->x; + l.a.y = lines[i].v1->y; + l.b.x = lines[i].v2->x; + l.b.y = lines[i].v2->y; + if (cheating || (lines[i].flags & ML_MAPPED)) { + if ((lines[i].flags & LINE_NEVERSEE) && !cheating) + continue; + if (!lines[i].backsector) { + AM_drawMline(&l, WALLCOLORS + lightlev); + } else { + if (lines[i].special == 39) { // teleporters + AM_drawMline(&l, WALLCOLORS + WALLRANGE / 2); + } else if (lines[i].flags & ML_SECRET) // secret door + { + if (cheating) + AM_drawMline(&l, SECRETWALLCOLORS + lightlev); + else + AM_drawMline(&l, WALLCOLORS + lightlev); + } else if (lines[i].backsector->floorheight != + lines[i].frontsector->floorheight) { + AM_drawMline(&l, FDWALLCOLORS + lightlev); // floor level change + } else if (lines[i].backsector->ceilingheight != + lines[i].frontsector->ceilingheight) { + AM_drawMline(&l, CDWALLCOLORS + lightlev); // ceiling level change + } else if (cheating) { + AM_drawMline(&l, TSWALLCOLORS + lightlev); + } + } + } else if (plr->powers[pw_allmap]) { + if (!(lines[i].flags & LINE_NEVERSEE)) + AM_drawMline(&l, GRAYS + 3); + } + } +} + +// +// Rotation in 2D. +// Used to rotate player arrow line character. +// +void AM_rotate(fixed_t *x, fixed_t *y, angle_t a) { + fixed_t tmpx; + + tmpx = FixedMul(*x, finecosine[a >> ANGLETOFINESHIFT]) - + FixedMul(*y, finesine[a >> ANGLETOFINESHIFT]); + + *y = FixedMul(*x, finesine[a >> ANGLETOFINESHIFT]) + + FixedMul(*y, finecosine[a >> ANGLETOFINESHIFT]); + + *x = tmpx; +} + +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; + + for (i = 0; i < lineguylines; i++) { + l.a.x = lineguy[i].a.x; + l.a.y = lineguy[i].a.y; + + if (scale) { + l.a.x = FixedMul(scale, l.a.x); + l.a.y = FixedMul(scale, l.a.y); + } + + if (angle) + AM_rotate(&l.a.x, &l.a.y, angle); + + l.a.x += x; + l.a.y += y; + + l.b.x = lineguy[i].b.x; + l.b.y = lineguy[i].b.y; + + if (scale) { + l.b.x = FixedMul(scale, l.b.x); + l.b.y = FixedMul(scale, l.b.y); + } + + if (angle) + AM_rotate(&l.b.x, &l.b.y, angle); + + l.b.x += x; + l.b.y += y; + + AM_drawMline(&l, color); + } +} + +void AM_drawPlayers(void) { + int i; + player_t *p; + static int their_colors[] = {GREENS, GRAYS, BROWNS, REDS}; + int their_color = -1; + int color; + + if (!netgame) { + if (cheating) + AM_drawLineCharacter(cheat_player_arrow, arrlen(cheat_player_arrow), 0, + plr->mo->angle, WHITE, plr->mo->x, plr->mo->y); + else + AM_drawLineCharacter(player_arrow, arrlen(player_arrow), 0, + plr->mo->angle, WHITE, plr->mo->x, plr->mo->y); + return; + } + + for (i = 0; i < MAXPLAYERS; i++) { + their_color++; + p = &players[i]; + + if ((deathmatch && !singledemo) && p != plr) + continue; + + if (!playeringame[i]) + continue; + + if (p->powers[pw_invisibility]) + color = 246; // *close* to black + else + color = their_colors[their_color]; + + AM_drawLineCharacter(player_arrow, arrlen(player_arrow), 0, p->mo->angle, + color, p->mo->x, p->mo->y); + } +} + +void AM_drawThings(int colors, int colorrange) { + int i; + mobj_t *t; + + for (i = 0; i < numsectors; i++) { + t = sectors[i].thinglist; + while (t) { + AM_drawLineCharacter(thintriangle_guy, arrlen(thintriangle_guy), + 16 << FRACBITS, t->angle, colors + lightlev, t->x, + t->y); + t = t->snext; + } + } +} + +void AM_drawMarks(void) { + int i, fx, fy, w, h; + + for (i = 0; i < AM_NUMMARKPOINTS; i++) { + if (markpoints[i].x != -1) { + // w = SHORT(marknums[i]->width); + // h = SHORT(marknums[i]->height); + w = 5; // because something's wrong with the wad, i guess + h = 6; // because something's wrong with the wad, i guess + fx = CXMTOF(markpoints[i].x); + fy = CYMTOF(markpoints[i].y); + if (fx >= f_x && fx <= f_w - w && fy >= f_y && fy <= f_h - h) + V_DrawPatch(fx, fy, marknums[i]); + } + } +} + +void AM_drawCrosshair(int color) { + fb[(f_w * (f_h + 1)) / 2] = color; // single point for now +} + +void AM_Drawer(void) { + if (!automapactive) + return; + + AM_clearFB(BACKGROUND); + if (grid) + AM_drawGrid(GRIDCOLORS); + AM_drawWalls(); + AM_drawPlayers(); + if (cheating == 2) + AM_drawThings(THINGCOLORS, THINGRANGE); + AM_drawCrosshair(XHAIRCOLORS); + + AM_drawMarks(); + + V_MarkRect(f_x, f_y, f_w, f_h); +} diff --git a/client/src/am_map.h b/client/src/am_map.h new file mode 100644 index 0000000..665bd5b --- /dev/null +++ b/client/src/am_map.h @@ -0,0 +1,47 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// AutoMap module. +// + +#ifndef __AMMAP_H__ +#define __AMMAP_H__ + +#include "d_event.h" +#include "doomtype.h" +#include "m_cheat.h" + +// 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); + +extern cheatseq_t cheat_amap; + +#endif diff --git a/client/src/d_englsh.h b/client/src/d_englsh.h new file mode 100644 index 0000000..ddecc14 --- /dev/null +++ b/client/src/d_englsh.h @@ -0,0 +1,631 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Printed strings for translation. +// English language support (default). +// + +#ifndef __D_ENGLSH__ +#define __D_ENGLSH__ + +// +// Printed strings for translation +// + +// +// D_Main.C +// +#define D_DEVSTR "Development mode ON.\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 DOSY "(press y to quit to dos.)" + +#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" + +// +// 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 "Green: " +#define HUSTR_PLRINDIGO "Indigo: " +#define HUSTR_PLRBROWN "Brown: " +#define HUSTR_PLRRED "Red: " + +#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" + +// +// 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 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" + +// 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." + +#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." + +#endif diff --git a/client/src/d_event.c b/client/src/d_event.c new file mode 100644 index 0000000..3eef572 --- /dev/null +++ b/client/src/d_event.c @@ -0,0 +1,63 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: Event handling. +// +// Events are asynchronous inputs generally generated by the game user. +// Events can be discarded if no responder claims them +// + +#include +#include "d_event.h" + +#define MAXEVENTS 64 + +static event_t events[MAXEVENTS]; +static int eventhead; +static int eventtail; + +// +// D_PostEvent +// Called by the I/O functions when input is detected +// +void D_PostEvent (event_t* ev) +{ + events[eventhead] = *ev; + eventhead = (eventhead + 1) % MAXEVENTS; +} + +// Read an event from the queue. + +event_t *D_PopEvent(void) +{ + event_t *result; + + // No more events waiting. + + if (eventtail == eventhead) + { + return NULL; + } + + result = &events[eventtail]; + + // Advance to the next event in the queue. + + eventtail = (eventtail + 1) % MAXEVENTS; + + return result; +} + + diff --git a/client/src/d_event.h b/client/src/d_event.h new file mode 100644 index 0000000..c29b54b --- /dev/null +++ b/client/src/d_event.h @@ -0,0 +1,140 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// + + +#ifndef __D_EVENT__ +#define __D_EVENT__ +// +// Event handling. +// + +// Input event types. +typedef enum +{ + // Key press/release events. + // data1: Key code (from doomkeys.h) of the key that was + // pressed or released. This is the key as it appears + // on a US keyboard layout, and does not change with + // layout. + // For ev_keydown only: + // data2: ASCII representation of the key that was pressed that + // changes with the keyboard layout; eg. if 'Z' is + // pressed on a German keyboard, data1='y',data2='z'. + // Not affected by modifier keys. + // data3: ASCII input, fully modified according to keyboard + // layout and any modifier keys that are held down. + // Only set if I_StartTextInput() has been called. + ev_keydown, + ev_keyup, + + // Mouse movement event. + // data1: Bitfield of buttons currently held down. + // (bit 0 = left; bit 1 = right; bit 2 = middle). + // data2: X axis mouse movement (turn). + // data3: Y axis mouse movement (forward/backward). + ev_mouse, + + // Joystick state. + // data1: Bitfield of buttons currently pressed. + // data2: X axis mouse movement (turn). + // data3: Y axis mouse movement (forward/backward). + // data4: Third axis mouse movement (strafe). + ev_joystick, + + // Quit event. Triggered when the user clicks the "close" button + // to terminate the application. + ev_quit +} evtype_t; + +// Event structure. +typedef struct +{ + evtype_t type; + + // Event-specific data; see the descriptions given above. + int data1, data2, data3, data4; +} event_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 3 bits hold weapon num. + BT_CHANGE = 4, + // The 3bit weapon mask and shift, convenience. + BT_WEAPONMASK = (8+16+32), + BT_WEAPONSHIFT = 3, + + // Pause the game. + BTS_PAUSE = 1, + // Save the game at each console. + BTS_SAVEGAME = 2, + + // Savegame slot numbers + // occupy the second byte of buttons. + BTS_SAVEMASK = (4+8+16), + BTS_SAVESHIFT = 2, + +} buttoncode_t; + +// villsa [STRIFE] Strife specific buttons +// TODO - not finished +typedef enum +{ + // Player view look up + BT2_LOOKUP = 1, + // Player view look down + BT2_LOOKDOWN = 2, + // Center player's view + BT2_CENTERVIEW = 4, + // Use inventory item + BT2_INVUSE = 8, + // Drop inventory item + BT2_INVDROP = 16, + // Jump up and down + BT2_JUMP = 32, + // Use medkit + BT2_HEALTH = 128, + +} buttoncode2_t; + + + + +// Called by IO functions when input is detected. +void D_PostEvent (event_t *ev); + +// Read an event from the event queue + +event_t *D_PopEvent(void); + + +#endif + diff --git a/client/src/d_items.c b/client/src/d_items.c new file mode 100644 index 0000000..5789998 --- /dev/null +++ b/client/src/d_items.c @@ -0,0 +1,55 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// + +// We are referring to sprite numbers. +#include "info.h" + +#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/client/src/d_items.h b/client/src/d_items.h new file mode 100644 index 0000000..d4a1f60 --- /dev/null +++ b/client/src/d_items.h @@ -0,0 +1,37 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Items: key cards, artifacts, weapon, ammunition. +// + +#ifndef __D_ITEMS__ +#define __D_ITEMS__ + +#include "doomdef.h" + +// 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/client/src/d_iwad.c b/client/src/d_iwad.c new file mode 100644 index 0000000..d7c5171 --- /dev/null +++ b/client/src/d_iwad.c @@ -0,0 +1,489 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Search for and locate an IWAD file, and initialize according +// to the IWAD type. +// + +#include +#include +#include +#include + +#include "d_iwad.h" +#include "doomtype.h" +#include "i_system.h" +#include "m_argv.h" +#include "m_misc.h" + +static const iwad_t iwads[] = +{ + { "doom2.wad", doom2, commercial, "Doom II" }, + { "plutonia.wad", pack_plut, commercial, "Final Doom: Plutonia Experiment" }, + { "tnt.wad", pack_tnt, commercial, "Final Doom: TNT: Evilution" }, + { "doom.wad", doom, retail, "Doom" }, + { "doom1.wad", doom, shareware, "Doom Shareware" }, + { "chex.wad", pack_chex, retail, "Chex Quest" }, + { "hacx.wad", pack_hacx, commercial, "Hacx" }, + { "freedm.wad", doom2, commercial, "FreeDM" }, + { "freedoom2.wad", doom2, commercial, "Freedoom: Phase 2" }, + { "freedoom1.wad", doom, retail, "Freedoom: Phase 1" }, + { "heretic.wad", heretic, retail, "Heretic" }, + { "heretic1.wad", heretic, shareware, "Heretic Shareware" }, + { "hexen.wad", hexen, commercial, "Hexen" }, + //{ "strife0.wad", strife, commercial, "Strife" }, // haleyjd: STRIFE-FIXME + { "strife1.wad", strife, commercial, "Strife" }, +}; + +// Array of locations to search for IWAD files +// +// "128 IWAD search directories should be enough for anybody". + +#define MAX_IWAD_DIRS 128 + +static boolean iwad_dirs_built = false; +static char *iwad_dirs[MAX_IWAD_DIRS]; +static int num_iwad_dirs = 0; + +static void AddIWADDir(char *dir) +{ + if (num_iwad_dirs < MAX_IWAD_DIRS) + { + iwad_dirs[num_iwad_dirs] = dir; + ++num_iwad_dirs; + } +} + +// Returns true if the specified path is a path to a file +// of the specified name. + +static boolean DirIsFile(char *path, char *filename) +{ + size_t path_len; + size_t filename_len; + + path_len = strlen(path); + filename_len = strlen(filename); + + return path_len >= filename_len + 1 + && path[path_len - filename_len - 1] == DIR_SEPARATOR + && !strcasecmp(&path[path_len - filename_len], filename); +} + +// Check if the specified directory contains the specified IWAD +// file, returning the full path to the IWAD if found, or NULL +// if not found. + +static char *CheckDirectoryHasIWAD(char *dir, char *iwadname) +{ + char *filename; + char *probe; + + // As a special case, the "directory" may refer directly to an + // IWAD file if the path comes from DOOMWADDIR or DOOMWADPATH. + + probe = M_FileCaseExists(dir); + if (DirIsFile(dir, iwadname) && probe != NULL) + { + return probe; + } + + // Construct the full path to the IWAD if it is located in + // this directory, and check if it exists. + + if (!strcmp(dir, ".")) + { + filename = M_StringDuplicate(iwadname); + } + else + { + filename = M_StringJoin(dir, DIR_SEPARATOR_S, iwadname, NULL); + } + + probe = M_FileCaseExists(filename); + if (probe != NULL) + { + return probe; + } + + free(filename); + + return NULL; +} + +// Search a directory to try to find an IWAD +// Returns the location of the IWAD if found, otherwise NULL. + +static char *SearchDirectoryForIWAD(char *dir, int mask, GameMission_t *mission) +{ + char *filename; + size_t i; + + for (i=0; i + // + + iwadparm = M_CheckParmWithArgs("-iwad", 1); + + if (iwadparm) + { + // Search through IWAD dirs for an IWAD with the given name. + + iwadfile = myargv[iwadparm + 1]; + + result = D_FindWADByName(iwadfile); + + if (result == NULL) + { + I_Error("IWAD file '%s' not found!", iwadfile); + } + + *mission = IdentifyIWADByName(result, mask); + } + else + { + // Search through the list and look for an IWAD + + result = NULL; + + BuildIWADDirList(); + + for (i=0; result == NULL && i +#include +#include + +#include "d_event.h" +#include "d_loop.h" +#include "d_main.h" +#include "d_ticcmd.h" +#include "g_game.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" +#include "m_argv.h" +#include "m_fixed.h" +#include "m_menu.h" +#include "net_client.h" +#include "net_io.h" +#include "net_loop.h" +#include "net_query.h" +#include "net_sdl.h" +#include "net_server.h" +#include "w_file.h" + +// The complete set of data for a particular tic. + +typedef struct +{ + ticcmd_t cmds[NET_MAXPLAYERS]; + boolean ingame[NET_MAXPLAYERS]; +} ticcmd_set_t; + +// Maximum time that we wait in TryRunTics() for netgame data to be +// received before we bail out and render a frame anyway. +// Vanilla Doom used 20 for this value, but we use a smaller value +// instead for better responsiveness of the menu when we're stuck. +#define MAX_NETGAME_STALL_TICS 5 + +// +// gametic is the tic about to (or currently being) run +// maketic is the tic that hasn't had control made for it yet +// recvtic is the latest tic received from the server. +// +// a gametic cannot be run until ticcmds are received for it +// from all players. +// + +static ticcmd_set_t ticdata[BACKUPTICS]; + +// The index of the next tic to be made (with a call to BuildTiccmd). + +static int maketic; + +// The number of complete tics received from the server so far. + +static int recvtic; + +// The number of tics that have been run (using RunTic) so far. + +int gametic; + +// When set to true, a single tic is run each time TryRunTics() is called. +// This is used for -timedemo mode. + +boolean singletics = false; + +// Index of the local player. + +static int localplayer; + +// Used for original sync code. + +static int skiptics = 0; + +// Reduce the bandwidth needed by sampling game input less and transmitting +// less. If ticdup is 2, sample half normal, 3 = one third normal, etc. + +int ticdup; + +// Amount to offset the timer for game sync. + +fixed_t offsetms; + +// Use new client syncronisation code + +static boolean new_sync = true; + +// Callback functions for loop code. + +// static loop_interface_t *loop_interface = NULL; +void D_RunTic(ticcmd_t *cmds, boolean *ingame); // JOSEF + + +// Current players in the multiplayer game. +// This is distinct from playeringame[] used by the game code, which may +// modify playeringame[] when playing back multiplayer demos. + +static boolean local_playeringame[NET_MAXPLAYERS]; + +// Requested player class "sent" to the server on connect. +// If we are only doing a single player game then this needs to be remembered +// and saved in the game settings. + +static int player_class; + + +// 35 fps clock adjusted by offsetms milliseconds + +static int GetAdjustedTime(void) +{ + int time_ms; + + time_ms = I_GetTimeMS(); + + if (new_sync) + { + // Use the adjustments from net_client.c only if we are + // using the new sync mode. + + time_ms += (offsetms / FRACUNIT); + } + + return (time_ms * TICRATE) / 1000; +} + +static boolean BuildNewTic(void) +{ + int gameticdiv; + ticcmd_t cmd; + + gameticdiv = gametic/ticdup; + + I_StartTic (); + // loop_interface->ProcessEvents(); // JOSEF + D_ProcessEvents(); + + // Always run the menu + + // loop_interface->RunMenu(); // JOSEF + M_Ticker(); + + if (drone) + { + // In drone mode, do not generate any ticcmds. + + return false; + } + + if (new_sync) + { + // If playing single player, do not allow tics to buffer + // up very far + + if (!net_client_connected && maketic - gameticdiv > 2) + return false; + + // Never go more than ~200ms ahead + + if (maketic - gameticdiv > 8) + return false; + } + else + { + if (maketic - gameticdiv >= 5) + return false; + } + + //printf ("mk:%i ",maketic); + memset(&cmd, 0, sizeof(ticcmd_t)); + // loop_interface->BuildTiccmd(&cmd, maketic); // JOSEF + G_BuildTiccmd(&cmd, maketic); + + if (net_client_connected) + { + NET_CL_SendTiccmd(&cmd, maketic); + } + + ticdata[maketic % BACKUPTICS].cmds[localplayer] = cmd; + ticdata[maketic % BACKUPTICS].ingame[localplayer] = true; + + ++maketic; + + return true; +} + +// +// NetUpdate +// Builds ticcmds for console player, +// sends out a packet +// +int lasttime; + +void NetUpdate (void) +{ + int nowtime; + int newtics; + int i; + + // If we are running with singletics (timing a demo), this + // is all done separately. + + if (singletics) + return; + + // Run network subsystems + + NET_CL_Run(); + NET_SV_Run(); + + // check time + nowtime = GetAdjustedTime() / ticdup; + newtics = nowtime - lasttime; + + lasttime = nowtime; + + if (skiptics <= newtics) + { + newtics -= skiptics; + skiptics = 0; + } + else + { + skiptics -= newtics; + newtics = 0; + } + + // build new ticcmds for console player + + for (i=0 ; iconsoleplayer = 0; + settings->num_players = 1; + settings->player_classes[0] = player_class; + + //! + // @category net + // + // Use new network client sync code rather than the classic + // sync code. This is currently disabled by default because it + // has some bugs. + // + if (M_CheckParm("-newsync") > 0) + settings->new_sync = 1; + else + settings->new_sync = 0; + + // TODO: New sync code is not enabled by default because it's + // currently broken. + //if (M_CheckParm("-oldsync") > 0) + // settings->new_sync = 0; + //else + // settings->new_sync = 1; + + //! + // @category net + // @arg + // + // Send n extra tics in every packet as insurance against dropped + // packets. + // + + i = M_CheckParmWithArgs("-extratics", 1); + + if (i > 0) + settings->extratics = atoi(myargv[i+1]); + else + settings->extratics = 1; + + //! + // @category net + // @arg + // + // Reduce the resolution of the game by a factor of n, reducing + // the amount of network bandwidth needed. + // + + i = M_CheckParmWithArgs("-dup", 1); + + if (i > 0) + settings->ticdup = atoi(myargv[i+1]); + else + settings->ticdup = 1; + + if (net_client_connected) + { + // Send our game settings and block until game start is received + // from the server. + + NET_CL_StartGame(settings); + BlockUntilStart(settings, callback); + + // Read the game settings that were received. + + NET_CL_GetSettings(settings); + } + + if (drone) + { + settings->consoleplayer = 0; + } + + // Set the local player and playeringame[] values. + + localplayer = settings->consoleplayer; + + for (i = 0; i < NET_MAXPLAYERS; ++i) + { + local_playeringame[i] = i < settings->num_players; + } + + // Copy settings to global variables. + + ticdup = settings->ticdup; + new_sync = settings->new_sync; + + // TODO: Message disabled until we fix new_sync. + //if (!new_sync) + //{ + // printf("Syncing netgames like Vanilla Doom.\n"); + //} +} + +boolean D_InitNetGame(net_connect_data_t *connect_data) +{ + boolean result = false; + net_addr_t *addr = NULL; + int i; + + // Call D_QuitNetGame on exit: + + I_AtExit(D_QuitNetGame, true); + + player_class = connect_data->player_class; + + //! + // @category net + // + // Start a multiplayer server, listening for connections. + // + + if (M_CheckParm("-server") > 0 + || M_CheckParm("-privateserver") > 0) + { + NET_SV_Init(); + NET_SV_AddModule(&net_loop_server_module); + NET_SV_AddModule(&net_sdl_module); + NET_SV_RegisterWithMaster(); + + net_loop_client_module.InitClient(); + addr = net_loop_client_module.ResolveAddress(NULL); + } + else + { + //! + // @category net + // + // Automatically search the local LAN for a multiplayer + // server and join it. + // + + i = M_CheckParm("-autojoin"); + + if (i > 0) + { + addr = NET_FindLANServer(); + + if (addr == NULL) + { + I_Error("No server found on local LAN"); + } + } + + //! + // @arg
+ // @category net + // + // Connect to a multiplayer server running on the given + // address. + // + + i = M_CheckParmWithArgs("-connect", 1); + + if (i > 0) + { + net_sdl_module.InitClient(); + addr = net_sdl_module.ResolveAddress(myargv[i+1]); + + if (addr == NULL) + { + I_Error("Unable to resolve '%s'\n", myargv[i+1]); + } + } + } + + if (addr != NULL) + { + if (M_CheckParm("-drone") > 0) + { + connect_data->drone = true; + } + + if (!NET_CL_Connect(addr, connect_data)) + { + I_Error("D_InitNetGame: Failed to connect to %s\n", + NET_AddrToString(addr)); + } + + printf("D_InitNetGame: Connected to %s\n", NET_AddrToString(addr)); + + // Wait for launch message received from server. + + /*NET_WaitForLaunch();*/ + + result = true; + } + + return result; +} + + +// +// D_QuitNetGame +// Called before quitting to leave a net game +// without hanging the other players +// +void D_QuitNetGame (void) +{ + NET_SV_Shutdown(); + NET_CL_Disconnect(); +} + +static int GetLowTic(void) +{ + int lowtic; + + lowtic = maketic; + + if (net_client_connected) + { + if (drone || recvtic < lowtic) + { + lowtic = recvtic; + } + } + + return lowtic; +} + +static int frameon; +static int frameskip[4]; +static int oldnettics; + +static void OldNetSync(void) +{ + unsigned int i; + int keyplayer = -1; + + frameon++; + + // ideally maketic should be 1 - 3 tics above lowtic + // if we are consistantly slower, speed up time + + for (i=0 ; i recvtic; + oldnettics = maketic; + + if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3]) + { + skiptics = 1; + // printf ("+"); + } + } +} + +// Returns true if there are players in the game: + +static boolean PlayersInGame(void) +{ + boolean result = false; + unsigned int i; + + // If we are connected to a server, check if there are any players + // in the game. + + if (net_client_connected) + { + for (i = 0; i < NET_MAXPLAYERS; ++i) + { + result = result || local_playeringame[i]; + } + } + + // Whether single or multi-player, unless we are running as a drone, + // we are in the game. + + if (!drone) + { + result = true; + } + + return result; +} + +// When using ticdup, certain values must be cleared out when running +// the duplicate ticcmds. + +static void TicdupSquash(ticcmd_set_t *set) +{ + ticcmd_t *cmd; + unsigned int i; + + for (i = 0; i < NET_MAXPLAYERS ; ++i) + { + cmd = &set->cmds[i]; + cmd->chatchar = 0; + if (cmd->buttons & BT_SPECIAL) + cmd->buttons = 0; + } +} + +// When running in single player mode, clear all the ingame[] array +// except the local player. + +static void SinglePlayerClear(ticcmd_set_t *set) +{ + unsigned int i; + + for (i = 0; i < NET_MAXPLAYERS; ++i) + { + if (i != localplayer) + { + set->ingame[i] = false; + } + } +} + +// +// TryRunTics +// + +void TryRunTics (void) +{ + int i; + int lowtic; + int entertic; + static int oldentertics; + int realtics; + int availabletics; + int counts; + + // get real tics + entertic = I_GetTime() / ticdup; + realtics = entertic - oldentertics; + oldentertics = entertic; + + // in singletics mode, run a single tic every time this function + // is called. + + if (singletics) + { + BuildNewTic(); + } + else + { + // G_Responder is called in here + NetUpdate (); + } + + lowtic = GetLowTic(); + + availabletics = lowtic - gametic/ticdup; + + // decide how many tics to run + + if (new_sync) + { + counts = availabletics; + } + else + { + // decide how many tics to run + if (realtics < availabletics-1) + counts = realtics+1; + else if (realtics < availabletics) + counts = realtics; + else + counts = availabletics; + + if (counts < 1) + counts = 1; + + if (net_client_connected) + { + OldNetSync(); + } + } + + if (counts < 1) + counts = 1; + + // wait for new tics if needed + while (!PlayersInGame() || lowtic < gametic/ticdup + counts) + { + NetUpdate (); + + lowtic = GetLowTic(); + + if (lowtic < gametic/ticdup) + I_Error ("TryRunTics: lowtic < gametic"); + + // Still no tics to run? Sleep until some are available. + if (lowtic < gametic/ticdup + counts) + { + // If we're in a netgame, we might spin forever waiting for + // new network data to be received. So don't stay in here + // forever - give the menu a chance to work. + if (I_GetTime() / ticdup - entertic >= MAX_NETGAME_STALL_TICS) + { + return; + } + + I_Sleep(1); + } + } + + // run the count * ticdup dics + while (counts--) + { + ticcmd_set_t *set; + + if (!PlayersInGame()) + { + return; + } + + set = &ticdata[(gametic / ticdup) % BACKUPTICS]; + + if (!net_client_connected) + { + SinglePlayerClear(set); + } + + for (i=0 ; i lowtic) + I_Error ("gametic>lowtic"); + + memcpy(local_playeringame, set->ingame, sizeof(local_playeringame)); + + // G_Ticker is called in here + // JOSEF: loop_interface->RunTic(set->cmds, set->ingame); + D_RunTic(set->cmds, set->ingame); // JOSEF + gametic++; + + // modify command for duplicated tics + + TicdupSquash(set); + } + + + NetUpdate (); // check for new console commands + } +} + +// void D_RegisterLoopCallbacks(loop_interface_t *i) +// { + // loop_interface = i; //JOSEF +// } + +// TODO: Move nonvanilla demo functions into a dedicated file. +#include "m_misc.h" +#include "w_wad.h" + +static boolean StrictDemos(void) +{ + //! + // @category demo + // + // When recording or playing back demos, disable any extensions + // of the vanilla demo format - record demos as vanilla would do, + // and play back demos as vanilla would do. + // + return M_ParmExists("-strictdemos"); +} + +// If the provided conditional value is true, we're trying to record +// a demo file that will include a non-vanilla extension. The function +// will return true if the conditional is true and it's allowed to use +// this extension (no extensions are allowed if -strictdemos is given +// on the command line). A warning is shown on the console using the +// provided string describing the non-vanilla expansion. +boolean D_NonVanillaRecord(boolean conditional, char *feature) +{ + if (!conditional || StrictDemos()) + { + return false; + } + + printf("Warning: Recording a demo file with a non-vanilla extension " + "(%s). Use -strictdemos to disable this extension.\n", + feature); + + return true; +} + +// Returns true if the given lump number corresponds to data from a .lmp +// file, as opposed to a WAD. +static boolean IsDemoFile(int lumpnum) +{ + char *lower; + boolean result; + + lower = M_StringDuplicate(lumpinfo[lumpnum]->wad_file->path); + M_ForceLowercase(lower); + result = M_StringEndsWith(lower, ".lmp"); + free(lower); + + return result; +} + +// If the provided conditional value is true, we're trying to play back +// a demo that includes a non-vanilla extension. We return true if the +// conditional is true and it's allowed to use this extension, checking +// that: +// - The -strictdemos command line argument is not provided. +// - The given lumpnum identifying the demo to play back identifies a +// demo that comes from a .lmp file, not a .wad file. +// - Before proceeding, a warning is shown to the user on the console. +boolean D_NonVanillaPlayback(boolean conditional, int lumpnum, + char *feature) +{ + if (!conditional || StrictDemos()) + { + return false; + } + + if (!IsDemoFile(lumpnum)) + { + printf("Warning: WAD contains demo with a non-vanilla extension " + "(%s)\n", feature); + return false; + } + + printf("Warning: Playing back a demo file with a non-vanilla extension " + "(%s). Use -strictdemos to disable this extension.\n", + feature); + + return true; +} + diff --git a/client/src/d_loop.h b/client/src/d_loop.h new file mode 100644 index 0000000..b85fc42 --- /dev/null +++ b/client/src/d_loop.h @@ -0,0 +1,92 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Main loop stuff. +// + +#ifndef __D_LOOP__ +#define __D_LOOP__ + +#include "d_ticcmd.h" +#include "doomtype.h" +#include "net_defs.h" + +// Callback function invoked while waiting for the netgame to start. +// The callback is invoked when new players are ready. The callback +// should return true, or return false to abort startup. + +typedef boolean (*netgame_startup_callback_t)(int ready_players, + int num_players); + +/* JOSEF +typedef struct +{ + // Read events from the event queue, and process them. + + void (*ProcessEvents)(); + + // Given the current input state, fill in the fields of the specified + // ticcmd_t structure with data for a new tic. + + void (*BuildTiccmd)(ticcmd_t *cmd, int maketic); + + // Advance the game forward one tic, using the specified player input. + + void (*RunTic)(ticcmd_t *cmds, boolean *ingame); + + // Run the menu (runs independently of the game). + + void (*RunMenu)(); +} loop_interface_t; + +// Register callback functions for the main loop code to use. +void D_RegisterLoopCallbacks(loop_interface_t *i); +*/ + +// Create any new ticcmds and broadcast to other players. +void NetUpdate (void); + +// Broadcasts special packets to other players +// to notify of game exit +void D_QuitNetGame (void); + +//? how many ticks to run? +void TryRunTics (void); + +// Called at start of game loop to initialize timers +void D_StartGameLoop(void); + +// Initialize networking code and connect to server. + +boolean D_InitNetGame(net_connect_data_t *connect_data); + +// Start game with specified settings. The structure will be updated +// with the actual settings for the game. + +void D_StartNetGame(net_gamesettings_t *settings, + netgame_startup_callback_t callback); + +extern boolean singletics; +extern int gametic, ticdup; + +// Check if it is permitted to record a demo with a non-vanilla feature. +boolean D_NonVanillaRecord(boolean conditional, char *feature); + +// Check if it is permitted to play back a demo with a non-vanilla feature. +boolean D_NonVanillaPlayback(boolean conditional, int lumpnum, + char *feature); + +#endif + diff --git a/client/src/d_main.c b/client/src/d_main.c new file mode 100644 index 0000000..09dae3d --- /dev/null +++ b/client/src/d_main.c @@ -0,0 +1,1342 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// DOOM 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 "am_map.h" +#include "d_englsh.h" +#include "d_event.h" +#include "d_iwad.h" +#include "d_loop.h" +#include "d_main.h" +#include "d_mode.h" +#include "d_player.h" +#include "doomdef.h" +#include "doomstat.h" +#include "doomtype.h" +#include "f_wipe.h" +#include "g_game.h" +#include "hu_stuff.h" +#include "i_input.h" +#include "i_joystick.h" +#include "i_sound.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" +#include "m_argv.h" +#include "m_config.h" +#include "m_controls.h" +#include "m_menu.h" +#include "m_misc.h" +#include "net_client.h" +#include "net_dedicated.h" +#include "net_query.h" +#include "p_saveg.h" +#include "p_setup.h" +#include "r_draw.h" +#include "r_main.h" +#include "r_state.h" +#include "s_sound.h" +#include "sounds.h" +#include "st_stuff.h" +#include "statdump.h" +#include "v_video.h" +#include "w_file.h" +#include "w_main.h" +#include "w_wad.h" +#include "wi_stuff.h" +#include "z_zone.h" + +#include "ipu_host.h" +#include "ipu/ipu_interface.h" +#include "streaming.h" + +// +// 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 +// +void D_DoomLoop(void); + +// Location where savegames are stored + +char *savegamedir; + +// location of IWAD and WAD files + +char *iwadfile; + +boolean devparm; // started game with -devparm +boolean nomonsters; // checkparm of -nomonsters +boolean respawnparm; // checkparm of -respawn +boolean fastparm; // checkparm of -fast + +// extern int soundVolume; +// extern int sfxVolume; +// extern int musicVolume; + +extern boolean inhelpscreens; + +skill_t startskill; +int startepisode; +int startmap; +boolean autostart; +int startloadgame; + +boolean advancedemo; + +// Store demo, do not accept any inputs +boolean storedemo; + +// If true, the main game loop has started. +boolean main_loop_started = false; + +// Josef: CPU traces walls while IPU does automap +boolean livewallupdates = false; + +char wadfile[1024]; // primary wad file +char mapdir[1024]; // directory of development maps + +void D_ConnectNetGame(void); +void D_CheckNetGame(void); + +// +// D_ProcessEvents +// Send all the events of the given timestamp down the responder chain +// +void D_ProcessEvents(void) { + event_t *ev; + + // IF STORE DEMO, DO NOT ACCEPT INPUT + if (storedemo) + return; + + + // JOSEF: Buffer events to send to IPU + G_Responder_MiscValues_t ev_buf; + ev_buf.num_ev = 0; + + while ((ev = D_PopEvent()) != NULL) { + if (M_Responder(ev)) + continue; // menu ate the event + G_Responder(ev); + + if (ev_buf.num_ev < IPUMAXEVENTSPERTIC) { + ev_buf.events[ev_buf.num_ev++] = *ev; + } else { + printf("WARNING: exceeding %d events per tic, keypresses may be dropped\n", IPUMAXEVENTSPERTIC); + } + } + + IPU_G_Responder(&ev_buf); +} + +// +// D_Display +// draw current display, possibly wiping it from the previous +// + +// 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; +void R_ExecuteSetViewSize(void); + +void D_Display(void) { + static boolean viewactivestate = false; + static boolean menuactivestate = false; + static boolean inhelpscreensstate = false; + static boolean fullscreen = false; + static gamestate_t oldgamestate = -1; + static int borderdrawcount; + int nowtime; + int tics; + int wipestart; + int y; + boolean done; + boolean wipe; + boolean redrawsbar; + + // JOSEF: streaming + recv_loop(); + + if (nodrawers) + return; // for comparative timing / profiling + + redrawsbar = false; + + // change the view size if needed + if (setsizeneeded) { + R_ExecuteSetViewSize(); + oldgamestate = -1; // force background redraw + borderdrawcount = 3; + } + + // save the current screen if about to wipe + if (gamestate != wipegamestate) { + wipe = true; + wipe_StartScreen(0, 0, SCREENWIDTH, SCREENHEIGHT); + } else + wipe = false; + + if (gamestate == GS_LEVEL && gametic) + HU_Erase(); + + // do buffered drawing + switch (gamestate) { + case GS_LEVEL: + if (!gametic) + break; + + /* JOSEF: IPU draws automap now + if (automapactive) + AM_Drawer(); + */ + + if (wipe || (viewheight != SCREENHEIGHT && fullscreen)) + redrawsbar = true; + if (inhelpscreensstate && !inhelpscreens) + redrawsbar = true; // just put away the help screen + ST_Drawer(viewheight == SCREENHEIGHT, redrawsbar); + fullscreen = viewheight == SCREENHEIGHT; + break; + + case GS_INTERMISSION: + WI_Drawer(); + break; + + case GS_DEMOSCREEN: + D_PageDrawer(); + break; + } + + // draw buffered stuff to screen + I_UpdateNoBlit(); + + // draw the view directly + int busy_with_automap = automapactive && !livewallupdates; // JOSEF + if (gamestate == GS_LEVEL && !busy_with_automap && gametic) + R_RenderPlayerView(&players[displayplayer]); + + IPU_AM_Drawer(); // JOSEF: After RenderplayerView for wall updates + + if (gamestate == GS_LEVEL && gametic) + HU_Drawer(); + + // clean up border stuff + if (gamestate != oldgamestate && gamestate != GS_LEVEL) + I_SetPalette(W_CacheLumpName("PLAYPAL", PU_CACHE)); + + // see if the border needs to be initially drawn + if (gamestate == GS_LEVEL && oldgamestate != GS_LEVEL) { + viewactivestate = false; // view was not active + R_FillBackScreen(); // draw the pattern into the back screen + } + + // see if the border needs to be updated to the screen + if (gamestate == GS_LEVEL && !automapactive && + scaledviewwidth != SCREENWIDTH) { + if (menuactive || menuactivestate || !viewactivestate) + borderdrawcount = 3; + if (borderdrawcount) { + R_DrawViewBorder(); // erase old menu stuff + borderdrawcount--; + } + } + + if (testcontrols) { + // Box showing current mouse speed + + V_DrawMouseSpeedBox(testcontrols_mousespeed); + } + + menuactivestate = menuactive; + viewactivestate = viewactive; + inhelpscreensstate = inhelpscreens; + oldgamestate = wipegamestate = gamestate; + + // draw pause pic + if (paused) { + if (automapactive) + y = 4; + else + y = viewwindowy + 4; + V_DrawPatchDirect(viewwindowx + (scaledviewwidth - 68) / 2, y, + W_CacheLumpName("M_PAUSE", PU_CACHE)); + } + + // menus go directly to the screen + M_Drawer(); // menu is drawn even on top of everything + NetUpdate(); // send out any new accumulation + + // normal update + if (!wipe) { + I_FinishUpdate(); // page flip or blit buffer + return; + } + + // wipe update + wipe_EndScreen(0, 0, SCREENWIDTH, SCREENHEIGHT); + + wipestart = I_GetTime() - 1; + + do { + do { + nowtime = I_GetTime(); + tics = nowtime - wipestart; + I_Sleep(1); + } while (tics <= 0); + + wipestart = nowtime; + done = wipe_ScreenWipe(wipe_Melt, 0, 0, SCREENWIDTH, SCREENHEIGHT, tics); + I_UpdateNoBlit(); + M_Drawer(); // menu is drawn even on top of wipes + I_FinishUpdate(); // page flip or blit buffer + } while (!done); +} + +// +// Add configuration file variable bindings. +// + +void D_BindVariables(void) { + int i; + + M_ApplyPlatformDefaults(); + + I_BindInputVariables(); + I_BindVideoVariables(); + I_BindJoystickVariables(); + I_BindSoundVariables(); + + M_BindBaseControls(); + M_BindWeaponControls(); + M_BindMapControls(); + M_BindMenuControls(); + M_BindChatControls(MAXPLAYERS); + + key_multi_msgplayer[0] = HUSTR_KEYGREEN; + key_multi_msgplayer[1] = HUSTR_KEYINDIGO; + key_multi_msgplayer[2] = HUSTR_KEYBROWN; + key_multi_msgplayer[3] = HUSTR_KEYRED; + + NET_BindVariables(); + + M_BindIntVariable("mouse_sensitivity", &mouseSensitivity); + M_BindIntVariable("sfx_volume", &sfxVolume); + M_BindIntVariable("music_volume", &musicVolume); + M_BindIntVariable("show_messages", &showMessages); + M_BindIntVariable("screenblocks", &screenblocks); + M_BindIntVariable("detaillevel", &detailLevel); + M_BindIntVariable("snd_channels", &snd_channels); + M_BindIntVariable("vanilla_savegame_limit", &vanilla_savegame_limit); + M_BindIntVariable("vanilla_demo_limit", &vanilla_demo_limit); + + // Multiplayer chat macros + + for (i = 0; i < 10; ++i) { + char buf[12]; + + M_snprintf(buf, sizeof(buf), "chatmacro%i", i); + M_BindStringVariable(buf, &chat_macros[i]); + } +} + +// +// D_GrabMouseCallback +// +// Called to determine whether to grab the mouse pointer +// + +boolean D_GrabMouseCallback(void) { + // Drone players don't need mouse focus + + if (drone) + return false; + + // when menu is active or game is paused, release the mouse + + if (menuactive || paused) + return false; + + // only grab mouse when playing levels (but not demos) + + return (gamestate == GS_LEVEL) && !demoplayback && !advancedemo; +} + +// +// D_DoomLoop +// +void D_DoomLoop(void) { + if (gamevariant == bfgedition && + (demorecording || (gameaction == ga_playdemo) || netgame)) { + printf(" WARNING: You are playing using one of the Doom Classic\n" + " IWAD files shipped with the Doom 3: BFG Edition. These are\n" + " known to be incompatible with the regular IWAD files and\n" + " may cause demos and network games to get out of sync.\n"); + } + + if (demorecording) + G_BeginRecording(); + + main_loop_started = true; + + I_SetWindowTitle(gamedescription); + I_GraphicsCheckCommandLine(); + I_SetGrabMouseCallback(D_GrabMouseCallback); + I_InitGraphics(); + IPU_Init(); + + TryRunTics(); + + V_RestoreBuffer(); + R_ExecuteSetViewSize(); + + D_StartGameLoop(); + + if (testcontrols) { + wipegamestate = gamestate; + } + + while (1) { + // frame syncronous IO operations + I_StartFrame(); + + TryRunTics(); // will run at least one tic + + S_UpdateSounds(players[consoleplayer].mo); // move positional sounds + + // Update display, next frame, with current state. + if (screenvisible) + D_Display(); + + if (M_CheckParm("-onetictest") > 0) + break; + } +} + +// +// DEMO LOOP +// +int demosequence; +int pagetic; +char *pagename; + +// +// D_PageTicker +// Handles timing for warped projection +// +void D_PageTicker(void) { + if (--pagetic < 0) + D_AdvanceDemo(); +} + +// +// D_PageDrawer +// +void D_PageDrawer(void) { + V_DrawPatch(0, 0, W_CacheLumpName(pagename, PU_CACHE)); +} + +// +// D_AdvanceDemo +// Called after each demo or intro demosequence finishes +// +void D_AdvanceDemo(void) { advancedemo = true; } + +// +// This cycles through the demo sequences. +// FIXME - version dependend demo numbers? +// +void D_DoAdvanceDemo(void) { + players[consoleplayer].playerstate = PST_LIVE; // not reborn + advancedemo = false; + usergame = false; // no save / end game here + paused = false; + gameaction = ga_nothing; + + // The Ultimate Doom executable changed the demo sequence to add + // a DEMO4 demo. Final Doom was based on Ultimate, so also + // includes this change; however, the Final Doom IWADs do not + // include a DEMO4 lump, so the game bombs out with an error + // when it reaches this point in the demo sequence. + + // However! There is an alternate version of Final Doom that + // includes a fixed executable. + + if (gameversion == exe_ultimate || gameversion == exe_final) + demosequence = (demosequence + 1) % 7; + else + demosequence = (demosequence + 1) % 6; + + switch (demosequence) { + case 0: + if (gamemode == commercial) + pagetic = TICRATE * 11; + else + pagetic = 170; + gamestate = GS_DEMOSCREEN; + pagename = "TITLEPIC"; + if (gamemode == commercial) + S_StartMusic(mus_dm2ttl); + else + S_StartMusic(mus_intro); + break; + case 1: + G_DeferedPlayDemo("demo1"); + break; + case 2: + pagetic = 200; + gamestate = GS_DEMOSCREEN; + pagename = "CREDIT"; + break; + case 3: + G_DeferedPlayDemo("demo2"); + break; + case 4: + gamestate = GS_DEMOSCREEN; + if (gamemode == commercial) { + pagetic = TICRATE * 11; + pagename = "TITLEPIC"; + S_StartMusic(mus_dm2ttl); + } else { + pagetic = 200; + + if (gameversion >= exe_ultimate) + pagename = "CREDIT"; + else + pagename = "HELP2"; + } + break; + case 5: + G_DeferedPlayDemo("demo3"); + break; + // THE DEFINITIVE DOOM Special Edition demo + case 6: + G_DeferedPlayDemo("demo4"); + break; + } + + // The Doom 3: BFG Edition version of doom2.wad does not have a + // TITLETPIC lump. Use INTERPIC instead as a workaround. + if (gamevariant == bfgedition && !strcasecmp(pagename, "TITLEPIC") && + W_CheckNumForName("titlepic") < 0) { + pagename = "INTERPIC"; + } +} + +// +// D_StartTitle +// +void D_StartTitle(void) { + gameaction = ga_nothing; + demosequence = -1; + D_AdvanceDemo(); +} + +/* JOSEF: Don't support Doom II +static void SetMissionForPackName(char *pack_name) { + int i; + static const struct { + char *name; + int mission; + } packs[] = { + {"doom2", doom2}, {"tnt", pack_tnt}, {"plutonia", pack_plut}, + }; + + for (i = 0; i < arrlen(packs); ++i) { + if (!strcasecmp(pack_name, packs[i].name)) { + gamemission = packs[i].mission; + return; + } + } + + printf("Valid mission packs are:\n"); + + for (i = 0; i < arrlen(packs); ++i) { + printf("\t%s\n", packs[i].name); + } + + I_Error("Unknown mission pack name: %s", pack_name); +} +*/ + +// +// Find out what version of Doom is playing. +// + +void D_IdentifyVersion(void) { + // JOSEF : I'm not supporting other options + gamemission = doom; + gamemode = shareware; + + /* + // gamemission is set up by the D_FindIWAD function. But if + // we specify '-iwad', we have to identify using + // IdentifyIWADByName. However, if the iwad does not match + // any known IWAD name, we may have a dilemma. Try to + // identify by its contents. + + if (gamemission == none) { + unsigned int i; + + for (i = 0; i < numlumps; ++i) { + if (!strncasecmp(lumpinfo[i]->name, "MAP01", 8)) { + gamemission = doom2; + break; + } else if (!strncasecmp(lumpinfo[i]->name, "E1M1", 8)) { + gamemission = doom; + break; + } + } + + if (gamemission == none) { + // Still no idea. I don't think this is going to work. + + I_Error("Unknown or invalid IWAD file."); + } + } + + // Make sure gamemode is set up correctly + + if (logical_gamemission == doom) { + // Doom 1. But which version? + + if (W_CheckNumForName("E4M1") > 0) { + // Ultimate Doom + + gamemode = retail; + } else if (W_CheckNumForName("E3M1") > 0) { + gamemode = registered; + } else { + gamemode = shareware; + } + } else { + int p; + + // Doom 2 of some kind. + gamemode = commercial; + + // We can manually override the gamemission that we got from the + // IWAD detection code. This allows us to eg. play Plutonia 2 + // with Freedoom and get the right level names. + + //! + // @category compat + // @arg + // + // Explicitly specify a Doom II "mission pack" to run as, instead of + // detecting it based on the filename. Valid values are: "doom2", + // "tnt" and "plutonia". + // + p = M_CheckParmWithArgs("-pack", 1); + if (p > 0) { + SetMissionForPackName(myargv[p + 1]); + } + }*/ +} + +// Set the gamedescription string + +void D_SetGameDescription(void) { gamedescription = "Microdoom"; } + +// print title for every printed line +char title[128]; + +static boolean D_AddFile(char *filename) { + wad_file_t *handle; + + printf(" adding %s\n", filename); + handle = W_AddFile(filename); + + return handle != NULL; +} + +static struct { + char *description; + char *cmdline; + GameVersion_t version; +} gameversions[] = { + {"Doom 1.666", "1.666", exe_doom_1_666}, + {"Doom 1.7/1.7a", "1.7", exe_doom_1_7}, + {"Doom 1.8", "1.8", exe_doom_1_8}, + {"Doom 1.9", "1.9", exe_doom_1_9}, + {"Hacx", "hacx", exe_hacx}, + {"Ultimate Doom", "ultimate", exe_ultimate}, + {"Final Doom", "final", exe_final}, + {"Final Doom (alt)", "final2", exe_final2}, + {"Chex Quest", "chex", exe_chex}, + {NULL, NULL, 0}, +}; + +// Initialize the game version + +static void InitGameVersion(void) { + byte *demolump; + char demolumpname[6]; + int demoversion; + int p; + int i; + boolean status; + + //! + // @arg + // @category compat + // + // Emulate a specific version of Doom. Valid values are "1.666", + // "1.7", "1.8", "1.9", "ultimate", "final", "final2", "hacx" and + // "chex". + // + + p = M_CheckParmWithArgs("-gameversion", 1); + + if (p) { + for (i = 0; gameversions[i].description != NULL; ++i) { + if (!strcmp(myargv[p + 1], gameversions[i].cmdline)) { + gameversion = gameversions[i].version; + break; + } + } + + if (gameversions[i].description == NULL) { + printf("Supported game versions:\n"); + + for (i = 0; gameversions[i].description != NULL; ++i) { + printf("\t%s (%s)\n", gameversions[i].cmdline, + gameversions[i].description); + } + + I_Error("Unknown game version '%s'", myargv[p + 1]); + } + } else { + // Determine automatically + + if (gamemission == pack_chex) { + // chex.exe - identified by iwad filename + + gameversion = exe_chex; + } else if (gamemission == pack_hacx) { + // hacx.exe: identified by iwad filename + + gameversion = exe_hacx; + } else if (gamemode == shareware || gamemode == registered || + (gamemode == commercial && gamemission == doom2)) { + // original + gameversion = exe_doom_1_9; + + // Detect version from demo lump + for (i = 1; i <= 3; ++i) { + M_snprintf(demolumpname, 6, "demo%i", i); + if (W_CheckNumForName(demolumpname) > 0) { + demolump = W_CacheLumpName(demolumpname, PU_STATIC); + demoversion = demolump[0]; + W_ReleaseLumpName(demolumpname); + status = true; + switch (demoversion) { + case 106: + gameversion = exe_doom_1_666; + break; + case 107: + gameversion = exe_doom_1_7; + break; + case 108: + gameversion = exe_doom_1_8; + break; + case 109: + gameversion = exe_doom_1_9; + break; + default: + status = false; + break; + } + if (status) { + break; + } + } + } + } else if (gamemode == retail) { + gameversion = exe_ultimate; + } else if (gamemode == commercial) { + // Final Doom: tnt or plutonia + // Defaults to emulating the first Final Doom executable, + // which has the crash in the demo loop; however, having + // this as the default should mean that it plays back + // most demos correctly. + + gameversion = exe_final; + } + } + + // The original exe does not support retail - 4th episode not supported + + if (gameversion < exe_ultimate && gamemode == retail) { + gamemode = registered; + } + + // EXEs prior to the Final Doom exes do not support Final Doom. + + if (gameversion < exe_final && gamemode == commercial && + (gamemission == pack_tnt || gamemission == pack_plut)) { + gamemission = doom2; + } +} + +void PrintGameVersion(void) { + int i; + + for (i = 0; gameversions[i].description != NULL; ++i) { + if (gameversions[i].version == gameversion) { + printf("Emulating the behavior of the " + "'%s' executable.\n", + gameversions[i].description); + break; + } + } +} + +static void G_CheckDemoStatusAtExit(void) { G_CheckDemoStatus(); } + +// +// D_DoomMain +// +void D_DoomMain(void) { + int p; + char file[256]; + char demolumpname[9]; + // int numiwadlumps; + + // JOSEF + connect_to_server(); + + + printf("Z_Init: Init zone memory allocation daemon. \n"); + Z_Init(); + + + // JOSEF: flag to enable CPU wall mapping while in automap + if (M_CheckParm("-livewallupdates")) + livewallupdates = true; + + //! + // @category net + // + // Start a dedicated server, routing packets but not participating + // in the game itself. + // + + if (M_CheckParm("-dedicated") > 0) { + printf("Dedicated server mode.\n"); + NET_DedicatedServer(); + + // Never returns + } + + //! + // @category net + // + // Query the Internet master server for a global list of active + // servers. + // + + if (M_CheckParm("-search")) { + NET_MasterQuery(); + exit(0); + } + + //! + // @arg
+ // @category net + // + // Query the status of the server running on the given IP + // address. + // + + p = M_CheckParmWithArgs("-query", 1); + + if (p) { + NET_QueryAddress(myargv[p + 1]); + exit(0); + } + + //! + // @category net + // + // Search the local LAN for running servers. + // + + if (M_CheckParm("-localsearch")) { + NET_LANQuery(); + exit(0); + } + + //! + // @vanilla + // + // Disable monsters. + // + + nomonsters = M_CheckParm("-nomonsters"); + + //! + // @vanilla + // + // Monsters respawn after being killed. + // + + respawnparm = M_CheckParm("-respawn"); + + //! + // @vanilla + // + // Monsters move faster. + // + + fastparm = M_CheckParm("-fast"); + + //! + // @vanilla + // + // Developer mode. F1 saves a screenshot in the current working + // directory. + // + + devparm = M_CheckParm("-devparm"); + + I_DisplayFPSDots(devparm); + + //! + // @category net + // @vanilla + // + // Start a deathmatch game. + // + + if (M_CheckParm("-deathmatch")) + deathmatch = 1; + + //! + // @category net + // @vanilla + // + // Start a deathmatch 2.0 game. Weapons do not stay in place and + // all items respawn after 30 seconds. + // + + if (M_CheckParm("-altdeath")) + deathmatch = 2; + + if (devparm) + printf(D_DEVSTR); + + // find which dir to use for config files + + M_SetConfigDir(NULL); + + //! + // @arg + // @vanilla + // + // Turbo mode. The player's speed is multiplied by x%. If unspecified, + // x defaults to 200. Values are rounded up to 10 and down to 400. + // + + if ((p = M_CheckParm("-turbo"))) { + int scale = 200; + extern int forwardmove[2]; + extern int sidemove[2]; + + if (p < myargc - 1) + scale = atoi(myargv[p + 1]); + if (scale < 10) + scale = 10; + if (scale > 400) + scale = 400; + printf("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; + } + + // init subsystems + printf("V_Init: allocate screens.\n"); + V_Init(); + + // Load configuration files before initialising other subsystems. + printf("M_LoadDefaults: Load system defaults.\n"); + M_SetConfigFilenames("default.cfg", "chocolate-doom.cfg"); + D_BindVariables(); + M_LoadDefaults(); + + // Save configuration at exit. + I_AtExit(M_SaveDefaults, false); + + // Find main IWAD file and load it. + iwadfile = D_FindIWAD(IWAD_MASK_DOOM, &gamemission); + + // None found? + + if (iwadfile == NULL) { + I_Error("Game mode indeterminate. No IWAD file was found. Try\n" + "specifying one with the '-iwad' command line parameter.\n"); + } + + modifiedgame = false; + + printf("W_Init: Init WADfiles.\n"); + D_AddFile(iwadfile); + // numiwadlumps = numlumps; + + W_CheckCorrectIWAD(doom); + + // Now that we've loaded the IWAD, we can figure out what gamemission + // we're playing and which version of Vanilla Doom we need to emulate. + D_IdentifyVersion(); + printf("Gamemode: %d\n", gamemode); + InitGameVersion(); + + // TODO: surely very related to init game version? + // + // Check which IWAD variant we are using. + + if (W_CheckNumForName("FREEDOOM") >= 0) { + if (W_CheckNumForName("FREEDM") >= 0) { + gamevariant = freedm; + } else { + gamevariant = freedoom; + } + } else if (W_CheckNumForName("DMENUPIC") >= 0) { + gamevariant = bfgedition; + } + + // Load PWAD files. + modifiedgame = W_ParseCommandLine(); + + // Debug: + // W_PrintDirectory(); + + // TODO: the following demo stuff seems like a good candidate for extracting + + //! + // @arg + // @category demo + // @vanilla + // + // Play back the demo named demo.lmp. + // + + p = M_CheckParmWithArgs("-playdemo", 1); + + if (!p) { + //! + // @arg + // @category demo + // @vanilla + // + // Play back the demo named demo.lmp, determining the framerate + // of the screen. + // + p = M_CheckParmWithArgs("-timedemo", 1); + } + + if (p) { + char *uc_filename = strdup(myargv[p + 1]); + M_ForceUppercase(uc_filename); + + // With Vanilla you have to specify the file without extension, + // but make that optional. + if (M_StringEndsWith(uc_filename, ".LMP")) { + M_StringCopy(file, myargv[p + 1], sizeof(file)); + } else { + M_snprintf(file, sizeof(file), "%s.lmp", myargv[p + 1]); + } + + free(uc_filename); + + if (D_AddFile(file)) { + M_StringCopy(demolumpname, lumpinfo[numlumps - 1]->name, + sizeof(demolumpname)); + } else { + // If file failed to load, still continue trying to play + // the demo in the same way as Vanilla Doom. This makes + // tricks like "-playdemo demo1" possible. + + M_StringCopy(demolumpname, myargv[p + 1], sizeof(demolumpname)); + } + + printf("Playing demo %s.\n", file); + } + + I_AtExit(G_CheckDemoStatusAtExit, true); + + // Generate the WAD hash table. Speed things up a bit. + W_GenerateHashTable(); + + // Set the gamedescription string. + D_SetGameDescription(); + + savegamedir = M_GetSaveGameDir(D_SaveGameIWADName(gamemission)); + + if (W_CheckNumForName("SS_START") >= 0 || W_CheckNumForName("FF_END") >= 0) { + I_PrintDivider(); + printf(" WARNING: The loaded WAD file contains modified sprites or\n" + " floor textures. You may want to use the '-merge' command\n" + " line option instead of '-file'.\n"); + } + + /*I_PrintStartupBanner(gamedescription);*/ + + // Freedoom's IWADs are Boom-compatible, which means they usually + // don't work in Vanilla (though FreeDM is okay). Show a warning + // message and give a link to the website. + /*if (gamevariant == freedoom) {*/ + /*printf(" WARNING: You are playing using one of the Freedoom IWAD\n"*/ + /*" files, which might not work in this port. See this page\n"*/ + /*" for more information on how to play using Freedoom:\n"*/ + /*" https://www.chocolate-doom.org/wiki/index.php/Freedoom\n");*/ + /*I_PrintDivider();*/ + /*}*/ + + printf("I_Init: Setting up machine state.\n"); + I_CheckIsScreensaver(); + printf("Checked screen saver\n"); + I_InitTimer(); + printf("inited timer\n"); + I_InitJoystick(); + printf("inited joystick \n"); + I_InitSound(true); + printf("initted sound\n"); + I_InitMusic(); + printf("initted music\n"); + + printf("NET_Init: Init network subsystem.\n"); + NET_Init(); + + // Initial netgame startup. Connect to server etc. + D_ConnectNetGame(); + + // get skill / episode / map from parms + startskill = sk_medium; + startepisode = 1; + startmap = 1; + autostart = false; + + //! + // @arg + // @vanilla + // + // Set the game skill, 1-5 (1: easiest, 5: hardest). A skill of + // 0 disables all monsters. + // + + p = M_CheckParmWithArgs("-skill", 1); + + if (p) { + startskill = myargv[p + 1][0] - '1'; + autostart = true; + } + + //! + // @arg + // @vanilla + // + // Start playing on episode n (1-4) + // + + p = M_CheckParmWithArgs("-episode", 1); + + if (p) { + startepisode = myargv[p + 1][0] - '0'; + startmap = 1; + autostart = true; + } + + timelimit = 0; + + //! + // @arg + // @category net + // @vanilla + // + // For multiplayer games: exit each level after n minutes. + // + + p = M_CheckParmWithArgs("-timer", 1); + + if (p) { + timelimit = atoi(myargv[p + 1]); + } + + //! + // @category net + // @vanilla + // + // Austin Virtual Gaming: end levels after 20 minutes. + // + + p = M_CheckParm("-avg"); + + if (p) { + timelimit = 20; + } + + //! + // @arg [ | ] + // @vanilla + // + // Start a game immediately, warping to ExMy (Doom 1) or MAPxy + // (Doom 2) + // + + p = M_CheckParmWithArgs("-warp", 1); + + if (p) { + if (gamemode == commercial) + startmap = atoi(myargv[p + 1]); + else { + startepisode = myargv[p + 1][0] - '0'; + + if (p + 2 < myargc) { + startmap = myargv[p + 2][0] - '0'; + } else { + startmap = 1; + } + } + autostart = true; + } + + // Undocumented: + // Invoked by setup to test the controls. + + p = M_CheckParm("-testcontrols"); + + if (p > 0) { + startepisode = 1; + startmap = 1; + autostart = true; + testcontrols = true; + } + + // Check for load game parameter + // We do this here and save the slot number, so that the network code + // can override it or send the load slot to other players. + + //! + // @arg + // @vanilla + // + // Load the game in slot s. + // + + p = M_CheckParmWithArgs("-loadgame", 1); + + if (p) { + startloadgame = atoi(myargv[p + 1]); + } else { + // Not loading a game + startloadgame = -1; + } + + printf("M_Init: Init miscellaneous info.\n"); + M_Init(); + + printf("R_Init: Init DOOM refresh daemon - "); + R_Init(); + + printf("\nP_Init: Init Playloop state.\n"); + P_Init(); + + printf("S_Init: Setting up sound.\n"); + S_Init(sfxVolume * 8, musicVolume * 8); + + printf("D_CheckNetGame: Checking network game status.\n"); + D_CheckNetGame(); + + PrintGameVersion(); + + printf("HU_Init: Setting up heads up display.\n"); + HU_Init(); + + printf("ST_Init: Init status bar.\n"); + ST_Init(); + + + // If Doom II without a MAP01 lump, this is a store demo. + // Moved this here so that MAP01 isn't constantly looked up + // in the main loop. + + if (gamemode == commercial && W_CheckNumForName("map01") < 0) + storedemo = true; + + if (M_CheckParmWithArgs("-statdump", 1)) { + I_AtExit(StatDump, true); + printf("External statistics registered.\n"); + } + + //! + // @arg + // @category demo + // @vanilla + // + // Record a demo named x.lmp. + // + + p = M_CheckParmWithArgs("-record", 1); + + if (p) { + G_RecordDemo(myargv[p + 1]); + autostart = true; + } + + p = M_CheckParmWithArgs("-playdemo", 1); + if (p) { + singledemo = true; // quit after one demo + G_DeferedPlayDemo(demolumpname); + D_DoomLoop(); // never returns + } + + p = M_CheckParmWithArgs("-timedemo", 1); + if (p) { + G_TimeDemo(demolumpname); + D_DoomLoop(); // never returns + } + + if (startloadgame >= 0) { + M_StringCopy(file, P_SaveGameFile(startloadgame), sizeof(file)); + G_LoadGame(file); + } + + if (gameaction != ga_loadgame) { + if (autostart || netgame) + G_InitNew(startskill, startepisode, startmap); + else + D_StartTitle(); // start up intro loop + } + + D_DoomLoop(); // never returns +} diff --git a/client/src/d_main.h b/client/src/d_main.h new file mode 100644 index 0000000..0c0491b --- /dev/null +++ b/client/src/d_main.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + +#ifndef __D_MAIN__ +#define __D_MAIN__ + +#include "doomdef.h" + +// Read events from all input devices + +void D_ProcessEvents(void); + +// +// BASE LEVEL +// +void D_PageTicker(void); +void D_PageDrawer(void); +void D_AdvanceDemo(void); +void D_DoAdvanceDemo(void); +void D_StartTitle(void); + +extern void D_DoomMain(void); + +// +// GLOBAL VARIABLES +// + +extern gameaction_t gameaction; + +#endif diff --git a/client/src/d_mode.c b/client/src/d_mode.c new file mode 100644 index 0000000..2d3ef3c --- /dev/null +++ b/client/src/d_mode.c @@ -0,0 +1,212 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: +// Functions and definitions relating to the game type and operational +// mode. +// + +#include "doomtype.h" +#include "d_mode.h" + +// Valid game mode/mission combinations, with the number of +// episodes/maps for each. + +static struct +{ + GameMission_t mission; + GameMode_t mode; + int episode; + int map; +} valid_modes[] = { + { pack_chex, retail, 1, 5 }, + { doom, shareware, 1, 9 }, + { doom, registered, 3, 9 }, + { doom, retail, 4, 9 }, + { doom2, commercial, 1, 32 }, + { pack_tnt, commercial, 1, 32 }, + { pack_plut, commercial, 1, 32 }, + { pack_hacx, commercial, 1, 32 }, + { heretic, shareware, 1, 9 }, + { heretic, registered, 3, 9 }, + { heretic, retail, 5, 9 }, + { hexen, commercial, 1, 60 }, + { strife, commercial, 1, 34 }, +}; + +// Check that a gamemode+gamemission received over the network is valid. + +boolean D_ValidGameMode(GameMission_t mission, GameMode_t mode) +{ + int i; + + for (i=0; i= 1 && map <= 3; + } + else if (mode == registered && episode == 4) + { + return map == 1; + } + } + + // Find the table entry for this mission/mode combination. + + for (i=0; i= 1 && episode <= valid_modes[i].episode + && map >= 1 && map <= valid_modes[i].map; + } + } + + // Unknown mode/mission combination + + return false; +} + +// Get the number of valid episodes for the specified mission/mode. + +int D_GetNumEpisodes(GameMission_t mission, GameMode_t mode) +{ + int episode; + + episode = 1; + + while (D_ValidEpisodeMap(mission, mode, episode, 1)) + { + ++episode; + } + + return episode - 1; +} + +// Table of valid versions + +static struct { + GameMission_t mission; + GameVersion_t version; +} valid_versions[] = { + { doom, exe_doom_1_666 }, + { doom, exe_doom_1_7 }, + { doom, exe_doom_1_8 }, + { doom, exe_doom_1_9 }, + { doom, exe_hacx }, + { doom, exe_ultimate }, + { doom, exe_final }, + { doom, exe_final2 }, + { doom, exe_chex }, + { heretic, exe_heretic_1_3 }, + { hexen, exe_hexen_1_1 }, + { strife, exe_strife_1_2 }, + { strife, exe_strife_1_31 }, +}; + +boolean D_ValidGameVersion(GameMission_t mission, GameVersion_t version) +{ + int i; + + // All Doom variants can use the Doom versions. + + if (mission == doom2 || mission == pack_plut || mission == pack_tnt + || mission == pack_hacx || mission == pack_chex) + { + mission = doom; + } + + for (i=0; i + +#include "d_loop.h" +#include "d_main.h" +#include "d_player.h" +#include "d_ticcmd.h" +#include "doomdef.h" +#include "doomstat.h" +#include "doomtype.h" +#include "g_game.h" +#include "m_argv.h" +#include "m_menu.h" +#include "m_misc.h" +#include "net_defs.h" +#include "tables.h" +#include "w_checksum.h" +#include "w_wad.h" + +#include "ipu_host.h" + +ticcmd_t *netcmds; + +// Called when a player leaves the game + +static void PlayerQuitGame(player_t *player) { + static char exitmsg[80]; + unsigned int player_num; + + player_num = player - players; + + // Do this the same way as Vanilla Doom does, to allow dehacked + // replacements of this message + + M_StringCopy(exitmsg, "Player 1 left the game", sizeof(exitmsg)); + + exitmsg[7] += player_num; + + playeringame[player_num] = false; + players[consoleplayer].message = exitmsg; + + // TODO: check if it is sensible to do this: + + if (demorecording) { + G_CheckDemoStatus(); + } +} + +void D_RunTic(ticcmd_t *cmds, boolean *ingame) { + extern boolean advancedemo; + unsigned int i; + + // Check for player quits. + + for (i = 0; i < MAXPLAYERS; ++i) { + if (!demoplayback && playeringame[i] && !ingame[i]) { + PlayerQuitGame(&players[i]); + } + } + + netcmds = cmds; + + // check that there are players in the game. if not, we cannot + // run a tic. + + if (advancedemo) + D_DoAdvanceDemo(); + + G_Ticker(); + IPU_G_Ticker(); +} + +// static loop_interface_t doom_loop_interface = {D_ProcessEvents, G_BuildTiccmd, + // RunTic, M_Ticker}; + +// Load game settings from the specified structure and +// set global variables. + +static void LoadGameSettings(net_gamesettings_t *settings) { + unsigned int i; + + deathmatch = settings->deathmatch; + startepisode = settings->episode; + startmap = settings->map; + startskill = settings->skill; + startloadgame = settings->loadgame; + lowres_turn = settings->lowres_turn; + nomonsters = settings->nomonsters; + fastparm = settings->fast_monsters; + respawnparm = settings->respawn_monsters; + timelimit = settings->timelimit; + consoleplayer = settings->consoleplayer; + + if (lowres_turn) { + printf("NOTE: Turning resolution is reduced; this is probably " + "because there is a client recording a Vanilla demo.\n"); + } + + for (i = 0; i < MAXPLAYERS; ++i) { + playeringame[i] = i < settings->num_players; + } +} + +// Save the game settings from global variables to the specified +// game settings structure. + +static void SaveGameSettings(net_gamesettings_t *settings) { + // Fill in game settings structure with appropriate parameters + // for the new game + + settings->deathmatch = deathmatch; + settings->episode = startepisode; + settings->map = startmap; + settings->skill = startskill; + settings->loadgame = startloadgame; + settings->gameversion = gameversion; + settings->nomonsters = nomonsters; + settings->fast_monsters = fastparm; + settings->respawn_monsters = respawnparm; + settings->timelimit = timelimit; + + settings->lowres_turn = + M_CheckParm("-record") > 0 && M_CheckParm("-longtics") == 0; +} + +static void InitConnectData(net_connect_data_t *connect_data) { + connect_data->max_players = MAXPLAYERS; + connect_data->drone = false; + + //! + // @category net + // + // Run as the left screen in three screen mode. + // + + if (M_CheckParm("-left") > 0) { + viewangleoffset = ANG90; + connect_data->drone = true; + } + + //! + // @category net + // + // Run as the right screen in three screen mode. + // + + if (M_CheckParm("-right") > 0) { + viewangleoffset = ANG270; + connect_data->drone = true; + } + + // + // Connect data + // + + // Game type fields: + + connect_data->gamemode = gamemode; + connect_data->gamemission = gamemission; + + // Are we recording a demo? Possibly set lowres turn mode + + connect_data->lowres_turn = + M_CheckParm("-record") > 0 && M_CheckParm("-longtics") == 0; + + // Read checksums of our WAD directory + + W_Checksum(connect_data->wad_sha1sum); + + // Are we playing with the Freedoom IWAD? + + connect_data->is_freedoom = W_CheckNumForName("FREEDOOM") >= 0; +} + +void D_ConnectNetGame(void) { + net_connect_data_t connect_data; + + InitConnectData(&connect_data); + netgame = D_InitNetGame(&connect_data); + + //! + // @category net + // + // Start the game playing as though in a netgame with a single + // player. This can also be used to play back single player netgame + // demos. + // + + if (M_CheckParm("-solo-net") > 0) { + netgame = true; + } +} + +// +// D_CheckNetGame +// Works out player numbers among the net participants +// +void D_CheckNetGame(void) { + net_gamesettings_t settings; + + if (netgame) { + autostart = true; + } + + // D_RegisterLoopCallbacks(&doom_loop_interface); + + SaveGameSettings(&settings); + D_StartNetGame(&settings, NULL); + LoadGameSettings(&settings); + + printf("startskill %i deathmatch: %i startmap: %i startepisode: %i\n", + startskill, deathmatch, startmap, startepisode); + + printf("player %i of %i (%i nodes)\n", consoleplayer + 1, + settings.num_players, settings.num_players); + + // Show players here; the server might have specified a time limit + + if (timelimit > 0 && deathmatch) { + // Gross hack to work like Vanilla: + + if (timelimit == 20 && M_CheckParm("-avg")) { + printf("Austin Virtual Gaming: Levels will end " + "after 20 minutes\n"); + } else { + printf("Levels will end after %d minute", timelimit); + if (timelimit > 1) + printf("s"); + printf(".\n"); + } + } +} diff --git a/client/src/d_player.h b/client/src/d_player.h new file mode 100644 index 0000000..7163abc --- /dev/null +++ b/client/src/d_player.h @@ -0,0 +1,195 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// + +#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" + +#include "net_defs.h" + +// +// 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; + + // 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; + + int 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. + 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]; + +} wbstartstruct_t; + +#endif diff --git a/client/src/d_textur.h b/client/src/d_textur.h new file mode 100644 index 0000000..706b356 --- /dev/null +++ b/client/src/d_textur.h @@ -0,0 +1,35 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Typedefs related to to textures etc., +// isolated here to make it easier separating modules. +// + +#ifndef __D_TEXTUR__ +#define __D_TEXTUR__ + +#include "doomtype.h" + +// +// Flats? +// +// a pic is an unmasked block of pixels +typedef struct { + byte width; + byte height; + byte data; +} pic_t; + +#endif diff --git a/client/src/d_think.h b/client/src/d_think.h new file mode 100644 index 0000000..ab61382 --- /dev/null +++ b/client/src/d_think.h @@ -0,0 +1,54 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// MapObj data. Map Objects or mobjs are actors, entities, +// thinker, take-your-pick... anything that moves, acts, or +// suffers state changes of more or less violent nature. +// + +#ifndef __D_THINK__ +#define __D_THINK__ + +// +// Experimental stuff. +// To compile this as "ANSI C with classes" +// we will need to handle the various +// action functions cleanly. +// +typedef void (*actionf_v)(); +typedef void (*actionf_p1)(void *); +typedef void (*actionf_p2)(void *, void *); + +typedef union { + actionf_v acv; + actionf_p1 acp1; + actionf_p2 acp2; + +} actionf_t; + +// Historically, "think_t" is yet another +// function pointer to a routine to handle +// an actor. +typedef actionf_t think_t; + +// Doubly linked list of actors. +typedef struct thinker_s { + struct thinker_s *prev; + struct thinker_s *next; + think_t function; + +} thinker_t; + +#endif diff --git a/client/src/d_ticcmd.h b/client/src/d_ticcmd.h new file mode 100644 index 0000000..daf0da3 --- /dev/null +++ b/client/src/d_ticcmd.h @@ -0,0 +1,56 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + + +#ifndef __D_TICCMD__ +#define __D_TICCMD__ + +#include "doomtype.h" + + +// The data sampled per tick (single player) +// and transmitted to other peers (multiplayer). +// Mainly movements/button commands per game tick, +// plus a checksum for internal state consistency. + +typedef struct +{ + signed char forwardmove; // *2048 for move + signed char sidemove; // *2048 for move + short angleturn; // <<16 for angle delta + byte chatchar; + byte buttons; + // villsa [STRIFE] according to the asm, + // consistancy is a short, not a byte + byte consistancy; // checks for net game + + // villsa - Strife specific: + + byte buttons2; + int inventory; + + // Heretic/Hexen specific: + + byte lookfly; // look/fly up/down/centering + byte arti; // artitype_t to use +} ticcmd_t; + + + +#endif diff --git a/client/src/doomdata.h b/client/src/doomdata.h new file mode 100644 index 0000000..2606459 --- /dev/null +++ b/client/src/doomdata.h @@ -0,0 +1,184 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// all external data is defined here +// most of the data is loaded into different structures at run time +// some internal structures shared by many modules are here +// + +#ifndef __DOOMDATA__ +#define __DOOMDATA__ + +// The most basic types we use, portability. +#include "doomtype.h" + +// Some global defines, that configure the game. +#include "doomdef.h" + +// +// Map level types. +// The following data structures define the persistent format +// used in the lumps of the WAD files. +// + +// Lump order in a map WAD: each map needs a couple of lumps +// to provide a complete scene geometry description. +enum { + ML_LABEL, // A separator, name, ExMx or MAPxx + ML_THINGS, // Monsters, items.. + ML_LINEDEFS, // LineDefs, from editing + ML_SIDEDEFS, // SideDefs, from editing + ML_VERTEXES, // Vertices, edited and BSP splits generated + ML_SEGS, // LineSegs, from LineDefs split by BSP + ML_SSECTORS, // SubSectors, list of LineSegs + ML_NODES, // BSP nodes + ML_SECTORS, // Sectors, from editing + ML_REJECT, // LUT, sector-sector visibility + ML_BLOCKMAP // LUT, motion clipping, walls/grid element +}; + +// A single Vertex. +typedef PACKED_STRUCT({ + short x; + short y; +}) mapvertex_t; + +// A SideDef, defining the visual appearance of a wall, +// by setting textures and offsets. +typedef PACKED_STRUCT({ + short textureoffset; + short rowoffset; + char toptexture[8]; + char bottomtexture[8]; + char midtexture[8]; + // Front sector, towards viewer. + short sector; +}) mapsidedef_t; + +// A LineDef, as used for editing, and as input +// to the BSP builder. +typedef PACKED_STRUCT({ + short v1; + short v2; + short flags; + short special; + short tag; + // sidenum[1] will be -1 if one sided + short sidenum[2]; +}) maplinedef_t; + +// +// LineDef attributes. +// + +// Solid, is an obstacle. +#define ML_BLOCKING 1 + +// Blocks monsters only. +#define ML_BLOCKMONSTERS 2 + +// Backside will not be present at all +// if not two sided. +#define ML_TWOSIDED 4 + +// If a texture is pegged, the texture will have +// the end exposed to air held constant at the +// top or bottom of the texture (stairs or pulled +// down things) and will move with a height change +// of one of the neighbor sectors. +// Unpegged textures allways have the first row of +// the texture at the top pixel of the line for both +// top and bottom textures (use next to windows). + +// upper texture unpegged +#define ML_DONTPEGTOP 8 + +// lower texture unpegged +#define ML_DONTPEGBOTTOM 16 + +// In AutoMap: don't map as two sided: IT'S A SECRET! +#define ML_SECRET 32 + +// Sound rendering: don't let sound cross two of these. +#define ML_SOUNDBLOCK 64 + +// Don't draw on the automap at all. +#define ML_DONTDRAW 128 + +// Set if already seen, thus drawn in automap. +#define ML_MAPPED 256 + +// Sector definition, from editing. +typedef PACKED_STRUCT({ + short floorheight; + short ceilingheight; + char floorpic[8]; + char ceilingpic[8]; + short lightlevel; + short special; + short tag; +}) mapsector_t; + +// SubSector, as generated by BSP. +typedef PACKED_STRUCT({ + short numsegs; + // Index of first one, segs are stored sequentially. + short firstseg; +}) mapsubsector_t; + +// LineSeg, generated by splitting LineDefs +// using partition lines selected by BSP builder. +typedef PACKED_STRUCT({ + short v1; + short v2; + short angle; + short linedef; + short side; + short offset; +}) mapseg_t; + +// BSP node structure. + +// Indicate a leaf. +#define NF_SUBSECTOR 0x8000 + +typedef PACKED_STRUCT({ + // Partition line from (x,y) to x+dx,y+dy) + short x; + short y; + short dx; + short dy; + + // Bounding box for each child, + // clip against view frustum. + short bbox[2][4]; + + // If NF_SUBSECTOR its a subsector, + // else it's a node of another subtree. + unsigned short children[2]; + +}) mapnode_t; + +// Thing definition, position, orientation and type, +// plus skill/visibility flags and attributes. +typedef PACKED_STRUCT({ + short x; + short y; + short angle; + short type; + short options; +}) mapthing_t; + +#endif // __DOOMDATA__ diff --git a/client/src/doomdef.c b/client/src/doomdef.c new file mode 100644 index 0000000..74841cf --- /dev/null +++ b/client/src/doomdef.c @@ -0,0 +1,24 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// DoomDef - basic defines for DOOM, e.g. Version, game mode +// and skill level, and display parameters. +// + +#include "doomdef.h" + +// Location for any defines turned variables. + +// None. diff --git a/client/src/doomdef.h b/client/src/doomdef.h new file mode 100644 index 0000000..0aac349 --- /dev/null +++ b/client/src/doomdef.h @@ -0,0 +1,143 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Internally used data structures for virtually everything, +// lots of other stuff. +// + +#ifndef __DOOMDEF__ +#define __DOOMDEF__ + +#include "i_timer.h" + +// +// Global parameters/defines. +// +// DOOM version +#define DOOM_VERSION 109 + +// Version code for cph's longtics hack ("v1.91") +#define DOOM_191_VERSION 111 + +// The maximum number of players, multiplayer/networking. +#define MAXPLAYERS 4 + +// The current state of the game: whether we are +// playing, gazing at the intermission screen, +// the game final animation, or a demo. +typedef enum { + GS_LEVEL, + GS_INTERMISSION, + GS_DEMOSCREEN, +} gamestate_t; + +typedef enum { + ga_nothing, + ga_loadlevel, + ga_newgame, + ga_loadgame, + ga_savegame, + ga_playdemo, + ga_completed, + ga_victory, + ga_worlddone, + ga_screenshot +} gameaction_t; + +// +// Difficulty/skill settings/filters. +// + +// Skill flags. +#define MTF_EASY 1 +#define MTF_NORMAL 2 +#define MTF_HARD 4 + +// Deaf monsters/do not react to sound. +#define MTF_AMBUSH 8 + +// +// Key cards. +// +typedef enum { + it_bluecard, + it_yellowcard, + it_redcard, + it_blueskull, + it_yellowskull, + it_redskull, + + NUMCARDS + +} card_t; + +// The defined weapons, +// including a marker indicating +// user has not changed weapon. +typedef enum { + wp_fist, + wp_pistol, + wp_shotgun, + wp_chaingun, + wp_missile, + wp_plasma, + wp_bfg, + wp_chainsaw, + wp_supershotgun, + + NUMWEAPONS, + + // No pending weapon change. + wp_nochange + +} weapontype_t; + +// Ammunition types defined. +typedef enum { + am_clip, // Pistol / chaingun ammo. + am_shell, // Shotgun / double barreled shotgun. + am_cell, // Plasma rifle, BFG. + am_misl, // Missile launcher. + NUMAMMO, + am_noammo // Unlimited for chainsaw / fist. + +} ammotype_t; + +// Power up artifacts. +typedef enum { + pw_invulnerability, + pw_strength, + pw_invisibility, + pw_ironfeet, + pw_allmap, + pw_infrared, + NUMPOWERS + +} powertype_t; + +// +// Power up durations, +// how many seconds till expiration, +// assuming TICRATE is 35 ticks/second. +// +typedef enum { + INVULNTICS = (30 * TICRATE), + INVISTICS = (60 * TICRATE), + INFRATICS = (120 * TICRATE), + IRONTICS = (60 * TICRATE) + +} powerduration_t; + +#endif // __DOOMDEF__ diff --git a/client/src/doomkeys.h b/client/src/doomkeys.h new file mode 100644 index 0000000..35310a6 --- /dev/null +++ b/client/src/doomkeys.h @@ -0,0 +1,153 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Key definitions +// + +#ifndef __DOOMKEYS__ +#define __DOOMKEYS__ + +// +// DOOM keyboard definition. +// This is the stuff configured by Setup.Exe. +// Most key data are simple ascii (uppercased). +// +#define KEY_RIGHTARROW 0xae +#define KEY_LEFTARROW 0xac +#define KEY_UPARROW 0xad +#define KEY_DOWNARROW 0xaf +#define KEY_ESCAPE 27 +#define KEY_ENTER 13 +#define KEY_TAB 9 +#define KEY_F1 (0x80+0x3b) +#define KEY_F2 (0x80+0x3c) +#define KEY_F3 (0x80+0x3d) +#define KEY_F4 (0x80+0x3e) +#define KEY_F5 (0x80+0x3f) +#define KEY_F6 (0x80+0x40) +#define KEY_F7 (0x80+0x41) +#define KEY_F8 (0x80+0x42) +#define KEY_F9 (0x80+0x43) +#define KEY_F10 (0x80+0x44) +#define KEY_F11 (0x80+0x57) +#define KEY_F12 (0x80+0x58) + +#define KEY_BACKSPACE 0x7f +#define KEY_PAUSE 0xff + +#define KEY_EQUALS 0x3d +#define KEY_MINUS 0x2d + +#define KEY_RSHIFT (0x80+0x36) +#define KEY_RCTRL (0x80+0x1d) +#define KEY_RALT (0x80+0x38) + +#define KEY_LALT KEY_RALT + +// new keys: + +#define KEY_CAPSLOCK (0x80+0x3a) +#define KEY_NUMLOCK (0x80+0x45) +#define KEY_SCRLCK (0x80+0x46) +#define KEY_PRTSCR (0x80+0x59) + +#define KEY_HOME (0x80+0x47) +#define KEY_END (0x80+0x4f) +#define KEY_PGUP (0x80+0x49) +#define KEY_PGDN (0x80+0x51) +#define KEY_INS (0x80+0x52) +#define KEY_DEL (0x80+0x53) + +#define KEYP_0 0 +#define KEYP_1 KEY_END +#define KEYP_2 KEY_DOWNARROW +#define KEYP_3 KEY_PGDN +#define KEYP_4 KEY_LEFTARROW +#define KEYP_5 '5' +#define KEYP_6 KEY_RIGHTARROW +#define KEYP_7 KEY_HOME +#define KEYP_8 KEY_UPARROW +#define KEYP_9 KEY_PGUP + +#define KEYP_DIVIDE '/' +#define KEYP_PLUS '+' +#define KEYP_MINUS '-' +#define KEYP_MULTIPLY '*' +#define KEYP_PERIOD 0 +#define KEYP_EQUALS KEY_EQUALS +#define KEYP_ENTER KEY_ENTER + +#define SCANCODE_TO_KEYS_ARRAY { \ + 0, 0, 0, 0, 'a', /* 0-9 */ \ + 'b', 'c', 'd', 'e', 'f', \ + 'g', 'h', 'i', 'j', 'k', /* 10-19 */ \ + 'l', 'm', 'n', 'o', 'p', \ + 'q', 'r', 's', 't', 'u', /* 20-29 */ \ + 'v', 'w', 'x', 'y', 'z', \ + '1', '2', '3', '4', '5', /* 30-39 */ \ + '6', '7', '8', '9', '0', \ + KEY_ENTER, KEY_ESCAPE, KEY_BACKSPACE, KEY_TAB, ' ', /* 40-49 */ \ + KEY_MINUS, KEY_EQUALS, '[', ']', '\\', \ + 0, ';', '\'', '`', ',', /* 50-59 */ \ + '.', '/', KEY_CAPSLOCK, KEY_F1, KEY_F2, \ + KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, /* 60-69 */ \ + KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, \ + KEY_PRTSCR, KEY_SCRLCK, KEY_PAUSE, KEY_INS, KEY_HOME, /* 70-79 */ \ + KEY_PGUP, KEY_DEL, KEY_END, KEY_PGDN, KEY_RIGHTARROW, \ + KEY_LEFTARROW, KEY_DOWNARROW, KEY_UPARROW, /* 80-89 */ \ + KEY_NUMLOCK, KEYP_DIVIDE, \ + KEYP_MULTIPLY, KEYP_MINUS, KEYP_PLUS, KEYP_ENTER, KEYP_1, \ + KEYP_2, KEYP_3, KEYP_4, KEYP_5, KEYP_6, /* 90-99 */ \ + KEYP_7, KEYP_8, KEYP_9, KEYP_0, KEYP_PERIOD, \ + 0, 0, 0, KEYP_EQUALS, /* 100-103 */ \ +} + +// Default names for keys, to use in English or as fallback. +#define KEY_NAMES_ARRAY { \ + { KEY_BACKSPACE, "BACKSP" }, { KEY_TAB, "TAB" }, \ + { KEY_INS, "INS" }, { KEY_DEL, "DEL" }, \ + { KEY_PGUP, "PGUP" }, { KEY_PGDN, "PGDN" }, \ + { KEY_ENTER, "ENTER" }, { KEY_ESCAPE, "ESC" }, \ + { KEY_F1, "F1" }, { KEY_F2, "F2" }, \ + { KEY_F3, "F3" }, { KEY_F4, "F4" }, \ + { KEY_F5, "F5" }, { KEY_F6, "F6" }, \ + { KEY_F7, "F7" }, { KEY_F8, "F8" }, \ + { KEY_F9, "F9" }, { KEY_F10, "F10" }, \ + { KEY_F11, "F11" }, { KEY_F12, "F12" }, \ + { KEY_HOME, "HOME" }, { KEY_END, "END" }, \ + { KEY_MINUS, "-" }, { KEY_EQUALS, "=" }, \ + { KEY_NUMLOCK, "NUMLCK" }, { KEY_SCRLCK, "SCRLCK" }, \ + { KEY_PAUSE, "PAUSE" }, { KEY_PRTSCR, "PRTSC" }, \ + { KEY_UPARROW, "UP" }, { KEY_DOWNARROW, "DOWN" }, \ + { KEY_LEFTARROW, "LEFT" }, { KEY_RIGHTARROW, "RIGHT" }, \ + { KEY_RALT, "ALT" }, { KEY_LALT, "ALT" }, \ + { KEY_RSHIFT, "SHIFT" }, { KEY_CAPSLOCK, "CAPS" }, \ + { KEY_RCTRL, "CTRL" }, { ' ', "SPACE" }, \ + { 'a', "A" }, { 'b', "B" }, { 'c', "C" }, { 'd', "D" }, \ + { 'e', "E" }, { 'f', "F" }, { 'g', "G" }, { 'h', "H" }, \ + { 'i', "I" }, { 'j', "J" }, { 'k', "K" }, { 'l', "L" }, \ + { 'm', "M" }, { 'n', "N" }, { 'o', "O" }, { 'p', "P" }, \ + { 'q', "Q" }, { 'r', "R" }, { 's', "S" }, { 't', "T" }, \ + { 'u', "U" }, { 'v', "V" }, { 'w', "W" }, { 'x', "X" }, \ + { 'y', "Y" }, { 'z', "Z" }, { '0', "0" }, { '1', "1" }, \ + { '2', "2" }, { '3', "3" }, { '4', "4" }, { '5', "5" }, \ + { '6', "6" }, { '7', "7" }, { '8', "8" }, { '9', "9" }, \ + { '[', "[" }, { ']', "]" }, { ';', ";" }, { '`', "`" }, \ + { ',', "," }, { '.', "." }, { '/', "/" }, { '\\', "\\" }, \ + { '\'', "\'" }, \ +} + +#endif // __DOOMKEYS__ + diff --git a/client/src/doomstat.c b/client/src/doomstat.c new file mode 100644 index 0000000..6617475 --- /dev/null +++ b/client/src/doomstat.c @@ -0,0 +1,29 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Put all global tate variables here. +// + +#include "doomstat.h" + +// Game Mode - identify IWAD as shareware, retail etc. +GameMode_t gamemode = indetermined; +GameMission_t gamemission = doom; +GameVersion_t gameversion = exe_final2; +GameVariant_t gamevariant = vanilla; +char *gamedescription; + +// Set if homebrew PWAD stuff has been added. +boolean modifiedgame; diff --git a/client/src/doomstat.h b/client/src/doomstat.h new file mode 100644 index 0000000..6013b29 --- /dev/null +++ b/client/src/doomstat.h @@ -0,0 +1,239 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// All the global variables that store the internal state. +// Theoretically speaking, the internal state of the engine +// should be found by looking at the variables collected +// here, and every relevant module will have to include +// this header file. +// In practice, things are a bit messy. +// + +#ifndef __D_STATE__ +#define __D_STATE__ + +// Game mode/mission +#include "d_mode.h" +// We need the playr data structure as well. +#include "d_player.h" +#include "d_ticcmd.h" +// We need globally shared data structures, +// for defining the global state variables. +#include "doomdata.h" +#include "doomdef.h" +#include "doomtype.h" + +// ------------------------ +// Command line parameters. +// +extern boolean nomonsters; // checkparm of -nomonsters +extern boolean respawnparm; // checkparm of -respawn +extern boolean fastparm; // checkparm of -fast + +extern boolean devparm; // DEBUG: launched with -devparm + +// ----------------------------------------------------- +// Game Mode - identify IWAD as shareware, retail etc. +// +extern GameMode_t gamemode; +extern GameMission_t gamemission; +extern GameVersion_t gameversion; +extern GameVariant_t gamevariant; +extern char *gamedescription; + +// Convenience macro. +// 'gamemission' can be equal to pack_chex or pack_hacx, but these are +// just modified versions of doom and doom2, and should be interpreted +// as the same most of the time. + +#define logical_gamemission \ + (gamemission == pack_chex ? doom \ + : gamemission == pack_hacx ? doom2 : gamemission) + +// Set if homebrew PWAD stuff has been added. +extern boolean modifiedgame; + +// ------------------------------------------- +// Selected skill type, map etc. +// + +// Defaults for menu, methinks. +extern skill_t startskill; +extern int startepisode; +extern int startmap; + +// Savegame slot to load on startup. This is the value provided to +// the -loadgame option. If this has not been provided, this is -1. + +extern int startloadgame; + +extern boolean autostart; + +// Selected by user. +extern skill_t gameskill; +extern int gameepisode; +extern int gamemap; + +// If non-zero, exit the level after this number of minutes +extern int timelimit; + +// Nightmare mode flag, single player. +extern boolean respawnmonsters; + +// Netgame? Only true if >1 player. +extern boolean netgame; + +// 0=Cooperative; 1=Deathmatch; 2=Altdeath +extern int deathmatch; + +// ------------------------- +// Internal parameters for sound rendering. +// These have been taken from the DOS version, +// but are not (yet) supported with Linux +// (e.g. no sound volume adjustment with menu. + +// From m_menu.c: +// Sound FX volume has default, 0 - 15 +// Music volume has default, 0 - 15 +// These are multiplied by 8. +extern int sfxVolume; +extern int musicVolume; + +// Current music/sfx card - index useless +// w/o a reference LUT in a sound module. +// Ideally, this would use indices found +// in: /usr/include/linux/soundcard.h +extern int snd_MusicDevice; +extern int snd_SfxDevice; +// Config file? Same disclaimer as above. +extern int snd_DesiredMusicDevice; +extern int snd_DesiredSfxDevice; + +// ------------------------- +// Status flags for refresh. +// + +// Depending on view size - no status bar? +// Note that there is no way to disable the +// status bar explicitely. +extern boolean statusbaractive; + +extern boolean automapactive; // In AutoMap mode? +extern boolean menuactive; // Menu overlayed? +extern boolean paused; // Game Pause? + +extern boolean viewactive; + +extern boolean nodrawers; + +extern boolean testcontrols; +extern int testcontrols_mousespeed; + +// This one is related to the 3-screen display mode. +// ANG90 = left side, ANG270 = right +extern int viewangleoffset; + +// Player taking events, and displaying. +extern int consoleplayer; +extern int displayplayer; + +// ------------------------------------- +// Scores, rating. +// Statistics on a given map, for intermission. +// +extern int totalkills; +extern int totalitems; +extern int totalsecret; + +// Timer, for scores. +extern int levelstarttic; // gametic at level start +extern int leveltime; // tics in game play for par + +// -------------------------------------- +// DEMO playback/recording related stuff. +// No demo, there is a human player in charge? +// Disable save/end game? +extern boolean usergame; + +//? +extern boolean demoplayback; +extern boolean demorecording; + +// Round angleturn in ticcmds to the nearest 256. This is used when +// recording Vanilla demos in netgames. + +extern boolean lowres_turn; + +// Quit after playing a demo from cmdline. +extern boolean singledemo; + +//? +extern gamestate_t gamestate; + +//----------------------------- +// Internal parameters, fixed. +// These are set by the engine, and not changed +// according to user inputs. Partly load from +// WAD, partly set at startup time. + +// Bookkeeping on players - state. +extern player_t players[MAXPLAYERS]; + +// Alive? Disconnected? +extern boolean playeringame[MAXPLAYERS]; + +// Player spawn spots for deathmatch. +#define MAX_DM_STARTS 10 +extern mapthing_t deathmatchstarts[MAX_DM_STARTS]; +extern mapthing_t *deathmatch_p; + +// Player spawn spots. +extern mapthing_t playerstarts[MAXPLAYERS]; + +// Intermission stats. +// Parameters for world map / intermission. +extern wbstartstruct_t wminfo; + +//----------------------------------------- +// Internal parameters, used for engine. +// + +// File handling stuff. +extern char *savegamedir; +extern char basedefault[1024]; + +// if true, load all graphics at level load +extern boolean precache; + +// wipegamestate can be set to -1 +// to force a wipe on the next draw +extern gamestate_t wipegamestate; + +extern int mouseSensitivity; + +extern int bodyqueslot; + +// Needed to store the number of the dummy sky flat. +// Used for rendering, +// as well as tracking projectiles etc. +extern int skyflatnum; + +// Netgame stuff (buffers and pointers, i.e. indices). + +extern int rndindex; + +extern ticcmd_t *netcmds; + +#endif diff --git a/client/src/doomtype.h b/client/src/doomtype.h new file mode 100644 index 0000000..c902209 --- /dev/null +++ b/client/src/doomtype.h @@ -0,0 +1,90 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Simple basic typedefs, isolated here to make it easier +// separating modules. +// + + +#ifndef __DOOMTYPE__ +#define __DOOMTYPE__ + +#include + + + +// +// The packed attribute forces structures to be packed into the minimum +// space necessary. If this is not done, the compiler may align structure +// fields differently to optimize memory access, inflating the overall +// structure size. It is important to use the packed attribute on certain +// structures where alignment is important, particularly data read/written +// to disk. +// + +#ifdef __GNUC__ + +#define PACKEDATTR __attribute__((packed)) + +#else +#define PACKEDATTR +#endif + +#define PACKEDPREFIX + +#define PACKED_STRUCT(...) PACKEDPREFIX struct __VA_ARGS__ PACKEDATTR + +// C99 integer types; with gcc we just use this. Other compilers +// should add conditional statements that define the C99 types. + +// What is really wanted here is stdint.h; however, some old versions +// of Solaris don't have stdint.h and only have inttypes.h (the +// pre-standardisation version). inttypes.h is also in the C99 +// standard and defined to include stdint.h, so include this. + +#include + +#if defined(__cplusplus) || defined(__bool_true_false_are_defined) + +// Use builtin bool type with C++. + +typedef bool boolean; + +#else + +typedef enum +{ + false, + true +} boolean; + +#endif + +typedef uint8_t byte; +typedef uint8_t pixel_t; +typedef int16_t dpixel_t; + +#include + + +#define DIR_SEPARATOR '/' +#define DIR_SEPARATOR_S "/" +#define PATH_SEPARATOR ':' + + +#define arrlen(array) (sizeof(array) / sizeof(*array)) + +#endif + diff --git a/client/src/dstrings.c b/client/src/dstrings.c new file mode 100644 index 0000000..90541a4 --- /dev/null +++ b/client/src/dstrings.c @@ -0,0 +1,66 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Globally defined strings. +// + +#include "dstrings.h" + +char *doom1_endmsg[] = { + "are you sure you want to\nquit this great game?", + "please don't leave, there's more\ndemons to toast!", + "let's beat it -- this is turning\ninto a bloodbath!", + "i wouldn't leave if i were you.\ndos is much worse.", + "you're trying to say you like dos\nbetter than me, right?", + "don't leave yet -- there's a\ndemon around that corner!", + "ya know, next time you come in here\ni'm gonna toast ya.", + "go ahead and leave. see if i care.", +}; + +char *doom2_endmsg[] = { + // QuitDOOM II messages + "are you sure you want to\nquit this great game?", + "you want to quit?\nthen, thou hast lost an eighth!", + "don't go now, there's a \ndimensional shambler waiting\nat the dos " + "prompt!", + "get outta here and go back\nto your boring programs.", + "if i were your boss, i'd \n deathmatch ya in a minute!", + "look, bud. you leave now\nand you forfeit your body count!", + "just leave. when you come\nback, i'll be waiting with a bat.", + "you're lucky i don't smack\nyou for thinking about leaving.", +}; + +#if 0 + +// UNUSED messages included in the source release + +char* endmsg[] = +{ + // DOOM1 + QUITMSG, + // FinalDOOM? + "fuck you, pussy!\nget the fuck out!", + "you quit and i'll jizz\nin your cystholes!", + "if you leave, i'll make\nthe lord drink my jizz.", + "hey, ron! can we say\n'fuck' in the game?", + "i'd leave: this is just\nmore monsters and levels.\nwhat a load.", + "suck it down, asshole!\nyou're a fucking wimp!", + "don't quit now! we're \nstill spending your money!", + + // Internal debug. Different style, too. + "THIS IS NO MESSAGE!\nPage intentionally left blank." +}; + +#endif diff --git a/client/src/dstrings.h b/client/src/dstrings.h new file mode 100644 index 0000000..7d2e388 --- /dev/null +++ b/client/src/dstrings.h @@ -0,0 +1,33 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: +// DOOM strings, by language. +// + +#ifndef __DSTRINGS__ +#define __DSTRINGS__ + +// Misc. other strings. +#define SAVEGAMENAME "doomsav" + +// QuitDOOM messages +// 8 per each game type +#define NUM_QUITMESSAGES 8 + +extern char *doom1_endmsg[]; +extern char *doom2_endmsg[]; + +#endif diff --git a/client/src/f_wipe.c b/client/src/f_wipe.c new file mode 100644 index 0000000..d92cb0c --- /dev/null +++ b/client/src/f_wipe.c @@ -0,0 +1,221 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Mission begin melt/wipe screen special effect. +// + +#include + +#include "i_video.h" +#include "m_random.h" +#include "v_video.h" +#include "z_zone.h" + +#include "doomtype.h" + +#include "f_wipe.h" + +// +// SCREEN WIPE PACKAGE +// + +// when zero, stop the wipe +static boolean go = 0; + +static pixel_t *wipe_scr_start; +static pixel_t *wipe_scr_end; +static pixel_t *wipe_scr; + +void wipe_shittyColMajorXform(dpixel_t *array, int width, int height) { + int x; + int y; + dpixel_t *dest; + + dest = (dpixel_t *)Z_Malloc(width * height * sizeof(*dest), PU_STATIC, 0); + + for (y = 0; y < height; y++) + for (x = 0; x < width; x++) + dest[x * height + y] = array[y * width + x]; + + memcpy(array, dest, width * height * sizeof(*dest)); + + Z_Free(dest); +} + +int wipe_initColorXForm(int width, int height, int ticks) { + memcpy(wipe_scr, wipe_scr_start, width * height * sizeof(*wipe_scr)); + return 0; +} + +int wipe_doColorXForm(int width, int height, int ticks) { + boolean changed; + pixel_t *w; + pixel_t *e; + int newval; + + changed = false; + w = wipe_scr; + e = wipe_scr_end; + + while (w != wipe_scr + width * height) { + if (*w != *e) { + if (*w > *e) { + newval = *w - ticks; + if (newval < *e) + *w = *e; + else + *w = newval; + changed = true; + } else if (*w < *e) { + newval = *w + ticks; + if (newval > *e) + *w = *e; + else + *w = newval; + changed = true; + } + } + w++; + e++; + } + + return !changed; +} + +int wipe_exitColorXForm(int width, int height, int ticks) { return 0; } + +static int *y; + +int wipe_initMelt(int width, int height, int ticks) { + int i, r; + + // copy start screen to main screen + memcpy(wipe_scr, wipe_scr_start, width * height * sizeof(*wipe_scr)); + + // makes this wipe faster (in theory) + // to have stuff in column-major format + wipe_shittyColMajorXform((dpixel_t *)wipe_scr_start, width / 2, height); + wipe_shittyColMajorXform((dpixel_t *)wipe_scr_end, width / 2, height); + + // setup initial column positions + // (y<0 => not ready to scroll yet) + y = (int *)Z_Malloc(width * sizeof(int), PU_STATIC, 0); + y[0] = -(M_Random() % 16); + for (i = 1; i < width; i++) { + r = (M_Random() % 3) - 1; + y[i] = y[i - 1] + r; + if (y[i] > 0) + y[i] = 0; + else if (y[i] == -16) + y[i] = -15; + } + + return 0; +} + +int wipe_doMelt(int width, int height, int ticks) { + int i; + int j; + int dy; + int idx; + + dpixel_t *s; + dpixel_t *d; + boolean done = true; + + width /= 2; + + while (ticks--) { + for (i = 0; i < width; i++) { + if (y[i] < 0) { + y[i]++; + done = false; + } else if (y[i] < height) { + dy = (y[i] < 16) ? y[i] + 1 : 8; + if (y[i] + dy >= height) + dy = height - y[i]; + s = &((dpixel_t *)wipe_scr_end)[i * height + y[i]]; + d = &((dpixel_t *)wipe_scr)[y[i] * width + i]; + idx = 0; + for (j = dy; j; j--) { + d[idx] = *(s++); + idx += width; + } + y[i] += dy; + s = &((dpixel_t *)wipe_scr_start)[i * height]; + d = &((dpixel_t *)wipe_scr)[y[i] * width + i]; + idx = 0; + for (j = height - y[i]; j; j--) { + d[idx] = *(s++); + idx += width; + } + done = false; + } + } + } + + return done; +} + +int wipe_exitMelt(int width, int height, int ticks) { + Z_Free(y); + Z_Free(wipe_scr_start); + Z_Free(wipe_scr_end); + return 0; +} + +int wipe_StartScreen(int x, int y, int width, int height) { + wipe_scr_start = Z_Malloc( + SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_start), PU_STATIC, NULL); + I_ReadScreen(wipe_scr_start); + return 0; +} + +int wipe_EndScreen(int x, int y, int width, int height) { + wipe_scr_end = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_end), + PU_STATIC, NULL); + I_ReadScreen(wipe_scr_end); + V_DrawBlock(x, y, width, height, wipe_scr_start); // restore start scr. + return 0; +} + +int wipe_ScreenWipe(int wipeno, int x, int y, int width, int height, + int ticks) { + int rc; + static int (*wipes[])(int, int, int) = { + wipe_initColorXForm, wipe_doColorXForm, wipe_exitColorXForm, + wipe_initMelt, wipe_doMelt, wipe_exitMelt}; + + // initial stuff + if (!go) { + go = 1; + // wipe_scr = (pixel_t *) Z_Malloc(width*height, PU_STATIC, 0); // DEBUG + wipe_scr = I_VideoBuffer; + (*wipes[wipeno * 3])(width, height, ticks); + } + + // do a piece of wipe-in + V_MarkRect(0, 0, width, height); + rc = (*wipes[wipeno * 3 + 1])(width, height, ticks); + // V_DrawBlock(x, y, 0, width, height, wipe_scr); // DEBUG + + // final stuff + if (rc) { + go = 0; + (*wipes[wipeno * 3 + 2])(width, height, ticks); + } + + return !go; +} diff --git a/client/src/f_wipe.h b/client/src/f_wipe.h new file mode 100644 index 0000000..daaabfd --- /dev/null +++ b/client/src/f_wipe.h @@ -0,0 +1,42 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Mission start screen wipe/melt, special effects. +// + +#ifndef __F_WIPE_H__ +#define __F_WIPE_H__ + +// +// SCREEN WIPE PACKAGE +// + +enum { + // simple gradual pixel change for 8-bit only + wipe_ColorXForm, + + // weird screen melt + wipe_Melt, + + wipe_NUMWIPES +}; + +int wipe_StartScreen(int x, int y, int width, int height); + +int wipe_EndScreen(int x, int y, int width, int height); + +int wipe_ScreenWipe(int wipeno, int x, int y, int width, int height, int ticks); + +#endif diff --git a/client/src/g_game.c b/client/src/g_game.c new file mode 100644 index 0000000..8ab7a7e --- /dev/null +++ b/client/src/g_game.c @@ -0,0 +1,1997 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: none +// + +#include +#include +#include + +#include "am_map.h" +#include "d_englsh.h" +#include "d_loop.h" +#include "d_main.h" +#include "d_player.h" +#include "doomdata.h" +#include "doomdef.h" +#include "doomstat.h" +#include "g_game.h" +#include "hu_stuff.h" +#include "i_input.h" +#include "i_system.h" +#include "i_timer.h" +#include "info.h" +#include "m_argv.h" +#include "m_controls.h" +#include "m_fixed.h" +#include "m_menu.h" +#include "m_misc.h" +#include "m_random.h" +#include "net_defs.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_saveg.h" +#include "p_setup.h" +#include "p_tick.h" +// SKY handling - still the wrong place. +#include "r_data.h" +#include "r_defs.h" +#include "r_draw.h" +#include "r_main.h" +#include "r_sky.h" +#include "s_sound.h" +#include "sounds.h" +#include "st_stuff.h" +#include "statdump.h" +#include "tables.h" +// Needs access to LFB. +#include "v_video.h" +#include "w_wad.h" +#include "wi_stuff.h" +#include "z_zone.h" + +#include "ipu_host.h" +#include "ipu_transfer.h" + + +#define SAVEGAMESIZE 0x2c000 + +void G_ReadDemoTiccmd(ticcmd_t *cmd); +void G_WriteDemoTiccmd(ticcmd_t *cmd); +void G_PlayerReborn(int player); + +void G_DoReborn(int playernum); + +void G_DoLoadLevel(void); +void G_DoNewGame(void); +void G_DoPlayDemo(void); +void G_DoCompleted(void); +void G_DoVictory(void); +void G_DoWorldDone(void); +void G_DoSaveGame(void); + +// Gamestate the last time G_Ticker was called. + +gamestate_t oldgamestate; + +gameaction_t gameaction; +gamestate_t gamestate; +skill_t gameskill; +boolean respawnmonsters; +int gameepisode; +int gamemap; + +// If non-zero, exit the level after this number of minutes. + +int timelimit; + +boolean paused; +boolean sendpause; // send a pause event next tic +boolean sendsave; // send a save event next tic +boolean usergame; // ok to save / end game + +boolean timingdemo; // if true, exit with report on completion +boolean nodrawers; // for comparative timing purposes +int starttime; // for comparative timing purposes + +boolean viewactive; + +int deathmatch; // only if started as net death +boolean netgame; // only true if packets are broadcast +boolean playeringame[MAXPLAYERS]; +player_t players[MAXPLAYERS]; + +boolean turbodetected[MAXPLAYERS]; + +int consoleplayer; // player taking events and displaying +int displayplayer; // view being displayed +int levelstarttic; // gametic at level start +int totalkills, totalitems, totalsecret; // for intermission + +char *demoname; +boolean demorecording; +boolean longtics; // cph's doom 1.91 longtics hack +boolean lowres_turn; // low resolution turning for longtics +boolean demoplayback; +boolean netdemo; +byte *demobuffer; +byte *demo_p; +byte *demoend; +boolean singledemo; // quit after playing a demo from cmdline + +boolean precache = true; // if true, load all graphics at start + +boolean testcontrols = false; // Invoked by setup to test controls +int testcontrols_mousespeed; + +wbstartstruct_t wminfo; // parms for world map / intermission + +byte consistancy[MAXPLAYERS][BACKUPTICS]; + +#define MAXPLMOVE (forwardmove[1]) + +#define TURBOTHRESHOLD 0x32 + +fixed_t forwardmove[2] = {0x19, 0x32}; +fixed_t sidemove[2] = {0x18, 0x28}; +fixed_t angleturn[3] = {640, 1280, 320}; // + slow turn + +static int *weapon_keys[] = {&key_weapon1, &key_weapon2, &key_weapon3, + &key_weapon4, &key_weapon5, &key_weapon6, + &key_weapon7, &key_weapon8}; + +// Set to -1 or +1 to switch to the previous or next weapon. + +static int next_weapon = 0; + +// Used for prev/next weapon keys. + +static const struct { + weapontype_t weapon; + weapontype_t weapon_num; +} weapon_order_table[] = {{wp_fist, wp_fist}, + {wp_chainsaw, wp_fist}, + {wp_pistol, wp_pistol}, + {wp_shotgun, wp_shotgun}, + {wp_supershotgun, wp_shotgun}, + {wp_chaingun, wp_chaingun}, + {wp_missile, wp_missile}, + {wp_plasma, wp_plasma}, + {wp_bfg, wp_bfg}}; + +#define SLOWTURNTICS 6 + +#define NUMKEYS 256 +#define MAX_JOY_BUTTONS 20 + +static boolean gamekeydown[NUMKEYS]; +static int turnheld; // for accelerative turning + +static boolean mousearray[MAX_MOUSE_BUTTONS + 1]; +static boolean *mousebuttons = &mousearray[1]; // allow [-1] + +// mouse values are used once +int mousex; +int mousey; + +static int dclicktime; +static boolean dclickstate; +static int dclicks; +static int dclicktime2; +static boolean dclickstate2; +static int dclicks2; + +// joystick values are repeated +static int joyxmove; +static int joyymove; +static int joystrafemove; +static boolean joyarray[MAX_JOY_BUTTONS + 1]; +static boolean *joybuttons = &joyarray[1]; // allow [-1] + +static int savegameslot; +static char savedescription[32]; + +#define BODYQUESIZE 32 + +mobj_t *bodyque[BODYQUESIZE]; +int bodyqueslot; + +int vanilla_savegame_limit = 1; +int vanilla_demo_limit = 1; + +int G_CmdChecksum(ticcmd_t *cmd) { + size_t i; + int sum = 0; + + for (i = 0; i < sizeof(*cmd) / 4 - 1; i++) + sum += ((int *)cmd)[i]; + + return sum; +} + +static boolean WeaponSelectable(weapontype_t weapon) { + // Can't select the super shotgun in Doom 1. + + if (weapon == wp_supershotgun && logical_gamemission == doom) { + return false; + } + + // These weapons aren't available in shareware. + + if ((weapon == wp_plasma || weapon == wp_bfg) && gamemission == doom && + gamemode == shareware) { + return false; + } + + // Can't select a weapon if we don't own it. + + if (!players[consoleplayer].weaponowned[weapon]) { + return false; + } + + // Can't select the fist if we have the chainsaw, unless + // we also have the berserk pack. + + if (weapon == wp_fist && players[consoleplayer].weaponowned[wp_chainsaw] && + !players[consoleplayer].powers[pw_strength]) { + return false; + } + + return true; +} + +static int G_NextWeapon(int direction) { + weapontype_t weapon; + int start_i, i; + + // Find index in the table. + + if (players[consoleplayer].pendingweapon == wp_nochange) { + weapon = players[consoleplayer].readyweapon; + } else { + weapon = players[consoleplayer].pendingweapon; + } + + for (i = 0; i < arrlen(weapon_order_table); ++i) { + if (weapon_order_table[i].weapon == weapon) { + break; + } + } + + // Switch weapon. Don't loop forever. + start_i = i; + do { + i += direction; + i = (i + arrlen(weapon_order_table)) % arrlen(weapon_order_table); + } while (i != start_i && !WeaponSelectable(weapon_order_table[i].weapon)); + + return weapon_order_table[i].weapon_num; +} + +// +// G_BuildTiccmd +// Builds a ticcmd from all of the available inputs +// or reads it from the demo buffer. +// If recording a demo, write it out +// +void G_BuildTiccmd(ticcmd_t *cmd, int maketic) { + int i; + boolean strafe; + boolean bstrafe; + int speed; + int tspeed; + int forward; + int side; + + memset(cmd, 0, sizeof(ticcmd_t)); + + cmd->consistancy = consistancy[consoleplayer][maketic % BACKUPTICS]; + + strafe = gamekeydown[key_strafe] || mousebuttons[mousebstrafe] || + joybuttons[joybstrafe]; + + // fraggle: support the old "joyb_speed = 31" hack which + // allowed an autorun effect + + speed = key_speed >= NUMKEYS || joybspeed >= MAX_JOY_BUTTONS || + gamekeydown[key_speed] || joybuttons[joybspeed]; + + forward = side = 0; + + // use two stage accelerative turning + // on the keyboard and joystick + if (joyxmove < 0 || joyxmove > 0 || gamekeydown[key_right] || + gamekeydown[key_left]) + turnheld += ticdup; + else + turnheld = 0; + + if (turnheld < SLOWTURNTICS) + tspeed = 2; // slow turn + else + tspeed = speed; + + // let movement keys cancel each other out + if (strafe) { + if (gamekeydown[key_right]) { + // fprintf(stderr, "strafe right\n"); + side += sidemove[speed]; + } + if (gamekeydown[key_left]) { + // fprintf(stderr, "strafe left\n"); + side -= sidemove[speed]; + } + if (joyxmove > 0) + side += sidemove[speed]; + if (joyxmove < 0) + side -= sidemove[speed]; + + } else { + if (gamekeydown[key_right]) + cmd->angleturn -= angleturn[tspeed]; + if (gamekeydown[key_left]) + cmd->angleturn += angleturn[tspeed]; + if (joyxmove > 0) + cmd->angleturn -= angleturn[tspeed]; + if (joyxmove < 0) + cmd->angleturn += angleturn[tspeed]; + } + + if (gamekeydown[key_up]) { + // fprintf(stderr, "up\n"); + forward += forwardmove[speed]; + } + if (gamekeydown[key_down]) { + // fprintf(stderr, "down\n"); + forward -= forwardmove[speed]; + } + + if (joyymove < 0) + forward += forwardmove[speed]; + if (joyymove > 0) + forward -= forwardmove[speed]; + + if (gamekeydown[key_strafeleft] || joybuttons[joybstrafeleft] || + mousebuttons[mousebstrafeleft] || joystrafemove < 0) { + side -= sidemove[speed]; + } + + if (gamekeydown[key_straferight] || joybuttons[joybstraferight] || + mousebuttons[mousebstraferight] || joystrafemove > 0) { + side += sidemove[speed]; + } + + // buttons + cmd->chatchar = HU_dequeueChatChar(); + + if (gamekeydown[key_fire] || mousebuttons[mousebfire] || joybuttons[joybfire]) + cmd->buttons |= BT_ATTACK; + + if (gamekeydown[key_use] || joybuttons[joybuse] || mousebuttons[mousebuse]) { + cmd->buttons |= BT_USE; + // clear double clicks if hit use button + dclicks = 0; + } + + // If the previous or next weapon button is pressed, the + // next_weapon variable is set to change weapons when + // we generate a ticcmd. Choose a new weapon. + + if (gamestate == GS_LEVEL && next_weapon != 0) { + i = G_NextWeapon(next_weapon); + cmd->buttons |= BT_CHANGE; + cmd->buttons |= i << BT_WEAPONSHIFT; + } else { + // Check weapon keys. + + for (i = 0; i < arrlen(weapon_keys); ++i) { + int key = *weapon_keys[i]; + + if (gamekeydown[key]) { + cmd->buttons |= BT_CHANGE; + cmd->buttons |= i << BT_WEAPONSHIFT; + break; + } + } + } + + next_weapon = 0; + + // mouse + if (mousebuttons[mousebforward]) { + forward += forwardmove[speed]; + } + if (mousebuttons[mousebbackward]) { + forward -= forwardmove[speed]; + } + + if (dclick_use) { + // forward double click + if (mousebuttons[mousebforward] != dclickstate && dclicktime > 1) { + dclickstate = mousebuttons[mousebforward]; + if (dclickstate) + dclicks++; + if (dclicks == 2) { + cmd->buttons |= BT_USE; + dclicks = 0; + } else + dclicktime = 0; + } else { + dclicktime += ticdup; + if (dclicktime > 20) { + dclicks = 0; + dclickstate = 0; + } + } + + // strafe double click + bstrafe = mousebuttons[mousebstrafe] || joybuttons[joybstrafe]; + if (bstrafe != dclickstate2 && dclicktime2 > 1) { + dclickstate2 = bstrafe; + if (dclickstate2) + dclicks2++; + if (dclicks2 == 2) { + cmd->buttons |= BT_USE; + dclicks2 = 0; + } else + dclicktime2 = 0; + } else { + dclicktime2 += ticdup; + if (dclicktime2 > 20) { + dclicks2 = 0; + dclickstate2 = 0; + } + } + } + + forward += mousey; + + if (strafe) + side += mousex * 2; + else + cmd->angleturn -= mousex * 0x8; + + if (mousex == 0) { + // No movement in the previous frame + + testcontrols_mousespeed = 0; + } + + mousex = mousey = 0; + + if (forward > MAXPLMOVE) + forward = MAXPLMOVE; + else if (forward < -MAXPLMOVE) + forward = -MAXPLMOVE; + if (side > MAXPLMOVE) + side = MAXPLMOVE; + else if (side < -MAXPLMOVE) + side = -MAXPLMOVE; + + cmd->forwardmove += forward; + cmd->sidemove += side; + + // special buttons + if (sendpause) { + sendpause = false; + cmd->buttons = BT_SPECIAL | BTS_PAUSE; + } + + if (sendsave) { + sendsave = false; + cmd->buttons = BT_SPECIAL | BTS_SAVEGAME | (savegameslot << BTS_SAVESHIFT); + } + + // low-res turning + + if (lowres_turn) { + static signed short carry = 0; + signed short desired_angleturn; + + desired_angleturn = cmd->angleturn + carry; + + // round angleturn to the nearest 256 unit boundary + // for recording demos with single byte values for turn + + cmd->angleturn = (desired_angleturn + 128) & 0xff00; + + // Carry forward the error from the reduced resolution to the + // next tic, so that successive small movements can accumulate. + + carry = desired_angleturn - cmd->angleturn; + } +} + +// +// G_DoLoadLevel +// +void G_DoLoadLevel(void) { + int i; + + // Set the sky map. + // First thing, we have a dummy sky texture name, + // a flat. The data is in the WAD only because + // we look for an actual index, instead of simply + // setting one. + + skyflatnum = R_FlatNumForName((SKYFLATNAME)); + + // The "Sky never changes in Doom II" bug was fixed in + // the id Anthology version of doom2.exe for Final Doom. + if ((gamemode == commercial) && + (gameversion == exe_final2 || gameversion == exe_chex)) { + char *skytexturename; + + if (gamemap < 12) { + skytexturename = "SKY1"; + } else if (gamemap < 21) { + skytexturename = "SKY2"; + } else { + skytexturename = "SKY3"; + } + + skytexture = R_TextureNumForName(skytexturename); + } + + levelstarttic = gametic; // for time calculation + + if (wipegamestate == GS_LEVEL) + wipegamestate = -1; // force a wipe + + gamestate = GS_LEVEL; + + for (i = 0; i < MAXPLAYERS; i++) { + turbodetected[i] = false; + if (playeringame[i] && players[i].playerstate == PST_DEAD) + players[i].playerstate = PST_REBORN; + memset(players[i].frags, 0, sizeof(players[i].frags)); + } + + P_SetupLevel(gameepisode, gamemap, 0, gameskill); + displayplayer = consoleplayer; // view the guy you are playing + gameaction = ga_nothing; + Z_CheckHeap(); + + // clear cmd building stuff + + memset(gamekeydown, 0, sizeof(gamekeydown)); + joyxmove = joyymove = joystrafemove = 0; + mousex = mousey = 0; + sendpause = sendsave = paused = false; + memset(mousearray, 0, sizeof(mousearray)); + memset(joyarray, 0, sizeof(joyarray)); + + if (testcontrols) { + players[consoleplayer].message = "Press escape to quit."; + } + + IPU_G_DoLoadLevel(); +} + +static void SetJoyButtons(unsigned int buttons_mask) { + int i; + + for (i = 0; i < MAX_JOY_BUTTONS; ++i) { + int button_on = (buttons_mask & (1 << i)) != 0; + + // Detect button press: + + if (!joybuttons[i] && button_on) { + // Weapon cycling: + + if (i == joybprevweapon) { + next_weapon = -1; + } else if (i == joybnextweapon) { + next_weapon = 1; + } + } + + joybuttons[i] = button_on; + } +} + +static void SetMouseButtons(unsigned int buttons_mask) { + int i; + + for (i = 0; i < MAX_MOUSE_BUTTONS; ++i) { + unsigned int button_on = (buttons_mask & (1 << i)) != 0; + + // Detect button press: + + if (!mousebuttons[i] && button_on) { + if (i == mousebprevweapon) { + next_weapon = -1; + } else if (i == mousebnextweapon) { + next_weapon = 1; + } + } + + mousebuttons[i] = button_on; + } +} + +// +// G_Responder +// Get info needed to make ticcmd_ts for the players. +// +boolean G_Responder(event_t *ev) { + // allow spy mode changes even during the demo + if (gamestate == GS_LEVEL && ev->type == ev_keydown && ev->data1 == key_spy && + (singledemo || !deathmatch)) { + // spy mode + do { + displayplayer++; + if (displayplayer == MAXPLAYERS) + displayplayer = 0; + } while (!playeringame[displayplayer] && displayplayer != consoleplayer); + return true; + } + + // any other key pops up menu if in demos + if (gameaction == ga_nothing && !singledemo && + (demoplayback || gamestate == GS_DEMOSCREEN)) { + if (ev->type == ev_keydown || (ev->type == ev_mouse && ev->data1) || + (ev->type == ev_joystick && ev->data1)) { + M_StartControlPanel(); + return true; + } + return false; + } + + if (gamestate == GS_LEVEL) { +#if 0 + if (devparm && ev->type == ev_keydown && ev->data1 == ';') + { + G_DeathMatchSpawnPlayer (0); + return true; + } +#endif + if (HU_Responder(ev)) + return true; // chat ate the event + if (ST_Responder(ev)) + return true; // status window ate it + if (AM_Responder(ev)) + return true; // automap ate it + } + + if (testcontrols && ev->type == ev_mouse) { + // If we are invoked by setup to test the controls, save the + // mouse speed so that we can display it on-screen. + // Perform a low pass filter on this so that the thermometer + // appears to move smoothly. + + testcontrols_mousespeed = abs(ev->data2); + } + + // If the next/previous weapon keys are pressed, set the next_weapon + // variable to change weapons when the next ticcmd is generated. + + if (ev->type == ev_keydown && ev->data1 == key_prevweapon) { + next_weapon = -1; + } else if (ev->type == ev_keydown && ev->data1 == key_nextweapon) { + next_weapon = 1; + } + + switch (ev->type) { + case ev_keydown: + if (ev->data1 == key_pause) { + sendpause = true; + } else if (ev->data1 < NUMKEYS) { + gamekeydown[ev->data1] = true; + } + + return true; // eat key down events + + case ev_keyup: + if (ev->data1 < NUMKEYS) + gamekeydown[ev->data1] = false; + return false; // always let key up events filter down + + case ev_mouse: + SetMouseButtons(ev->data1); + mousex = ev->data2 * (mouseSensitivity + 5) / 10; + mousey = ev->data3 * (mouseSensitivity + 5) / 10; + return true; // eat events + + case ev_joystick: + SetJoyButtons(ev->data1); + joyxmove = ev->data2; + joyymove = ev->data3; + joystrafemove = ev->data4; + return true; // eat events + + default: + break; + } + + return false; +} + +// +// G_Ticker +// Make ticcmd_ts for the players. +// +void G_Ticker(void) { + int i; + int buf; + ticcmd_t *cmd; + + // do player reborns if needed + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i] && players[i].playerstate == PST_REBORN) + G_DoReborn(i); + + // do things to change the game state + while (gameaction != ga_nothing) { + switch (gameaction) { + case ga_loadlevel: + G_DoLoadLevel(); + break; + case ga_newgame: + G_DoNewGame(); + break; + case ga_loadgame: + G_DoLoadGame(); + break; + case ga_savegame: + G_DoSaveGame(); + break; + case ga_playdemo: + G_DoPlayDemo(); + break; + case ga_completed: + G_DoCompleted(); + break; + case ga_victory: + break; + case ga_worlddone: + G_DoWorldDone(); + break; + case ga_screenshot: + V_ScreenShot("DOOM%02i.%s"); + players[consoleplayer].message = ("screen shot"); + gameaction = ga_nothing; + break; + case ga_nothing: + break; + } + } + + // get commands, check consistancy, + // and build new consistancy check + buf = (gametic / ticdup) % BACKUPTICS; + + for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + cmd = &players[i].cmd; + + memcpy(cmd, &netcmds[i], sizeof(ticcmd_t)); + + if (demoplayback) + G_ReadDemoTiccmd(cmd); + if (demorecording) + G_WriteDemoTiccmd(cmd); + + // check for turbo cheats + + // check ~ 4 seconds whether to display the turbo message. + // store if the turbo threshold was exceeded in any tics + // over the past 4 seconds. offset the checking period + // for each player so messages are not displayed at the + // same time. + + if (cmd->forwardmove > TURBOTHRESHOLD) { + turbodetected[i] = true; + } + + if ((gametic & 31) == 0 && ((gametic >> 5) % MAXPLAYERS) == i && + turbodetected[i]) { + static char turbomessage[80]; + extern char *player_names[4]; + M_snprintf(turbomessage, sizeof(turbomessage), "%s is turbo!", + player_names[i]); + players[consoleplayer].message = turbomessage; + turbodetected[i] = false; + } + + if (netgame && !netdemo && !(gametic % ticdup)) { + if (gametic > BACKUPTICS && consistancy[i][buf] != cmd->consistancy) { + I_Error("consistency failure (%i should be %i)", cmd->consistancy, + consistancy[i][buf]); + } + if (players[i].mo) + consistancy[i][buf] = players[i].mo->x; + else + consistancy[i][buf] = rndindex; + } + } + } + + // check for special buttons + for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + if (players[i].cmd.buttons & BT_SPECIAL) { + switch (players[i].cmd.buttons & BT_SPECIALMASK) { + case BTS_PAUSE: + paused ^= 1; + if (paused) + S_PauseSound(); + else + S_ResumeSound(); + break; + + case BTS_SAVEGAME: + if (!savedescription[0]) { + M_StringCopy(savedescription, "NET GAME", sizeof(savedescription)); + } + + savegameslot = + (players[i].cmd.buttons & BTS_SAVEMASK) >> BTS_SAVESHIFT; + gameaction = ga_savegame; + break; + } + } + } + } + + // Have we just finished displaying an intermission screen? + + if (oldgamestate == GS_INTERMISSION && gamestate != GS_INTERMISSION) { + WI_End(); + } + + oldgamestate = gamestate; + + // do main actions + switch (gamestate) { + case GS_LEVEL: + P_Ticker(); + ST_Ticker(); + AM_Ticker(); + HU_Ticker(); + break; + + case GS_INTERMISSION: + WI_Ticker(); + break; + + case GS_DEMOSCREEN: + D_PageTicker(); + break; + } +} + +// +// PLAYER STRUCTURE FUNCTIONS +// also see P_SpawnPlayer in P_Things +// + +// +// G_InitPlayer +// Called at the start. +// Called by the game initialization functions. +// +void G_InitPlayer(int player) { + // clear everything else to defaults + G_PlayerReborn(player); +} + +// +// G_PlayerFinishLevel +// Can when a player completes a level. +// +void G_PlayerFinishLevel(int player) { + player_t *p; + + p = &players[player]; + + memset(p->powers, 0, sizeof(p->powers)); + memset(p->cards, 0, sizeof(p->cards)); + p->mo->flags &= ~MF_SHADOW; // cancel invisibility + p->extralight = 0; // cancel gun flashes + p->fixedcolormap = 0; // cancel ir gogles + p->damagecount = 0; // no palette changes + p->bonuscount = 0; +} + +// +// G_PlayerReborn +// Called after a player dies +// almost everything is cleared and initialized +// +void G_PlayerReborn(int player) { + player_t *p; + int i; + int frags[MAXPLAYERS]; + int killcount; + int itemcount; + int secretcount; + + memcpy(frags, players[player].frags, sizeof(frags)); + killcount = players[player].killcount; + itemcount = players[player].itemcount; + secretcount = players[player].secretcount; + + p = &players[player]; + memset(p, 0, sizeof(*p)); + + memcpy(players[player].frags, frags, sizeof(players[player].frags)); + players[player].killcount = killcount; + players[player].itemcount = itemcount; + players[player].secretcount = secretcount; + + p->usedown = p->attackdown = true; // don't do anything immediately + p->playerstate = PST_LIVE; + p->health = 100; + // TODO: replace 100 above with player health constant + p->readyweapon = p->pendingweapon = wp_pistol; + p->weaponowned[wp_fist] = true; + p->weaponowned[wp_pistol] = true; + p->ammo[am_clip] = 50; + + for (i = 0; i < NUMAMMO; i++) + p->maxammo[i] = maxammo[i]; +} + +// +// G_CheckSpot +// Returns false if the player cannot be respawned +// at the given mapthing_t spot +// because something is occupying it +// +void P_SpawnPlayer(mapthing_t *mthing); + +boolean G_CheckSpot(int playernum, mapthing_t *mthing) { + fixed_t x; + fixed_t y; + subsector_t *ss; + mobj_t *mo; + int i; + + if (!players[playernum].mo) { + // first spawn of level, before corpses + for (i = 0; i < playernum; i++) + if (players[i].mo->x == mthing->x << FRACBITS && + players[i].mo->y == mthing->y << FRACBITS) + return false; + return true; + } + + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + + if (!P_CheckPosition(players[playernum].mo, x, y)) + return false; + + // flush an old corpse if needed + if (bodyqueslot >= BODYQUESIZE) + P_RemoveMobj(bodyque[bodyqueslot % BODYQUESIZE]); + bodyque[bodyqueslot % BODYQUESIZE] = players[playernum].mo; + bodyqueslot++; + + // spawn a teleport fog + ss = R_PointInSubsector(x, y); + + // The code in the released source looks like this: + // + // an = ( ANG45 * (((unsigned int) mthing->angle)/45) ) + // >> ANGLETOFINESHIFT; + // mo = P_SpawnMobj (x+20*finecosine[an], y+20*finesine[an] + // , ss->sector->floorheight + // , MT_TFOG); + // + // But 'an' can be a signed value in the DOS version. This means that + // we get a negative index and the lookups into finecosine/finesine + // end up dereferencing values in finetangent[]. + // A player spawning on a deathmatch start facing directly west spawns + // "silently" with no spawn fog. Emulate this. + // + // This code is imported from PrBoom+. + + { + fixed_t xa, ya; + signed int an; + + // This calculation overflows in Vanilla Doom, but here we deliberately + // avoid integer overflow as it is undefined behavior, so the value of + // 'an' will always be positive. + an = (ANG45 >> ANGLETOFINESHIFT) * ((signed int)mthing->angle / 45); + + switch (an) { + case 4096: // -4096: + xa = finetangent[2048]; // finecosine[-4096] + ya = finetangent[0]; // finesine[-4096] + break; + case 5120: // -3072: + xa = finetangent[3072]; // finecosine[-3072] + ya = finetangent[1024]; // finesine[-3072] + break; + case 6144: // -2048: + xa = finesine[0]; // finecosine[-2048] + ya = finetangent[2048]; // finesine[-2048] + break; + case 7168: // -1024: + xa = finesine[1024]; // finecosine[-1024] + ya = finetangent[3072]; // finesine[-1024] + break; + case 0: + case 1024: + case 2048: + case 3072: + xa = finecosine[an]; + ya = finesine[an]; + break; + default: + I_Error("G_CheckSpot: unexpected angle %d\n", an); + xa = ya = 0; + break; + } + mo = + P_SpawnMobj(x + 20 * xa, y + 20 * ya, ss->sector->floorheight, MT_TFOG); + } + + if (players[consoleplayer].viewz != 1) + S_StartSound(mo, sfx_telept); // don't start sound on first frame + + return true; +} + +// +// G_DeathMatchSpawnPlayer +// Spawns a player at one of the random death match spots +// called at level load and each death +// +void G_DeathMatchSpawnPlayer(int playernum) { + int i, j; + int selections; + + selections = deathmatch_p - deathmatchstarts; + if (selections < 4) + I_Error("Only %i deathmatch spots, 4 required", selections); + + for (j = 0; j < 20; j++) { + i = P_Random() % selections; + if (G_CheckSpot(playernum, &deathmatchstarts[i])) { + deathmatchstarts[i].type = playernum + 1; + P_SpawnPlayer(&deathmatchstarts[i]); + return; + } + } + + // no good spot, so the player will probably get stuck + P_SpawnPlayer(&playerstarts[playernum]); +} + +// +// G_DoReborn +// +void G_DoReborn(int playernum) { + int i; + + if (!netgame) { + // reload the level from scratch + gameaction = ga_loadlevel; + } else { + // respawn at the start + + // first dissasociate the corpse + players[playernum].mo->player = NULL; + + // spawn at random spot if in death match + if (deathmatch) { + G_DeathMatchSpawnPlayer(playernum); + return; + } + + if (G_CheckSpot(playernum, &playerstarts[playernum])) { + P_SpawnPlayer(&playerstarts[playernum]); + return; + } + + // try to spawn at one of the other players spots + for (i = 0; i < MAXPLAYERS; i++) { + if (G_CheckSpot(playernum, &playerstarts[i])) { + playerstarts[i].type = playernum + 1; // fake as other player + P_SpawnPlayer(&playerstarts[i]); + playerstarts[i].type = i + 1; // restore + return; + } + // he's going to be inside something. Too bad. + } + P_SpawnPlayer(&playerstarts[playernum]); + } +} + +void G_ScreenShot(void) { gameaction = ga_screenshot; } + +// DOOM Par Times +int pars[4][10] = {{0}, + {0, 30, 75, 120, 90, 165, 180, 180, 30, 165}, + {0, 90, 90, 90, 120, 90, 360, 240, 30, 170}, + {0, 90, 45, 90, 150, 90, 90, 165, 30, 135}}; + +// DOOM II Par Times +int cpars[32] = { + 30, 90, 120, 120, 90, 150, 120, 120, 270, 90, // 1-10 + 210, 150, 150, 150, 210, 150, 420, 150, 210, 150, // 11-20 + 240, 150, 180, 150, 150, 300, 330, 420, 300, 180, // 21-30 + 120, 30 // 31-32 +}; + +// +// G_DoCompleted +// +boolean secretexit; +extern char *pagename; + +void G_ExitLevel(void) { + secretexit = false; + gameaction = ga_completed; +} + +// Here's for the german edition. +void G_SecretExitLevel(void) { + // IF NO WOLF3D LEVELS, NO SECRET EXIT! + if ((gamemode == commercial) && (W_CheckNumForName("map31") < 0)) + secretexit = false; + else + secretexit = true; + gameaction = ga_completed; +} + +void G_DoCompleted(void) { + int i; + + gameaction = ga_nothing; + + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) + G_PlayerFinishLevel(i); // take away cards and stuff + + if (automapactive) + AM_Stop(); + + if (gamemode != commercial) { + // Chex Quest ends after 5 levels, rather than 8. + + if (gameversion == exe_chex) { + if (gamemap == 5) { + gameaction = ga_victory; + return; + } + } else { + switch (gamemap) { + case 8: + gameaction = ga_victory; + return; + case 9: + for (i = 0; i < MAXPLAYERS; i++) + players[i].didsecret = true; + break; + } + } + } + + //#if 0 Hmmm - why? + if ((gamemap == 8) && (gamemode != commercial)) { + // victory + gameaction = ga_victory; + return; + } + + if ((gamemap == 9) && (gamemode != commercial)) { + // exit secret level + for (i = 0; i < MAXPLAYERS; i++) + players[i].didsecret = true; + } + //#endif + + wminfo.didsecret = players[consoleplayer].didsecret; + wminfo.epsd = gameepisode - 1; + wminfo.last = gamemap - 1; + + // wminfo.next is 0 biased, unlike gamemap + if (gamemode == commercial) { + if (secretexit) + switch (gamemap) { + case 15: + wminfo.next = 30; + break; + case 31: + wminfo.next = 31; + break; + } + else + switch (gamemap) { + case 31: + case 32: + wminfo.next = 15; + break; + default: + wminfo.next = gamemap; + } + } else { + if (secretexit) + wminfo.next = 8; // go to secret level + else if (gamemap == 9) { + // returning from secret level + switch (gameepisode) { + case 1: + wminfo.next = 3; + break; + case 2: + wminfo.next = 5; + break; + case 3: + wminfo.next = 6; + break; + case 4: + wminfo.next = 2; + break; + } + } else + wminfo.next = gamemap; // go to next level + } + + wminfo.maxkills = totalkills; + wminfo.maxitems = totalitems; + wminfo.maxsecret = totalsecret; + wminfo.maxfrags = 0; + + // Set par time. Doom episode 4 doesn't have a par time, so this + // overflows into the cpars array. It's necessary to emulate this + // for statcheck regression testing. + if (gamemode == commercial) + wminfo.partime = TICRATE * cpars[gamemap - 1]; + else if (gameepisode < 4) + wminfo.partime = TICRATE * pars[gameepisode][gamemap]; + else + wminfo.partime = TICRATE * cpars[gamemap]; + + wminfo.pnum = consoleplayer; + + for (i = 0; i < MAXPLAYERS; i++) { + wminfo.plyr[i].in = playeringame[i]; + wminfo.plyr[i].skills = players[i].killcount; + wminfo.plyr[i].sitems = players[i].itemcount; + wminfo.plyr[i].ssecret = players[i].secretcount; + wminfo.plyr[i].stime = leveltime; + memcpy(wminfo.plyr[i].frags, players[i].frags, + sizeof(wminfo.plyr[i].frags)); + } + + gamestate = GS_INTERMISSION; + viewactive = false; + automapactive = false; + + StatCopy(&wminfo); + + WI_Start(&wminfo); +} + +// +// G_WorldDone +// +void G_WorldDone(void) { + gameaction = ga_worlddone; + + if (secretexit) + players[consoleplayer].didsecret = true; + + if (gamemode == commercial) { + switch (gamemap) { + case 15: + case 31: + if (!secretexit) + break; + case 6: + case 11: + case 20: + case 30: + break; + } + } +} + +void G_DoWorldDone(void) { + gamestate = GS_LEVEL; + gamemap = wminfo.next + 1; + G_DoLoadLevel(); + gameaction = ga_nothing; + viewactive = true; +} + +// +// G_InitFromSavegame +// Can be called by the startup code or the menu task. +// +extern boolean setsizeneeded; +void R_ExecuteSetViewSize(void); + +char savename[256]; + +void G_LoadGame(char *name) { + M_StringCopy(savename, name, sizeof(savename)); + gameaction = ga_loadgame; +} + +void G_DoLoadGame(void) { + int savedleveltime; + + gameaction = ga_nothing; + + save_stream = fopen(savename, "rb"); + + if (save_stream == NULL) { + return; + } + + savegame_error = false; + + if (!P_ReadSaveGameHeader()) { + fclose(save_stream); + return; + } + + savedleveltime = leveltime; + + // load a base level + G_InitNew(gameskill, gameepisode, gamemap); + + leveltime = savedleveltime; + + // dearchive all the modifications + P_UnArchivePlayers(); + P_UnArchiveWorld(); + P_UnArchiveThinkers(); + P_UnArchiveSpecials(); + + IPU_CheckAlreadyMappedLines(); + + if (!P_ReadSaveGameEOF()) + I_Error("Bad savegame"); + + fclose(save_stream); + + if (setsizeneeded) + R_ExecuteSetViewSize(); + + // draw the pattern into the back screen + R_FillBackScreen(); +} + +// +// G_SaveGame +// Called by the menu task. +// Description is a 24 byte text string +// +void G_SaveGame(int slot, char *description) { + savegameslot = slot; + M_StringCopy(savedescription, description, sizeof(savedescription)); + sendsave = true; +} + +void G_DoSaveGame(void) { + char *savegame_file; + char *temp_savegame_file; + char *recovery_savegame_file; + + recovery_savegame_file = NULL; + temp_savegame_file = P_TempSaveGameFile(); + savegame_file = P_SaveGameFile(savegameslot); + + // Open the savegame file for writing. We write to a temporary file + // and then rename it at the end if it was successfully written. + // This prevents an existing savegame from being overwritten by + // a corrupted one, or if a savegame buffer overrun occurs. + save_stream = fopen(temp_savegame_file, "wb"); + + if (save_stream == NULL) { + // Failed to save the game, so we're going to have to abort. But + // to be nice, save to somewhere else before we call I_Error(). + recovery_savegame_file = M_TempFile("recovery.dsg"); + save_stream = fopen(recovery_savegame_file, "wb"); + if (save_stream == NULL) { + I_Error("Failed to open either '%s' or '%s' to write savegame.", + temp_savegame_file, recovery_savegame_file); + } + } + + savegame_error = false; + + P_WriteSaveGameHeader(savedescription); + + P_ArchivePlayers(); + P_ArchiveWorld(); + P_ArchiveThinkers(); + P_ArchiveSpecials(); + + P_WriteSaveGameEOF(); + + // Enforce the same savegame size limit as in Vanilla Doom, + // except if the vanilla_savegame_limit setting is turned off. + + if (vanilla_savegame_limit && ftell(save_stream) > SAVEGAMESIZE) { + I_Error("Savegame buffer overrun"); + } + + // Finish up, close the savegame file. + + fclose(save_stream); + + if (recovery_savegame_file != NULL) { + // We failed to save to the normal location, but we wrote a + // recovery file to the temp directory. Now we can bomb out + // with an error. + I_Error("Failed to open savegame file '%s' for writing.\n" + "But your game has been saved to '%s' for recovery.", + temp_savegame_file, recovery_savegame_file); + } + + // Now rename the temporary savegame file to the actual savegame + // file, overwriting the old savegame if there was one there. + + remove(savegame_file); + rename(temp_savegame_file, savegame_file); + + gameaction = ga_nothing; + M_StringCopy(savedescription, "", sizeof(savedescription)); + + players[consoleplayer].message = (GGSAVED); + + // draw the pattern into the back screen + R_FillBackScreen(); +} + +// +// G_InitNew +// Can be called by the startup code or the menu task, +// consoleplayer, displayplayer, playeringame[] should be set. +// +skill_t d_skill; +int d_episode; +int d_map; + +void G_DeferedInitNew(skill_t skill, int episode, int map) { + d_skill = skill; + d_episode = episode; + d_map = map; + gameaction = ga_newgame; +} + +void G_DoNewGame(void) { + demoplayback = false; + netdemo = false; + netgame = false; + deathmatch = false; + playeringame[1] = playeringame[2] = playeringame[3] = 0; + respawnparm = false; + fastparm = false; + nomonsters = false; + consoleplayer = 0; + G_InitNew(d_skill, d_episode, d_map); + gameaction = ga_nothing; +} + +void G_InitNew(skill_t skill, int episode, int map) { + char *skytexturename; + int i; + + if (paused) { + paused = false; + S_ResumeSound(); + } + + /* + // Note: This commented-out block of code was added at some point + // between the DOS version(s) and the Doom source release. It isn't + // found in disassemblies of the DOS version and causes IDCLEV and + // the -warp command line parameter to behave differently. + // This is left here for posterity. + + // This was quite messy with SPECIAL and commented parts. + // Supposedly hacks to make the latest edition work. + // It might not work properly. + if (episode < 1) + episode = 1; + + if ( gamemode == retail ) + { + if (episode > 4) + episode = 4; + } + else if ( gamemode == shareware ) + { + if (episode > 1) + episode = 1; // only start episode 1 on shareware + } + else + { + if (episode > 3) + episode = 3; + } + */ + + if (skill > sk_nightmare) + skill = sk_nightmare; + + if (gameversion >= exe_ultimate) { + if (episode == 0) { + episode = 4; + } + } else { + if (episode < 1) { + episode = 1; + } + if (episode > 3) { + episode = 3; + } + } + + if (episode > 1 && gamemode == shareware) { + episode = 1; + } + + if (map < 1) + map = 1; + + if ((map > 9) && (gamemode != commercial)) + map = 9; + + M_ClearRandom(); + + if (skill == sk_nightmare || respawnparm) + respawnmonsters = true; + else + respawnmonsters = false; + + if (fastparm || (skill == sk_nightmare && gameskill != sk_nightmare)) { + for (i = S_SARG_RUN1; i <= S_SARG_PAIN2; i++) + states[i].tics >>= 1; + mobjinfo[MT_BRUISERSHOT].speed = 20 * FRACUNIT; + mobjinfo[MT_HEADSHOT].speed = 20 * FRACUNIT; + mobjinfo[MT_TROOPSHOT].speed = 20 * FRACUNIT; + } else if (skill != sk_nightmare && gameskill == sk_nightmare) { + for (i = S_SARG_RUN1; i <= S_SARG_PAIN2; i++) + states[i].tics <<= 1; + mobjinfo[MT_BRUISERSHOT].speed = 15 * FRACUNIT; + mobjinfo[MT_HEADSHOT].speed = 10 * FRACUNIT; + mobjinfo[MT_TROOPSHOT].speed = 10 * FRACUNIT; + } + + // force players to be initialized upon first level load + for (i = 0; i < MAXPLAYERS; i++) + players[i].playerstate = PST_REBORN; + + usergame = true; // will be set false if a demo + paused = false; + demoplayback = false; + automapactive = false; + viewactive = true; + gameepisode = episode; + gamemap = map; + gameskill = skill; + + viewactive = true; + + // Set the sky to use. + // + // Note: This IS broken, but it is how Vanilla Doom behaves. + // See http://doomwiki.org/wiki/Sky_never_changes_in_Doom_II. + // + // Because we set the sky here at the start of a game, not at the + // start of a level, the sky texture never changes unless we + // restore from a saved game. This was fixed before the Doom + // source release, but this IS the way Vanilla DOS Doom behaves. + + if (gamemode == commercial) { + if (gamemap < 12) + skytexturename = "SKY1"; + else if (gamemap < 21) + skytexturename = "SKY2"; + else + skytexturename = "SKY3"; + } else { + switch (gameepisode) { + default: + case 1: + skytexturename = "SKY1"; + break; + case 2: + skytexturename = "SKY2"; + break; + case 3: + skytexturename = "SKY3"; + break; + case 4: // Special Edition sky + skytexturename = "SKY4"; + break; + } + } + + skytexture = R_TextureNumForName(skytexturename); + + G_DoLoadLevel(); +} + +// +// DEMO RECORDING +// +#define DEMOMARKER 0x80 + +void G_ReadDemoTiccmd(ticcmd_t *cmd) { + if (*demo_p == DEMOMARKER) { + // end of demo data stream + G_CheckDemoStatus(); + return; + } + cmd->forwardmove = ((signed char)*demo_p++); + cmd->sidemove = ((signed char)*demo_p++); + + // If this is a longtics demo, read back in higher resolution + + if (longtics) { + cmd->angleturn = *demo_p++; + cmd->angleturn |= (*demo_p++) << 8; + } else { + cmd->angleturn = ((unsigned char)*demo_p++) << 8; + } + + cmd->buttons = (unsigned char)*demo_p++; +} + +// Increase the size of the demo buffer to allow unlimited demos + +static void IncreaseDemoBuffer(void) { + int current_length; + byte *new_demobuffer; + byte *new_demop; + int new_length; + + // Find the current size + + current_length = demoend - demobuffer; + + // Generate a new buffer twice the size + new_length = current_length * 2; + + new_demobuffer = Z_Malloc(new_length, PU_STATIC, 0); + new_demop = new_demobuffer + (demo_p - demobuffer); + + // Copy over the old data + + memcpy(new_demobuffer, demobuffer, current_length); + + // Free the old buffer and point the demo pointers at the new buffer. + + Z_Free(demobuffer); + + demobuffer = new_demobuffer; + demo_p = new_demop; + demoend = demobuffer + new_length; +} + +void G_WriteDemoTiccmd(ticcmd_t *cmd) { + byte *demo_start; + + if (gamekeydown[key_demo_quit]) // press q to end demo recording + G_CheckDemoStatus(); + + demo_start = demo_p; + + *demo_p++ = cmd->forwardmove; + *demo_p++ = cmd->sidemove; + + // If this is a longtics demo, record in higher resolution + + if (longtics) { + *demo_p++ = (cmd->angleturn & 0xff); + *demo_p++ = (cmd->angleturn >> 8) & 0xff; + } else { + *demo_p++ = cmd->angleturn >> 8; + } + + *demo_p++ = cmd->buttons; + + // reset demo pointer back + demo_p = demo_start; + + if (demo_p > demoend - 16) { + if (vanilla_demo_limit) { + // no more space + G_CheckDemoStatus(); + return; + } else { + // Vanilla demo limit disabled: unlimited + // demo lengths! + + IncreaseDemoBuffer(); + } + } + + G_ReadDemoTiccmd(cmd); // make SURE it is exactly the same +} + +// +// G_RecordDemo +// +void G_RecordDemo(char *name) { + size_t demoname_size; + int i; + int maxsize; + + usergame = false; + demoname_size = strlen(name) + 5; + demoname = Z_Malloc(demoname_size, PU_STATIC, NULL); + M_snprintf(demoname, demoname_size, "%s.lmp", name); + maxsize = 0x20000; + + //! + // @arg + // @category demo + // @vanilla + // + // Specify the demo buffer size (KiB) + // + + i = M_CheckParmWithArgs("-maxdemo", 1); + if (i) + maxsize = atoi(myargv[i + 1]) * 1024; + demobuffer = Z_Malloc(maxsize, PU_STATIC, NULL); + demoend = demobuffer + maxsize; + + demorecording = true; +} + +// Get the demo version code appropriate for the version set in gameversion. +int G_VanillaVersionCode(void) { + switch (gameversion) { + case exe_doom_1_2: + I_Error("Doom 1.2 does not have a version code!"); + case exe_doom_1_666: + return 106; + case exe_doom_1_7: + return 107; + case exe_doom_1_8: + return 108; + case exe_doom_1_9: + default: // All other versions are variants on v1.9: + return 109; + } +} + +void G_BeginRecording(void) { + int i; + + demo_p = demobuffer; + + //! + // @category demo + // + // Record a high resolution "Doom 1.91" demo. + // + + longtics = + D_NonVanillaRecord(M_ParmExists("-longtics"), "Doom 1.91 demo format"); + + // If not recording a longtics demo, record in low res + lowres_turn = !longtics; + + if (longtics) { + *demo_p++ = DOOM_191_VERSION; + } else { + *demo_p++ = G_VanillaVersionCode(); + } + + *demo_p++ = gameskill; + *demo_p++ = gameepisode; + *demo_p++ = gamemap; + *demo_p++ = deathmatch; + *demo_p++ = respawnparm; + *demo_p++ = fastparm; + *demo_p++ = nomonsters; + *demo_p++ = consoleplayer; + + for (i = 0; i < MAXPLAYERS; i++) + *demo_p++ = playeringame[i]; +} + +// +// G_PlayDemo +// + +char *defdemoname; + +void G_DeferedPlayDemo(char *name) { + defdemoname = name; + gameaction = ga_playdemo; +} + +// Generate a string describing a demo version + +static char *DemoVersionDescription(int version) { + static char resultbuf[16]; + + switch (version) { + case 104: + return "v1.4"; + case 105: + return "v1.5"; + case 106: + return "v1.6/v1.666"; + case 107: + return "v1.7/v1.7a"; + case 108: + return "v1.8"; + case 109: + return "v1.9"; + case 111: + return "v1.91 hack demo?"; + default: + break; + } + + // Unknown version. Perhaps this is a pre-v1.4 IWAD? If the version + // byte is in the range 0-4 then it can be a v1.0-v1.2 demo. + + if (version >= 0 && version <= 4) { + return "v1.0/v1.1/v1.2"; + } else { + M_snprintf(resultbuf, sizeof(resultbuf), "%i.%i (unknown)", version / 100, + version % 100); + return resultbuf; + } +} + +void G_DoPlayDemo(void) { + skill_t skill; + int i, lumpnum, episode, map; + int demoversion; + + lumpnum = W_GetNumForName(defdemoname); + gameaction = ga_nothing; + demobuffer = W_CacheLumpNum(lumpnum, PU_STATIC); + demo_p = demobuffer; + + demoversion = *demo_p++; + + longtics = false; + + // Longtics demos use the modified format that is generated by cph's + // hacked "v1.91" doom exe. This is a non-vanilla extension. + if (D_NonVanillaPlayback(demoversion == DOOM_191_VERSION, lumpnum, + "Doom 1.91 demo format")) { + longtics = true; + } else if (demoversion != G_VanillaVersionCode()) { + char *message = "Demo is from a different game version!\n" + "(read %i, should be %i)\n" + "\n" + "*** You may need to upgrade your version " + "of Doom to v1.9. ***\n" + " See: https://www.doomworld.com/classicdoom" + "/info/patches.php\n" + " This appears to be %s."; + + I_Error(message, demoversion, G_VanillaVersionCode(), + DemoVersionDescription(demoversion)); + } + + skill = *demo_p++; + episode = *demo_p++; + map = *demo_p++; + deathmatch = *demo_p++; + respawnparm = *demo_p++; + fastparm = *demo_p++; + nomonsters = *demo_p++; + consoleplayer = *demo_p++; + + for (i = 0; i < MAXPLAYERS; i++) + playeringame[i] = *demo_p++; + + if (playeringame[1] || M_CheckParm("-solo-net") > 0 || + M_CheckParm("-netdemo") > 0) { + netgame = true; + netdemo = true; + } + + // don't spend a lot of time in loadlevel + precache = false; + G_InitNew(skill, episode, map); + precache = true; + starttime = I_GetTime(); + + usergame = false; + demoplayback = true; +} + +// +// G_TimeDemo +// +void G_TimeDemo(char *name) { + //! + // @category video + // @vanilla + // + // Disable rendering the screen entirely. + // + + nodrawers = M_CheckParm("-nodraw"); + + timingdemo = true; + singletics = true; + + defdemoname = name; + gameaction = ga_playdemo; +} + +/* +=================== += += G_CheckDemoStatus += += Called after a death or level completion to allow demos to be cleaned up += Returns true if a new demo loop action will take place +=================== +*/ + +boolean G_CheckDemoStatus(void) { + int endtime; + + if (timingdemo) { + float fps; + int realtics; + + endtime = I_GetTime(); + realtics = endtime - starttime; + fps = ((float)gametic * TICRATE) / realtics; + + // Prevent recursive calls + timingdemo = false; + demoplayback = false; + + I_Error("timed %i gametics in %i realtics (%f fps)", gametic, realtics, + fps); + } + + if (demoplayback) { + W_ReleaseLumpName(defdemoname); + demoplayback = false; + netdemo = false; + netgame = false; + deathmatch = false; + playeringame[1] = playeringame[2] = playeringame[3] = 0; + respawnparm = false; + fastparm = false; + nomonsters = false; + consoleplayer = 0; + + if (singledemo) + I_Quit(); + else + D_AdvanceDemo(); + + return true; + } + + if (demorecording) { + *demo_p++ = DEMOMARKER; + M_WriteFile(demoname, demobuffer, demo_p - demobuffer); + Z_Free(demobuffer); + demorecording = false; + I_Error("Demo %s recorded", demoname); + } + + return false; +} diff --git a/client/src/g_game.h b/client/src/g_game.h new file mode 100644 index 0000000..f9097af --- /dev/null +++ b/client/src/g_game.h @@ -0,0 +1,78 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Duh. +// + +#ifndef __G_GAME__ +#define __G_GAME__ + +#include "d_event.h" +#include "d_mode.h" +#include "d_ticcmd.h" +#include "doomtype.h" + +// +// GAME +// +void G_DeathMatchSpawnPlayer(int playernum); + +void G_InitNew(skill_t skill, int episode, int map); + +// Can be called by the startup code or M_Responder. +// A normal game starts at map 1, +// but a warp test can start elsewhere +void G_DeferedInitNew(skill_t skill, int episode, int map); + +void G_DeferedPlayDemo(char *demo); + +// Can be called by the startup code or M_Responder, +// calls P_SetupLevel or W_EnterWorld. +void G_LoadGame(char *name); + +void G_DoLoadGame(void); + +// Called by M_Responder. +void G_SaveGame(int slot, char *description); + +// Only called by startup code. +void G_RecordDemo(char *name); + +void G_BeginRecording(void); + +void G_PlayDemo(char *name); +void G_TimeDemo(char *name); +boolean G_CheckDemoStatus(void); + +void G_ExitLevel(void); +void G_SecretExitLevel(void); + +void G_WorldDone(void); + +// Read current data from inputs and build a player movement command. + +void G_BuildTiccmd(ticcmd_t *cmd, int maketic); + +void G_Ticker(void); +boolean G_Responder(event_t *ev); + +void G_ScreenShot(void); + +void G_DrawMouseSpeedBox(void); +int G_VanillaVersionCode(void); + +extern int vanilla_savegame_limit; +extern int vanilla_demo_limit; +#endif diff --git a/client/src/gusconf.c b/client/src/gusconf.c new file mode 100644 index 0000000..3117d32 --- /dev/null +++ b/client/src/gusconf.c @@ -0,0 +1,302 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// GUS emulation code. +// +// Actually emulating a GUS is far too much work; fortunately +// GUS "emulation" already exists in the form of Timidity, which +// supports GUS patch files. This code therefore converts Doom's +// DMXGUS lump into an equivalent Timidity configuration file. +// + + +#include +#include +#include +#include + +#include "doomtype.h" +#include "m_misc.h" +#include "w_wad.h" +#include "z_zone.h" + +#define MAX_INSTRUMENTS 256 + +typedef struct +{ + char *patch_names[MAX_INSTRUMENTS]; + int used[MAX_INSTRUMENTS]; + int mapping[MAX_INSTRUMENTS]; + unsigned int count; +} gus_config_t; + +char *gus_patch_path = ""; +int gus_ram_kb = 1024; + +static unsigned int MappingIndex(void) +{ + unsigned int result = gus_ram_kb / 256; + + if (result < 1) + { + return 1; + } + else if (result > 4) + { + return 4; + } + else + { + return result; + } +} + +static int SplitLine(char *line, char **fields, unsigned int max_fields) +{ + unsigned int num_fields; + char *p; + + fields[0] = line; + num_fields = 1; + + for (p = line; *p != '\0'; ++p) + { + if (*p == ',') + { + *p = '\0'; + + // Skip spaces following the comma. + do + { + ++p; + } while (*p != '\0' && isspace(*p)); + + fields[num_fields] = p; + ++num_fields; + --p; + + if (num_fields >= max_fields) + { + break; + } + } + else if (*p == '#') + { + *p = '\0'; + break; + } + } + + // Strip off trailing whitespace from the end of the line. + p = fields[num_fields - 1] + strlen(fields[num_fields - 1]); + while (p > fields[num_fields - 1] && isspace(*(p - 1))) + { + --p; + *p = '\0'; + } + + return num_fields; +} + +static void ParseLine(gus_config_t *config, char *line) +{ + char *fields[6]; + unsigned int i; + unsigned int num_fields; + unsigned int instr_id, mapped_id; + + num_fields = SplitLine(line, fields, 6); + + if (num_fields < 6) + { + return; + } + + instr_id = atoi(fields[0]); + + // Skip non GM percussions. + if ((instr_id >= 128 && instr_id < 128 + 35) || instr_id > 128 + 81) + { + return; + } + + mapped_id = atoi(fields[MappingIndex()]); + + for (i = 0; i < config->count; i++) + { + if (config->used[i] == mapped_id) + { + break; + } + } + + if (i == config->count) + { + // DMX uses wrong patch name (we should use name of 'mapped_id' + // instrument, but DMX uses name of 'instr_id' instead). + free(config->patch_names[i]); + config->patch_names[i] = M_StringDuplicate(fields[5]); + config->used[i] = mapped_id; + config->count++; + } + config->mapping[instr_id] = i; +} + +static void ParseDMXConfig(char *dmxconf, gus_config_t *config) +{ + char *p, *newline; + unsigned int i; + + memset(config, 0, sizeof(gus_config_t)); + + for (i = 0; i < MAX_INSTRUMENTS; ++i) + { + config->mapping[i] = -1; + config->used[i] = -1; + } + + config->count = 0; + + p = dmxconf; + + for (;;) + { + newline = strchr(p, '\n'); + + if (newline != NULL) + { + *newline = '\0'; + } + + ParseLine(config, p); + + if (newline == NULL) + { + break; + } + else + { + p = newline + 1; + } + } +} + +static void FreeDMXConfig(gus_config_t *config) +{ + unsigned int i; + + for (i = 0; i < MAX_INSTRUMENTS; ++i) + { + free(config->patch_names[i]); + } +} + +static char *ReadDMXConfig(void) +{ + int lumpnum; + unsigned int len; + char *data; + + // TODO: This should be chosen based on gamemode == commercial: + + lumpnum = W_CheckNumForName("DMXGUS"); + + if (lumpnum < 0) + { + lumpnum = W_GetNumForName("DMXGUSC"); + } + + len = W_LumpLength(lumpnum); + data = Z_Malloc(len + 1, PU_STATIC, NULL); + W_ReadLump(lumpnum, data); + + data[len] = '\0'; + return data; +} + +static boolean WriteTimidityConfig(char *path, gus_config_t *config) +{ + FILE *fstream; + unsigned int i; + + fstream = fopen(path, "w"); + + if (fstream == NULL) + { + return false; + } + + fprintf(fstream, "# Autogenerated Timidity config.\n\n"); + + fprintf(fstream, "dir %s\n", gus_patch_path); + + fprintf(fstream, "\nbank 0\n\n"); + + for (i = 0; i < 128; ++i) + { + if (config->mapping[i] >= 0 && config->mapping[i] < MAX_INSTRUMENTS + && config->patch_names[config->mapping[i]] != NULL) + { + fprintf(fstream, "%i %s\n", + i, config->patch_names[config->mapping[i]]); + } + } + + fprintf(fstream, "\ndrumset 0\n\n"); + + for (i = 128 + 35; i <= 128 + 81; ++i) + { + if (config->mapping[i] >= 0 && config->mapping[i] < MAX_INSTRUMENTS + && config->patch_names[config->mapping[i]] != NULL) + { + fprintf(fstream, "%i %s\n", + i - 128, config->patch_names[config->mapping[i]]); + } + } + + fprintf(fstream, "\n"); + + fclose(fstream); + + return true; +} + +boolean GUS_WriteConfig(char *path) +{ + boolean result; + char *dmxconf; + gus_config_t config; + + if (!strcmp(gus_patch_path, "")) + { + printf("You haven't configured gus_patch_path.\n"); + printf("gus_patch_path needs to point to the location of " + "your GUS patch set.\n" + "To get a copy of the \"standard\" GUS patches, " + "download a copy of dgguspat.zip.\n"); + + return false; + } + + dmxconf = ReadDMXConfig(); + ParseDMXConfig(dmxconf, &config); + + result = WriteTimidityConfig(path, &config); + + FreeDMXConfig(&config); + Z_Free(dmxconf); + + return result; +} + diff --git a/client/src/gusconf.h b/client/src/gusconf.h new file mode 100644 index 0000000..4efbe99 --- /dev/null +++ b/client/src/gusconf.h @@ -0,0 +1,29 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// GUS emulation code. +// + +#ifndef __GUSCONF_H__ +#define __GUSCONF_H__ + +#include "doomtype.h" + +extern char *gus_patch_path; +extern int gus_ram_kb; + +boolean GUS_WriteConfig(char *path); + +#endif /* #ifndef __GUSCONF_H__ */ + diff --git a/client/src/hu_lib.c b/client/src/hu_lib.c new file mode 100644 index 0000000..f1559f4 --- /dev/null +++ b/client/src/hu_lib.c @@ -0,0 +1,261 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: heads-up text and input code +// + +#include + +#include "doomkeys.h" +#include "hu_lib.h" +#include "i_swap.h" +#include "i_video.h" +#include "r_draw.h" +#include "r_main.h" +#include "r_state.h" +#include "v_video.h" + +// boolean : whether the screen is always erased +#define noterased viewwindowx + +extern boolean automapactive; // in AM_map.c + +void HUlib_init(void) {} + +void HUlib_clearTextLine(hu_textline_t *t) { + t->len = 0; + t->l[0] = 0; + t->needsupdate = true; +} + +void HUlib_initTextLine(hu_textline_t *t, int x, int y, patch_t **f, int sc) { + t->x = x; + t->y = y; + t->f = f; + t->sc = sc; + HUlib_clearTextLine(t); +} + +boolean HUlib_addCharToTextLine(hu_textline_t *t, char ch) { + + if (t->len == HU_MAXLINELENGTH) + return false; + else { + t->l[t->len++] = ch; + t->l[t->len] = 0; + t->needsupdate = 4; + return true; + } +} + +boolean HUlib_delCharFromTextLine(hu_textline_t *t) { + + if (!t->len) + return false; + else { + t->l[--t->len] = 0; + t->needsupdate = 4; + return true; + } +} + +void HUlib_drawTextLine(hu_textline_t *l, boolean drawcursor) { + + int i; + int w; + int x; + unsigned char c; + + // draw the new stuff + x = l->x; + for (i = 0; i < l->len; i++) { + c = toupper(l->l[i]); + if (c != ' ' && c >= l->sc && c <= '_') { + w = SHORT(l->f[c - l->sc]->width); + if (x + w > SCREENWIDTH) + break; + V_DrawPatchDirect(x, l->y, l->f[c - l->sc]); + x += w; + } else { + x += 4; + if (x >= SCREENWIDTH) + break; + } + } + + // draw the cursor if requested + if (drawcursor && x + SHORT(l->f['_' - l->sc]->width) <= SCREENWIDTH) { + V_DrawPatchDirect(x, l->y, l->f['_' - l->sc]); + } +} + +// sorta called by HU_Erase and just better darn get things straight +void HUlib_eraseTextLine(hu_textline_t *l) { + int lh; + int y; + int yoffset; + + // Only erases when NOT in automap and the screen is reduced, + // and the text must either need updating or refreshing + // (because of a recent change back from the automap) + + if (!automapactive && viewwindowx && l->needsupdate) { + lh = SHORT(l->f[0]->height) + 1; + for (y = l->y, yoffset = y * SCREENWIDTH; y < l->y + lh; + y++, yoffset += SCREENWIDTH) { + if (y < viewwindowy || y >= viewwindowy + viewheight) + R_VideoErase(yoffset, SCREENWIDTH); // erase entire line + else { + R_VideoErase(yoffset, viewwindowx); // erase left border + R_VideoErase(yoffset + viewwindowx + viewwidth, viewwindowx); + // erase right border + } + } + } + + if (l->needsupdate) + l->needsupdate--; +} + +void HUlib_initSText(hu_stext_t *s, int x, int y, int h, patch_t **font, + int startchar, boolean *on) { + + int i; + + s->h = h; + s->on = on; + s->laston = true; + s->cl = 0; + for (i = 0; i < h; i++) + HUlib_initTextLine(&s->l[i], x, y - i * (SHORT(font[0]->height) + 1), font, + startchar); +} + +void HUlib_addLineToSText(hu_stext_t *s) { + + int i; + + // add a clear line + if (++s->cl == s->h) + s->cl = 0; + HUlib_clearTextLine(&s->l[s->cl]); + + // everything needs updating + for (i = 0; i < s->h; i++) + s->l[i].needsupdate = 4; +} + +void HUlib_addMessageToSText(hu_stext_t *s, char *prefix, char *msg) { + HUlib_addLineToSText(s); + if (prefix) + while (*prefix) + HUlib_addCharToTextLine(&s->l[s->cl], *(prefix++)); + + while (*msg) + HUlib_addCharToTextLine(&s->l[s->cl], *(msg++)); +} + +void HUlib_drawSText(hu_stext_t *s) { + int i, idx; + hu_textline_t *l; + + if (!*s->on) + return; // if not on, don't draw + + // draw everything + for (i = 0; i < s->h; i++) { + idx = s->cl - i; + if (idx < 0) + idx += s->h; // handle queue of lines + + l = &s->l[idx]; + + // need a decision made here on whether to skip the draw + HUlib_drawTextLine(l, false); // no cursor, please + } +} + +void HUlib_eraseSText(hu_stext_t *s) { + + int i; + + for (i = 0; i < s->h; i++) { + if (s->laston && !*s->on) + s->l[i].needsupdate = 4; + HUlib_eraseTextLine(&s->l[i]); + } + s->laston = *s->on; +} + +void HUlib_initIText(hu_itext_t *it, int x, int y, patch_t **font, + int startchar, boolean *on) { + it->lm = 0; // default left margin is start of text + it->on = on; + it->laston = true; + HUlib_initTextLine(&it->l, x, y, font, startchar); +} + +// The following deletion routines adhere to the left margin restriction +void HUlib_delCharFromIText(hu_itext_t *it) { + if (it->l.len != it->lm) + HUlib_delCharFromTextLine(&it->l); +} + +void HUlib_eraseLineFromIText(hu_itext_t *it) { + while (it->lm != it->l.len) + HUlib_delCharFromTextLine(&it->l); +} + +// Resets left margin as well +void HUlib_resetIText(hu_itext_t *it) { + it->lm = 0; + HUlib_clearTextLine(&it->l); +} + +void HUlib_addPrefixToIText(hu_itext_t *it, char *str) { + while (*str) + HUlib_addCharToTextLine(&it->l, *(str++)); + it->lm = it->l.len; +} + +// wrapper function for handling general keyed input. +// returns true if it ate the key +boolean HUlib_keyInIText(hu_itext_t *it, unsigned char ch) { + ch = toupper(ch); + + if (ch >= ' ' && ch <= '_') + HUlib_addCharToTextLine(&it->l, (char)ch); + else if (ch == KEY_BACKSPACE) + HUlib_delCharFromIText(it); + else if (ch != KEY_ENTER) + return false; // did not eat key + + return true; // ate the key +} + +void HUlib_drawIText(hu_itext_t *it) { + + hu_textline_t *l = &it->l; + + if (!*it->on) + return; + HUlib_drawTextLine(l, true); // draw the line w/ cursor +} + +void HUlib_eraseIText(hu_itext_t *it) { + if (it->laston && !*it->on) + it->l.needsupdate = 4; + HUlib_eraseTextLine(&it->l); + it->laston = *it->on; +} diff --git a/client/src/hu_lib.h b/client/src/hu_lib.h new file mode 100644 index 0000000..085cadb --- /dev/null +++ b/client/src/hu_lib.h @@ -0,0 +1,152 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: none +// + +#ifndef __HULIB__ +#define __HULIB__ + +#include "doomkeys.h" +#include "doomtype.h" +// We are referring to patches. +#include "v_patch.h" + +// font stuff +#define HU_CHARERASE KEY_BACKSPACE + +#define HU_MAXLINES 4 +#define HU_MAXLINELENGTH 80 + +// +// Typedefs of widgets +// + +// Text Line widget +// (parent of Scrolling Text and Input Text widgets) +typedef struct { + // left-justified position of scrolling text window + int x; + int y; + + patch_t **f; // font + int sc; // start character + char l[HU_MAXLINELENGTH + 1]; // line of text + int len; // current line length + + // whether this line needs to be udpated + int needsupdate; + +} hu_textline_t; + +// Scrolling Text window widget +// (child of Text Line widget) +typedef struct { + hu_textline_t l[HU_MAXLINES]; // text lines to draw + int h; // height in lines + int cl; // current line number + + // pointer to boolean stating whether to update window + boolean *on; + boolean laston; // last value of *->on. + +} hu_stext_t; + +// Input Text Line widget +// (child of Text Line widget) +typedef struct { + hu_textline_t l; // text line to input on + + // left margin past which I am not to delete characters + int lm; + + // pointer to boolean stating whether to update window + boolean *on; + boolean laston; // last value of *->on; + +} hu_itext_t; + +// +// Widget creation, access, and update routines +// + +// initializes heads-up widget library +void HUlib_init(void); + +// +// textline code +// + +// clear a line of text +void HUlib_clearTextLine(hu_textline_t *t); + +void HUlib_initTextLine(hu_textline_t *t, int x, int y, patch_t **f, int sc); + +// returns success +boolean HUlib_addCharToTextLine(hu_textline_t *t, char ch); + +// returns success +boolean HUlib_delCharFromTextLine(hu_textline_t *t); + +// draws tline +void HUlib_drawTextLine(hu_textline_t *l, boolean drawcursor); + +// erases text line +void HUlib_eraseTextLine(hu_textline_t *l); + +// +// Scrolling Text window widget routines +// + +// ? +void HUlib_initSText(hu_stext_t *s, int x, int y, int h, patch_t **font, + int startchar, boolean *on); + +// add a new line +void HUlib_addLineToSText(hu_stext_t *s); + +// ? +void HUlib_addMessageToSText(hu_stext_t *s, char *prefix, char *msg); + +// draws stext +void HUlib_drawSText(hu_stext_t *s); + +// erases all stext lines +void HUlib_eraseSText(hu_stext_t *s); + +// Input Text Line widget routines +void HUlib_initIText(hu_itext_t *it, int x, int y, patch_t **font, + int startchar, boolean *on); + +// enforces left margin +void HUlib_delCharFromIText(hu_itext_t *it); + +// enforces left margin +void HUlib_eraseLineFromIText(hu_itext_t *it); + +// resets line and left margin +void HUlib_resetIText(hu_itext_t *it); + +// left of left-margin +void HUlib_addPrefixToIText(hu_itext_t *it, char *str); + +// whether eaten +boolean HUlib_keyInIText(hu_itext_t *it, unsigned char ch); + +void HUlib_drawIText(hu_itext_t *it); + +// erases all itext lines +void HUlib_eraseIText(hu_itext_t *it); + +#endif diff --git a/client/src/hu_stuff.c b/client/src/hu_stuff.c new file mode 100644 index 0000000..bbc6862 --- /dev/null +++ b/client/src/hu_stuff.c @@ -0,0 +1,457 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: Heads-up displays +// + +#include "hu_stuff.h" +#include "d_englsh.h" +#include "d_mode.h" +#include "d_player.h" +#include "d_ticcmd.h" +#include "doomdef.h" +#include "doomkeys.h" +#include "doomstat.h" +#include "hu_lib.h" +#include "i_input.h" +#include "i_swap.h" +#include "i_video.h" +#include "m_controls.h" +#include "m_misc.h" +#include "s_sound.h" +#include "sounds.h" +#include "v_patch.h" +#include "w_wad.h" +#include "z_zone.h" + +// +// Locally used constants, shortcuts. +// +#define HU_TITLE (mapnames[(gameepisode - 1) * 9 + gamemap - 1]) +#define HU_TITLE2 (mapnames_commercial[gamemap - 1]) +#define HU_TITLEP (mapnames_commercial[gamemap - 1 + 32]) +#define HU_TITLET (mapnames_commercial[gamemap - 1 + 64]) +#define HU_TITLE_CHEX (mapnames_chex[(gameepisode - 1) * 9 + gamemap - 1]) +#define HU_TITLEHEIGHT 1 +#define HU_TITLEX 0 +#define HU_TITLEY (167 - SHORT(hu_font[0]->height)) + +#define HU_INPUTTOGGLE 't' +#define HU_INPUTX HU_MSGX +#define HU_INPUTY (HU_MSGY + HU_MSGHEIGHT * (SHORT(hu_font[0]->height) + 1)) +#define HU_INPUTWIDTH 64 +#define HU_INPUTHEIGHT 1 + +char *chat_macros[10] = {HUSTR_CHATMACRO0, HUSTR_CHATMACRO1, HUSTR_CHATMACRO2, + HUSTR_CHATMACRO3, HUSTR_CHATMACRO4, HUSTR_CHATMACRO5, + HUSTR_CHATMACRO6, HUSTR_CHATMACRO7, HUSTR_CHATMACRO8, + HUSTR_CHATMACRO9}; + +char *player_names[] = {HUSTR_PLRGREEN, HUSTR_PLRINDIGO, HUSTR_PLRBROWN, + HUSTR_PLRRED}; + +char chat_char; // remove later. +static player_t *plr; +patch_t *hu_font[HU_FONTSIZE]; +static hu_textline_t w_title; +boolean chat_on; +static hu_itext_t w_chat; +static boolean always_off = false; +static char chat_dest[MAXPLAYERS]; +static hu_itext_t w_inputbuffer[MAXPLAYERS]; + +static boolean message_on; +boolean message_dontfuckwithme; +static boolean message_nottobefuckedwith; + +static hu_stext_t w_message; +static int message_counter; + +extern int showMessages; + +static boolean headsupactive = false; + +// +// Builtin map names. +// The actual names can be found in DStrings.h. +// + +char *mapnames[] = // DOOM shareware/registered/retail (Ultimate) names. + { + + HUSTR_E1M1, HUSTR_E1M2, HUSTR_E1M3, HUSTR_E1M4, HUSTR_E1M5, + HUSTR_E1M6, HUSTR_E1M7, HUSTR_E1M8, HUSTR_E1M9, + + HUSTR_E2M1, HUSTR_E2M2, HUSTR_E2M3, HUSTR_E2M4, HUSTR_E2M5, + HUSTR_E2M6, HUSTR_E2M7, HUSTR_E2M8, HUSTR_E2M9, + + HUSTR_E3M1, HUSTR_E3M2, HUSTR_E3M3, HUSTR_E3M4, HUSTR_E3M5, + HUSTR_E3M6, HUSTR_E3M7, HUSTR_E3M8, HUSTR_E3M9, + + HUSTR_E4M1, HUSTR_E4M2, HUSTR_E4M3, HUSTR_E4M4, HUSTR_E4M5, + HUSTR_E4M6, HUSTR_E4M7, HUSTR_E4M8, HUSTR_E4M9, + + "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", + "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", "NEWLEVEL"}; + +char *mapnames_chex[] = // Chex Quest names. + { + + HUSTR_E1M1, HUSTR_E1M2, HUSTR_E1M3, HUSTR_E1M4, HUSTR_E1M5, + HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, + + HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, + HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, + + HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, + HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, + + HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, + HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, HUSTR_E1M5, + + "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", + "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", "NEWLEVEL"}; + +// List of names for levels in commercial IWADs +// (doom2.wad, plutonia.wad, tnt.wad). These are stored in a +// single large array; WADs like pl2.wad have a MAP33, and rely on +// the layout in the Vanilla executable, where it is possible to +// overflow the end of one array into the next. + +char *mapnames_commercial[] = { + // DOOM 2 map names. + + HUSTR_1, HUSTR_2, HUSTR_3, HUSTR_4, HUSTR_5, HUSTR_6, HUSTR_7, HUSTR_8, + HUSTR_9, HUSTR_10, HUSTR_11, + + HUSTR_12, HUSTR_13, HUSTR_14, HUSTR_15, HUSTR_16, HUSTR_17, HUSTR_18, + HUSTR_19, HUSTR_20, + + HUSTR_21, HUSTR_22, HUSTR_23, HUSTR_24, HUSTR_25, HUSTR_26, HUSTR_27, + HUSTR_28, HUSTR_29, HUSTR_30, HUSTR_31, HUSTR_32, + + // Plutonia WAD map names. + + PHUSTR_1, PHUSTR_2, PHUSTR_3, PHUSTR_4, PHUSTR_5, PHUSTR_6, PHUSTR_7, + PHUSTR_8, PHUSTR_9, PHUSTR_10, PHUSTR_11, + + PHUSTR_12, PHUSTR_13, PHUSTR_14, PHUSTR_15, PHUSTR_16, PHUSTR_17, PHUSTR_18, + PHUSTR_19, PHUSTR_20, + + PHUSTR_21, PHUSTR_22, PHUSTR_23, PHUSTR_24, PHUSTR_25, PHUSTR_26, PHUSTR_27, + PHUSTR_28, PHUSTR_29, PHUSTR_30, PHUSTR_31, PHUSTR_32, + + // TNT WAD map names. + + THUSTR_1, THUSTR_2, THUSTR_3, THUSTR_4, THUSTR_5, THUSTR_6, THUSTR_7, + THUSTR_8, THUSTR_9, THUSTR_10, THUSTR_11, + + THUSTR_12, THUSTR_13, THUSTR_14, THUSTR_15, THUSTR_16, THUSTR_17, THUSTR_18, + THUSTR_19, THUSTR_20, + + THUSTR_21, THUSTR_22, THUSTR_23, THUSTR_24, THUSTR_25, THUSTR_26, THUSTR_27, + THUSTR_28, THUSTR_29, THUSTR_30, THUSTR_31, THUSTR_32}; + +void HU_Init(void) { + + int i; + int j; + char buffer[9]; + + // load the heads-up font + j = HU_FONTSTART; + for (i = 0; i < HU_FONTSIZE; i++) { + M_snprintf(buffer, 9, "STCFN%.3d", j++); + hu_font[i] = (patch_t *)W_CacheLumpName(buffer, PU_STATIC); + } +} + +void HU_Stop(void) { headsupactive = false; } + +void HU_Start(void) { + + int i; + char *s; + + if (headsupactive) + HU_Stop(); + + plr = &players[consoleplayer]; + message_on = false; + message_dontfuckwithme = false; + message_nottobefuckedwith = false; + chat_on = false; + + // create the message widget + HUlib_initSText(&w_message, HU_MSGX, HU_MSGY, HU_MSGHEIGHT, hu_font, + HU_FONTSTART, &message_on); + + // create the map title widget + HUlib_initTextLine(&w_title, HU_TITLEX, HU_TITLEY, hu_font, HU_FONTSTART); + + switch (logical_gamemission) { + case doom: + s = HU_TITLE; + break; + case doom2: + s = HU_TITLE2; + break; + case pack_plut: + s = HU_TITLEP; + break; + case pack_tnt: + s = HU_TITLET; + break; + default: + s = "Unknown level"; + break; + } + + if (logical_gamemission == doom && gameversion == exe_chex) { + s = HU_TITLE_CHEX; + } + + while (*s) + HUlib_addCharToTextLine(&w_title, *(s++)); + + // create the chat widget + HUlib_initIText(&w_chat, HU_INPUTX, HU_INPUTY, hu_font, HU_FONTSTART, + &chat_on); + + // create the inputbuffer widgets + for (i = 0; i < MAXPLAYERS; i++) + HUlib_initIText(&w_inputbuffer[i], 0, 0, 0, 0, &always_off); + + headsupactive = true; +} + +void HU_Drawer(void) { + + HUlib_drawSText(&w_message); + HUlib_drawIText(&w_chat); + if (automapactive) + HUlib_drawTextLine(&w_title, false); +} + +void HU_Erase(void) { + + HUlib_eraseSText(&w_message); + HUlib_eraseIText(&w_chat); + HUlib_eraseTextLine(&w_title); +} + +void HU_Ticker(void) { + + int i, rc; + char c; + + // tick down message counter if message is up + if (message_counter && !--message_counter) { + message_on = false; + message_nottobefuckedwith = false; + } + + if (showMessages || message_dontfuckwithme) { + + // display message if necessary + if ((plr->message && !message_nottobefuckedwith) || + (plr->message && message_dontfuckwithme)) { + HUlib_addMessageToSText(&w_message, 0, plr->message); + plr->message = 0; + message_on = true; + message_counter = HU_MSGTIMEOUT; + message_nottobefuckedwith = message_dontfuckwithme; + message_dontfuckwithme = 0; + } + + } // else message_on = false; + + // check for incoming chat characters + if (netgame) { + for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + if (i != consoleplayer && (c = players[i].cmd.chatchar)) { + if (c <= HU_BROADCAST) + chat_dest[i] = c; + else { + rc = HUlib_keyInIText(&w_inputbuffer[i], c); + if (rc && c == KEY_ENTER) { + if (w_inputbuffer[i].l.len && (chat_dest[i] == consoleplayer + 1 || + chat_dest[i] == HU_BROADCAST)) { + HUlib_addMessageToSText(&w_message, (player_names[i]), + w_inputbuffer[i].l.l); + + message_nottobefuckedwith = true; + message_on = true; + message_counter = HU_MSGTIMEOUT; + if (gamemode == commercial) + S_StartSound(0, sfx_radio); + else + S_StartSound(0, sfx_tink); + } + HUlib_resetIText(&w_inputbuffer[i]); + } + } + players[i].cmd.chatchar = 0; + } + } + } +} + +#define QUEUESIZE 128 + +static char chatchars[QUEUESIZE]; +static int head = 0; +static int tail = 0; + +void HU_queueChatChar(char c) { + if (((head + 1) & (QUEUESIZE - 1)) == tail) { + plr->message = (HUSTR_MSGU); + } else { + chatchars[head] = c; + head = (head + 1) & (QUEUESIZE - 1); + } +} + +char HU_dequeueChatChar(void) { + char c; + + if (head != tail) { + c = chatchars[tail]; + tail = (tail + 1) & (QUEUESIZE - 1); + } else { + c = 0; + } + + return c; +} + +static void StartChatInput(int dest) { + chat_on = true; + HUlib_resetIText(&w_chat); + HU_queueChatChar(HU_BROADCAST); + + I_StartTextInput(0, 8, SCREENWIDTH, 16); +} + +static void StopChatInput(void) { + chat_on = false; + I_StopTextInput(); +} + +boolean HU_Responder(event_t *ev) { + + static char lastmessage[HU_MAXLINELENGTH + 1]; + char *macromessage; + boolean eatkey = false; + static boolean altdown = false; + unsigned char c; + int i; + int numplayers; + + static int num_nobrainers = 0; + + numplayers = 0; + for (i = 0; i < MAXPLAYERS; i++) + numplayers += playeringame[i]; + + if (ev->data1 == KEY_RSHIFT) { + return false; + } else if (ev->data1 == KEY_RALT || ev->data1 == KEY_LALT) { + altdown = ev->type == ev_keydown; + return false; + } + + if (ev->type != ev_keydown) + return false; + + if (!chat_on) { + if (ev->data1 == key_message_refresh) { + message_on = true; + message_counter = HU_MSGTIMEOUT; + eatkey = true; + } else if (netgame && ev->data2 == key_multi_msg) { + eatkey = true; + StartChatInput(HU_BROADCAST); + } else if (netgame && numplayers > 2) { + for (i = 0; i < MAXPLAYERS; i++) { + if (ev->data2 == key_multi_msgplayer[i]) { + if (playeringame[i] && i != consoleplayer) { + eatkey = true; + StartChatInput(i + 1); + break; + } else if (i == consoleplayer) { + num_nobrainers++; + if (num_nobrainers < 3) + plr->message = (HUSTR_TALKTOSELF1); + else if (num_nobrainers < 6) + plr->message = (HUSTR_TALKTOSELF2); + else if (num_nobrainers < 9) + plr->message = (HUSTR_TALKTOSELF3); + else if (num_nobrainers < 32) + plr->message = (HUSTR_TALKTOSELF4); + else + plr->message = (HUSTR_TALKTOSELF5); + } + } + } + } + } else { + // send a macro + if (altdown) { + c = ev->data1 - '0'; + if (c > 9) + return false; + // fprintf(stderr, "got here\n"); + macromessage = chat_macros[c]; + + // kill last message with a '\n' + HU_queueChatChar(KEY_ENTER); // DEBUG!!! + + // send the macro message + while (*macromessage) + HU_queueChatChar(*macromessage++); + HU_queueChatChar(KEY_ENTER); + + // leave chat mode and notify that it was sent + StopChatInput(); + M_StringCopy(lastmessage, chat_macros[c], sizeof(lastmessage)); + plr->message = lastmessage; + eatkey = true; + } else { + c = ev->data3; + + eatkey = HUlib_keyInIText(&w_chat, c); + if (eatkey) { + // static unsigned char buf[20]; // DEBUG + HU_queueChatChar(c); + + // M_snprintf(buf, sizeof(buf), "KEY: %d => %d", ev->data1, c); + // plr->message = buf; + } + if (c == KEY_ENTER) { + StopChatInput(); + if (w_chat.l.len) { + M_StringCopy(lastmessage, w_chat.l.l, sizeof(lastmessage)); + plr->message = lastmessage; + } + } else if (c == KEY_ESCAPE) { + StopChatInput(); + } + } + } + + return eatkey; +} diff --git a/client/src/hu_stuff.h b/client/src/hu_stuff.h new file mode 100644 index 0000000..3ebac49 --- /dev/null +++ b/client/src/hu_stuff.h @@ -0,0 +1,59 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: Head up display +// + +#ifndef __HU_STUFF_H__ +#define __HU_STUFF_H__ + +#include "d_event.h" +#include "doomtype.h" +#include "i_timer.h" + +// +// Globally visible constants. +// +#define HU_FONTSTART '!' // the first font characters +#define HU_FONTEND '_' // the last font characters + +// Calculate # of glyphs in font. +#define HU_FONTSIZE (HU_FONTEND - HU_FONTSTART + 1) + +#define HU_BROADCAST 5 + +#define HU_MSGX 0 +#define HU_MSGY 0 +#define HU_MSGWIDTH 64 // in characters +#define HU_MSGHEIGHT 1 // in lines + +#define HU_MSGTIMEOUT (4 * TICRATE) + +// +// HEADS UP TEXT +// + +void HU_Init(void); +void HU_Start(void); + +boolean HU_Responder(event_t *ev); + +void HU_Ticker(void); +void HU_Drawer(void); +char HU_dequeueChatChar(void); +void HU_Erase(void); + +extern char *chat_macros[10]; + +#endif diff --git a/client/src/i_cdmus.c b/client/src/i_cdmus.c new file mode 100644 index 0000000..b006e0f --- /dev/null +++ b/client/src/i_cdmus.c @@ -0,0 +1,69 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// +// 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. +// +// +// Hexen CD interface. +// + +#include "i_cdmus.h" + +int cd_Error; + +int I_CDMusInit(void) +{ + return 0; +} + +// We cannot print status messages inline during startup, they must +// be deferred until after I_CDMusInit has returned. + +void I_CDMusPrintStartup(void) +{ +} + +int I_CDMusPlay(int track) +{ + return 0; +} + +int I_CDMusStop(void) +{ + return 0; +} + +int I_CDMusResume(void) +{ + return 0; +} + +int I_CDMusSetVolume(int volume) +{ + return 0; +} + +int I_CDMusFirstTrack(void) +{ + return 0; +} + +int I_CDMusLastTrack(void) +{ + return 0; +} + +int I_CDMusTrackLength(int track_num) +{ + return 0; +} + diff --git a/client/src/i_cdmus.h b/client/src/i_cdmus.h new file mode 100644 index 0000000..31db2a6 --- /dev/null +++ b/client/src/i_cdmus.h @@ -0,0 +1,41 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// +// 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. +// + +// i_cdmus.h + +#ifndef __ICDMUS__ +#define __ICDMUS__ + +#define CDERR_NOTINSTALLED 10 // MSCDEX not installed +#define CDERR_NOAUDIOSUPPORT 11 // CD-ROM Doesn't support audio +#define CDERR_NOAUDIOTRACKS 12 // Current CD has no audio tracks +#define CDERR_BADDRIVE 20 // Bad drive number +#define CDERR_BADTRACK 21 // Bad track number +#define CDERR_IOCTLBUFFMEM 22 // Not enough low memory for IOCTL +#define CDERR_DEVREQBASE 100 // DevReq errors + +extern int cd_Error; + +int I_CDMusInit(void); +void I_CDMusPrintStartup(void); +int I_CDMusPlay(int track); +int I_CDMusStop(void); +int I_CDMusResume(void); +int I_CDMusSetVolume(int volume); +int I_CDMusFirstTrack(void); +int I_CDMusLastTrack(void); +int I_CDMusTrackLength(int track); + +#endif diff --git a/client/src/i_input.c b/client/src/i_input.c new file mode 100644 index 0000000..b0d4ca7 --- /dev/null +++ b/client/src/i_input.c @@ -0,0 +1,471 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// SDL implementation of system-specific input interface. +// + + +#include + +#include "SDL2/SDL_events.h" +#include "SDL2/SDL_keyboard.h" +#include "SDL2/SDL_keycode.h" +#include "SDL2/SDL_mouse.h" +#include "SDL2/SDL_scancode.h" +#include "d_event.h" +#include "doomkeys.h" +#include "doomtype.h" +#include "i_input.h" +#include "m_config.h" + +static const int scancode_translate_table[] = SCANCODE_TO_KEYS_ARRAY; + +// Lookup table for mapping ASCII characters to their equivalent when +// shift is pressed on a US layout keyboard. This is the original table +// as found in the Doom sources, comments and all. +static const char shiftxform[] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, ' ', '!', '"', '#', '$', '%', '&', + '"', // shift-' + '(', ')', '*', '+', + '<', // shift-, + '_', // shift-- + '>', // shift-. + '?', // shift-/ + ')', // shift-0 + '!', // shift-1 + '@', // shift-2 + '#', // shift-3 + '$', // shift-4 + '%', // shift-5 + '^', // shift-6 + '&', // shift-7 + '*', // shift-8 + '(', // shift-9 + ':', + ':', // shift-; + '<', + '+', // shift-= + '>', '?', '@', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '[', // shift-[ + '!', // shift-backslash - OH MY GOD DOES WATCOM SUCK + ']', // shift-] + '"', '_', + '\'', // shift-` + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '{', '|', '}', '~', 127 +}; + +// If true, I_StartTextInput() has been called, and we are populating +// the data3 field of ev_keydown events. +static boolean text_input_enabled = true; + +// Bit mask of mouse button state. +static unsigned int mouse_button_state = 0; + +// Disallow mouse and joystick movement to cause forward/backward +// motion. Specified with the '-novert' command line parameter. +// This is an int to allow saving to config file +int novert = 0; + +// If true, keyboard mapping is ignored, like in Vanilla Doom. +// The sensible thing to do is to disable this if you have a non-US +// keyboard. + +int vanilla_keyboard_mapping = true; + +// Mouse acceleration +// +// This emulates some of the behavior of DOS mouse drivers by increasing +// the speed when the mouse is moved fast. +// +// The mouse input values are input directly to the game, but when +// the values exceed the value of mouse_threshold, they are multiplied +// by mouse_acceleration to increase the speed. +float mouse_acceleration = 2.0; +int mouse_threshold = 10; + +// Translates the SDL key to a value of the type found in doomkeys.h +static int TranslateKey(SDL_Keysym *sym) +{ + int scancode = sym->scancode; + + switch (scancode) + { + case SDL_SCANCODE_LCTRL: + case SDL_SCANCODE_RCTRL: + return KEY_RCTRL; + + case SDL_SCANCODE_LSHIFT: + case SDL_SCANCODE_RSHIFT: + return KEY_RSHIFT; + + case SDL_SCANCODE_LALT: + return KEY_LALT; + + case SDL_SCANCODE_RALT: + return KEY_RALT; + + default: + if (scancode >= 0 && scancode < arrlen(scancode_translate_table)) + { + return scancode_translate_table[scancode]; + } + else + { + return 0; + } + } +} + +// Get the localized version of the key press. This takes into account the +// keyboard layout, but does not apply any changes due to modifiers, (eg. +// shift-, alt-, etc.) +static int GetLocalizedKey(SDL_Keysym *sym) +{ + // When using Vanilla mapping, we just base everything off the scancode + // and always pretend the user is using a US layout keyboard. + if (vanilla_keyboard_mapping) + { + return TranslateKey(sym); + } + else + { + int result = sym->sym; + + if (result < 0 || result >= 128) + { + result = 0; + } + + return result; + } +} + +// Get the equivalent ASCII (Unicode?) character for a keypress. +static int GetTypedChar(SDL_Keysym *sym) +{ + // We only return typed characters when entering text, after + // I_StartTextInput() has been called. Otherwise we return nothing. + if (!text_input_enabled) + { + return 0; + } + + // If we're strictly emulating Vanilla, we should always act like + // we're using a US layout keyboard (in ev_keydown, data1=data2). + // Otherwise we should use the native key mapping. + if (vanilla_keyboard_mapping) + { + int result = TranslateKey(sym); + + // If shift is held down, apply the original uppercase + // translation table used under DOS. + if ((SDL_GetModState() & KMOD_SHIFT) != 0 + && result >= 0 && result < arrlen(shiftxform)) + { + result = shiftxform[result]; + } + + return result; + } + else + { + SDL_Event next_event; + + // Special cases, where we always return a fixed value. + switch (sym->sym) + { + case SDLK_BACKSPACE: return KEY_BACKSPACE; + case SDLK_RETURN: return KEY_ENTER; + default: + break; + } + + // The following is a gross hack, but I don't see an easier way + // of doing this within the SDL2 API (in SDL1 it was easier). + // We want to get the fully transformed input character associated + // with this keypress - correct keyboard layout, appropriately + // transformed by any modifier keys, etc. So peek ahead in the SDL + // event queue and see if the key press is immediately followed by + // an SDL_TEXTINPUT event. If it is, it's reasonable to assume the + // key press and the text input are connected. Technically the SDL + // API does not guarantee anything of the sort, but in practice this + // is what happens and I've verified it through manual inspect of + // the SDL source code. + // + // In an ideal world we'd split out ev_keydown into a separate + // ev_textinput event, as SDL2 has done. But this doesn't work + // (I experimented with the idea), because lots of Doom's code is + // based around different responders "eating" events to stop them + // being passed on to another responder. If code is listening for + // a text input, it cannot block the corresponding keydown events + // which can affect other responders. + // + // So we're stuck with this as a rather fragile alternative. + + if (SDL_PeepEvents(&next_event, 1, SDL_PEEKEVENT, + SDL_FIRSTEVENT, SDL_LASTEVENT) == 1 + && next_event.type == SDL_TEXTINPUT) + { + // If an SDL_TEXTINPUT event is found, we always assume it + // matches the key press. The input text must be a single + // ASCII character - if it isn't, it's possible the input + // char is a Unicode value instead; better to send a null + // character than the unshifted key. + if (strlen(next_event.text.text) == 1 + && (next_event.text.text[0] & 0x80) == 0) + { + return next_event.text.text[0]; + } + } + + // Failed to find anything :/ + return 0; + } +} + +void I_HandleKeyboardEvent(SDL_Event *sdlevent) +{ + // XXX: passing pointers to event for access after this function + // has terminated is undefined behaviour + event_t event; + + switch (sdlevent->type) + { + case SDL_KEYDOWN: + event.type = ev_keydown; + event.data1 = TranslateKey(&sdlevent->key.keysym); + event.data2 = GetLocalizedKey(&sdlevent->key.keysym); + event.data3 = GetTypedChar(&sdlevent->key.keysym); + + if (event.data1 != 0) + { + D_PostEvent(&event); + } + break; + + case SDL_KEYUP: + event.type = ev_keyup; + event.data1 = TranslateKey(&sdlevent->key.keysym); + + // data2/data3 are initialized to zero for ev_keyup. + // For ev_keydown it's the shifted Unicode character + // that was typed, but if something wants to detect + // key releases it should do so based on data1 + // (key ID), not the printable char. + + event.data2 = 0; + event.data3 = 0; + + if (event.data1 != 0) + { + D_PostEvent(&event); + } + break; + + default: + break; + } +} + +void I_StartTextInput(int x1, int y1, int x2, int y2) +{ + text_input_enabled = true; + + if (!vanilla_keyboard_mapping) + { + // SDL2-TODO: SDL_SetTextInputRect(...); + SDL_StartTextInput(); + } +} + +void I_StopTextInput(void) +{ + text_input_enabled = false; + + if (!vanilla_keyboard_mapping) + { + SDL_StopTextInput(); + } +} + +static void UpdateMouseButtonState(unsigned int button, boolean on) +{ + static event_t event; + + if (button < SDL_BUTTON_LEFT || button > MAX_MOUSE_BUTTONS) + { + return; + } + + // Note: button "0" is left, button "1" is right, + // button "2" is middle for Doom. This is different + // to how SDL sees things. + + switch (button) + { + case SDL_BUTTON_LEFT: + button = 0; + break; + + case SDL_BUTTON_RIGHT: + button = 1; + break; + + case SDL_BUTTON_MIDDLE: + button = 2; + break; + + default: + // SDL buttons are indexed from 1. + --button; + break; + } + + // Turn bit representing this button on or off. + + if (on) + { + mouse_button_state |= (1 << button); + } + else + { + mouse_button_state &= ~(1 << button); + } + + // Post an event with the new button state. + + event.type = ev_mouse; + event.data1 = mouse_button_state; + event.data2 = event.data3 = 0; + D_PostEvent(&event); +} + +static void MapMouseWheelToButtons(SDL_MouseWheelEvent *wheel) +{ + // SDL2 distinguishes button events from mouse wheel events. + // We want to treat the mouse wheel as two buttons, as per + // SDL1 + static event_t up, down; + int button; + + if (wheel->y <= 0) + { // scroll down + button = 4; + } + else + { // scroll up + button = 3; + } + + // post a button down event + mouse_button_state |= (1 << button); + down.type = ev_mouse; + down.data1 = mouse_button_state; + down.data2 = down.data3 = 0; + D_PostEvent(&down); + + // post a button up event + mouse_button_state &= ~(1 << button); + up.type = ev_mouse; + up.data1 = mouse_button_state; + up.data2 = up.data3 = 0; + D_PostEvent(&up); +} + +void I_HandleMouseEvent(SDL_Event *sdlevent) +{ + switch (sdlevent->type) + { + case SDL_MOUSEBUTTONDOWN: + UpdateMouseButtonState(sdlevent->button.button, true); + break; + + case SDL_MOUSEBUTTONUP: + UpdateMouseButtonState(sdlevent->button.button, false); + break; + + case SDL_MOUSEWHEEL: + MapMouseWheelToButtons(&(sdlevent->wheel)); + break; + + default: + break; + } +} + +static int AccelerateMouse(int val) +{ + if (val < 0) + return -AccelerateMouse(-val); + + if (val > mouse_threshold) + { + return (int)((val - mouse_threshold) * mouse_acceleration + mouse_threshold); + } + else + { + return val; + } +} + +// +// Read the change in mouse state to generate mouse motion events +// +// This is to combine all mouse movement for a tic into one mouse +// motion event. +void I_ReadMouse(void) +{ + int x, y; + event_t ev; + + SDL_GetRelativeMouseState(&x, &y); + + if (x != 0 || y != 0) + { + ev.type = ev_mouse; + ev.data1 = mouse_button_state; + ev.data2 = AccelerateMouse(x); + + if (!novert) + { + ev.data3 = -AccelerateMouse(y); + } + else + { + ev.data3 = 0; + } + + // XXX: undefined behaviour since event is scoped to + // this function + D_PostEvent(&ev); + } +} + +// Bind all variables controlling input options. +void I_BindInputVariables(void) +{ + M_BindFloatVariable("mouse_acceleration", &mouse_acceleration); + M_BindIntVariable("mouse_threshold", &mouse_threshold); + M_BindIntVariable("vanilla_keyboard_mapping", &vanilla_keyboard_mapping); + M_BindIntVariable("novert", &novert); +} diff --git a/client/src/i_input.h b/client/src/i_input.h new file mode 100644 index 0000000..5da6e5f --- /dev/null +++ b/client/src/i_input.h @@ -0,0 +1,40 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System-specific keyboard/mouse input. +// + + +#ifndef __I_INPUT__ +#define __I_INPUT__ + +#define MAX_MOUSE_BUTTONS 8 + +extern float mouse_acceleration; +extern int mouse_threshold; + +void I_BindInputVariables(void); +void I_ReadMouse(void); + +// I_StartTextInput begins text input, activating the on-screen keyboard +// (if one is used). The caller indicates that any entered text will be +// displayed in the rectangle given by the provided set of coordinates. +void I_StartTextInput(int x1, int y1, int x2, int y2); + +// I_StopTextInput finishes text input, deactivating the on-screen keyboard +// (if one is used). +void I_StopTextInput(void); + +#endif diff --git a/client/src/i_joystick.c b/client/src/i_joystick.c new file mode 100644 index 0000000..d5d9984 --- /dev/null +++ b/client/src/i_joystick.c @@ -0,0 +1,384 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// SDL Joystick code. +// + + +#include +#include + +#include "SDL2/SDL.h" +#include "SDL2/SDL_events.h" +#include "SDL2/SDL_joystick.h" +#include "d_event.h" +#include "doomtype.h" +#include "i_joystick.h" +#include "i_system.h" +#include "m_config.h" +#include "m_misc.h" + +// When an axis is within the dead zone, it is set to zero. +// This is 5% of the full range: + +#define DEAD_ZONE (32768 / 3) + +static SDL_Joystick *joystick = NULL; + +// Configuration variables: + +// Standard default.cfg Joystick enable/disable + +static int usejoystick = 0; + +// SDL GUID and index of the joystick to use. +static char *joystick_guid = ""; +static int joystick_index = -1; + +// Which joystick axis to use for horizontal movement, and whether to +// invert the direction: + +static int joystick_x_axis = 0; +static int joystick_x_invert = 0; + +// Which joystick axis to use for vertical movement, and whether to +// invert the direction: + +static int joystick_y_axis = 1; +static int joystick_y_invert = 0; + +// Which joystick axis to use for strafing? + +static int joystick_strafe_axis = -1; +static int joystick_strafe_invert = 0; + +// Virtual to physical button joystick button mapping. By default this +// is a straight mapping. +static int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +}; + +void I_ShutdownJoystick(void) +{ + if (joystick != NULL) + { + SDL_JoystickClose(joystick); + joystick = NULL; + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } +} + +static boolean IsValidAxis(int axis) +{ + int num_axes; + + if (axis < 0) + { + return true; + } + + if (IS_BUTTON_AXIS(axis)) + { + return true; + } + + if (IS_HAT_AXIS(axis)) + { + return HAT_AXIS_HAT(axis) < SDL_JoystickNumHats(joystick); + } + + num_axes = SDL_JoystickNumAxes(joystick); + + return axis < num_axes; +} + +static int DeviceIndex(void) +{ + SDL_JoystickGUID guid, dev_guid; + int i; + + guid = SDL_JoystickGetGUIDFromString(joystick_guid); + + // GUID identifies a class of device rather than a specific device. + // Check if joystick_index has the expected GUID, as this can act + // as a tie-breaker in case there are multiple identical devices. + if (joystick_index >= 0 && joystick_index < SDL_NumJoysticks()) + { + dev_guid = SDL_JoystickGetDeviceGUID(joystick_index); + if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID))) + { + return joystick_index; + } + } + + // Check all devices to look for one with the expected GUID. + for (i = 0; i < SDL_NumJoysticks(); ++i) + { + dev_guid = SDL_JoystickGetDeviceGUID(i); + if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID))) + { + printf("I_InitJoystick: Joystick moved to index %d.\n", i); + return i; + } + } + + // No joystick found with the expected GUID. + return -1; +} + +void I_InitJoystick(void) +{ + int index; + + if (!usejoystick || !strcmp(joystick_guid, "")) + { + return; + } + + if (SDL_Init(SDL_INIT_JOYSTICK) < 0) + { + return; + } + + index = DeviceIndex(); + + if (index < 0) + { + printf("I_InitJoystick: Couldn't find joystick with GUID \"%s\": " + "device not found or not connected?\n", + joystick_guid); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + return; + } + + // Open the joystick + + joystick = SDL_JoystickOpen(index); + + if (joystick == NULL) + { + printf("I_InitJoystick: Failed to open joystick #%i\n", index); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + return; + } + + if (!IsValidAxis(joystick_x_axis) + || !IsValidAxis(joystick_y_axis) + || !IsValidAxis(joystick_strafe_axis)) + { + printf("I_InitJoystick: Invalid joystick axis for configured joystick " + "(run joystick setup again)\n"); + + SDL_JoystickClose(joystick); + joystick = NULL; + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } + + SDL_JoystickEventState(SDL_ENABLE); + + // Initialized okay! + + printf("I_InitJoystick: %s\n", SDL_JoystickName(joystick)); + + I_AtExit(I_ShutdownJoystick, true); +} + +static boolean IsAxisButton(int physbutton) +{ + if (IS_BUTTON_AXIS(joystick_x_axis)) + { + if (physbutton == BUTTON_AXIS_NEG(joystick_x_axis) + || physbutton == BUTTON_AXIS_POS(joystick_x_axis)) + { + return true; + } + } + if (IS_BUTTON_AXIS(joystick_y_axis)) + { + if (physbutton == BUTTON_AXIS_NEG(joystick_y_axis) + || physbutton == BUTTON_AXIS_POS(joystick_y_axis)) + { + return true; + } + } + if (IS_BUTTON_AXIS(joystick_strafe_axis)) + { + if (physbutton == BUTTON_AXIS_NEG(joystick_strafe_axis) + || physbutton == BUTTON_AXIS_POS(joystick_strafe_axis)) + { + return true; + } + } + + return false; +} + +// Get the state of the given virtual button. + +static int ReadButtonState(int vbutton) +{ + int physbutton; + + // Map from virtual button to physical (SDL) button. + if (vbutton < NUM_VIRTUAL_BUTTONS) + { + physbutton = joystick_physical_buttons[vbutton]; + } + else + { + physbutton = vbutton; + } + + // Never read axis buttons as buttons. + if (IsAxisButton(physbutton)) + { + return 0; + } + + return SDL_JoystickGetButton(joystick, physbutton); +} + +// Get a bitmask of all currently-pressed buttons + +static int GetButtonsState(void) +{ + int i; + int result; + + result = 0; + + for (i = 0; i < 20; ++i) + { + if (ReadButtonState(i)) + { + result |= 1 << i; + } + } + + return result; +} + +// Read the state of an axis, inverting if necessary. + +static int GetAxisState(int axis, int invert) +{ + int result; + + // Axis -1 means disabled. + + if (axis < 0) + { + return 0; + } + + // Is this a button axis, or a hat axis? + // If so, we need to handle it specially. + + result = 0; + + if (IS_BUTTON_AXIS(axis)) + { + if (SDL_JoystickGetButton(joystick, BUTTON_AXIS_NEG(axis))) + { + result -= 32767; + } + if (SDL_JoystickGetButton(joystick, BUTTON_AXIS_POS(axis))) + { + result += 32767; + } + } + else if (IS_HAT_AXIS(axis)) + { + int direction = HAT_AXIS_DIRECTION(axis); + int hatval = SDL_JoystickGetHat(joystick, HAT_AXIS_HAT(axis)); + + if (direction == HAT_AXIS_HORIZONTAL) + { + if ((hatval & SDL_HAT_LEFT) != 0) + { + result -= 32767; + } + else if ((hatval & SDL_HAT_RIGHT) != 0) + { + result += 32767; + } + } + else if (direction == HAT_AXIS_VERTICAL) + { + if ((hatval & SDL_HAT_UP) != 0) + { + result -= 32767; + } + else if ((hatval & SDL_HAT_DOWN) != 0) + { + result += 32767; + } + } + } + else + { + result = SDL_JoystickGetAxis(joystick, axis); + + if (result < DEAD_ZONE && result > -DEAD_ZONE) + { + result = 0; + } + } + + if (invert) + { + result = -result; + } + + return result; +} + +void I_UpdateJoystick(void) +{ + if (joystick != NULL) + { + event_t ev; + + ev.type = ev_joystick; + ev.data1 = GetButtonsState(); + ev.data2 = GetAxisState(joystick_x_axis, joystick_x_invert); + ev.data3 = GetAxisState(joystick_y_axis, joystick_y_invert); + ev.data4 = GetAxisState(joystick_strafe_axis, joystick_strafe_invert); + + D_PostEvent(&ev); + } +} + +void I_BindJoystickVariables(void) +{ + int i; + + M_BindIntVariable("use_joystick", &usejoystick); + M_BindStringVariable("joystick_guid", &joystick_guid); + M_BindIntVariable("joystick_index", &joystick_index); + M_BindIntVariable("joystick_x_axis", &joystick_x_axis); + M_BindIntVariable("joystick_y_axis", &joystick_y_axis); + M_BindIntVariable("joystick_strafe_axis", &joystick_strafe_axis); + M_BindIntVariable("joystick_x_invert", &joystick_x_invert); + M_BindIntVariable("joystick_y_invert", &joystick_y_invert); + M_BindIntVariable("joystick_strafe_invert",&joystick_strafe_invert); + + for (i = 0; i < NUM_VIRTUAL_BUTTONS; ++i) + { + char name[32]; + M_snprintf(name, sizeof(name), "joystick_physical_button%i", i); + M_BindIntVariable(name, &joystick_physical_buttons[i]); + } +} + diff --git a/client/src/i_joystick.h b/client/src/i_joystick.h new file mode 100644 index 0000000..1c9744f --- /dev/null +++ b/client/src/i_joystick.h @@ -0,0 +1,70 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System-specific joystick interface. +// + + +#ifndef __I_JOYSTICK__ +#define __I_JOYSTICK__ + +// Number of "virtual" joystick buttons defined in configuration files. +// This needs to be at least as large as the number of different key +// bindings supported by the higher-level game code (joyb* variables). +#define NUM_VIRTUAL_BUTTONS 11 + +// If this bit is set in a configuration file axis value, the axis is +// not actually a joystick axis, but instead is a "button axis". This +// means that instead of reading an SDL joystick axis, we read the +// state of two buttons to get the axis value. This is needed for eg. +// the PS3 SIXAXIS controller, where the D-pad buttons register as +// buttons, not as two axes. +#define BUTTON_AXIS 0x10000 + +// Query whether a given axis value describes a button axis. +#define IS_BUTTON_AXIS(axis) ((axis) >= 0 && ((axis) & BUTTON_AXIS) != 0) + +// Get the individual buttons from a button axis value. +#define BUTTON_AXIS_NEG(axis) ((axis) & 0xff) +#define BUTTON_AXIS_POS(axis) (((axis) >> 8) & 0xff) + +// Create a button axis value from two button values. +#define CREATE_BUTTON_AXIS(neg, pos) (BUTTON_AXIS | (neg) | ((pos) << 8)) + +// If this bit is set in an axis value, the axis is not actually a +// joystick axis, but is a "hat" axis. This means that we read (one of) +// the hats on the joystick. +#define HAT_AXIS 0x20000 + +#define IS_HAT_AXIS(axis) ((axis) >= 0 && ((axis) & HAT_AXIS) != 0) + +// Get the hat number from a hat axis value. +#define HAT_AXIS_HAT(axis) ((axis) & 0xff) +// Which axis of the hat? (horizonal or vertical) +#define HAT_AXIS_DIRECTION(axis) (((axis) >> 8) & 0xff) + +#define CREATE_HAT_AXIS(hat, direction) \ + (HAT_AXIS | (hat) | ((direction) << 8)) + +#define HAT_AXIS_HORIZONTAL 1 +#define HAT_AXIS_VERTICAL 2 + +void I_InitJoystick(void); +void I_ShutdownJoystick(void); +void I_UpdateJoystick(void); + +void I_BindJoystickVariables(void); + +#endif /* #ifndef __I_JOYSTICK__ */ + diff --git a/client/src/i_main.c b/client/src/i_main.c new file mode 100644 index 0000000..a12cb48 --- /dev/null +++ b/client/src/i_main.c @@ -0,0 +1,37 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Main program, simply calls D_DoomMain high level loop. +// + +#include "d_main.h" +#include "m_argv.h" + +int main(int argc, char **argv) +{ + // save arguments + + myargc = argc; + myargv = argv; + + M_FindResponseFile(); + + // start doom + + D_DoomMain (); + + return 0; +} + diff --git a/client/src/i_sdlmusic.c b/client/src/i_sdlmusic.c new file mode 100644 index 0000000..36cc2a0 --- /dev/null +++ b/client/src/i_sdlmusic.c @@ -0,0 +1,1302 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System interface for music. +// + + +#include +#include +#include +#include +#include + +#include "SDL2/SDL.h" +#include "SDL2/SDL_audio.h" +#include "SDL2/SDL_mixer.h" +#include "SDL2/SDL_stdinc.h" +#include "doomtype.h" +#include "gusconf.h" +#include "i_sound.h" +#include "i_swap.h" +#include "i_system.h" +#include "m_argv.h" +#include "m_config.h" +#include "m_misc.h" +#include "memio.h" +#include "mus2mid.h" +#include "sha1.h" +#include "w_wad.h" +#include "z_zone.h" + +#define MAXMIDLENGTH (96 * 1024) +#define MID_HEADER_MAGIC "MThd" +#define MUS_HEADER_MAGIC "MUS\x1a" + +#define FLAC_HEADER "fLaC" +#define OGG_HEADER "OggS" + +// Looping Vorbis metadata tag names. These have been defined by ZDoom +// for specifying the start and end positions for looping music tracks +// in .ogg and .flac files. +// More information is here: http://zdoom.org/wiki/Audio_loop +#define LOOP_START_TAG "LOOP_START" +#define LOOP_END_TAG "LOOP_END" + +// FLAC metadata headers that we care about. +#define FLAC_STREAMINFO 0 +#define FLAC_VORBIS_COMMENT 4 + +// Ogg metadata headers that we care about. +#define OGG_ID_HEADER 1 +#define OGG_COMMENT_HEADER 3 + +// Structure for music substitution. +// We store a mapping based on SHA1 checksum -> filename of substitute music +// file to play, so that substitution occurs based on content rather than +// lump name. This has some inherent advantages: +// * Music for Plutonia (reused from Doom 1) works automatically. +// * If a PWAD replaces music, the replacement music is used rather than +// the substitute music for the IWAD. +// * If a PWAD reuses music from an IWAD (even from a different game), we get +// the high quality version of the music automatically (neat!) + +typedef struct +{ + sha1_digest_t hash; + char *filename; +} subst_music_t; + +// Structure containing parsed metadata read from a digital music track: +typedef struct +{ + boolean valid; + unsigned int samplerate_hz; + int start_time, end_time; +} file_metadata_t; + +static subst_music_t *subst_music = NULL; +static unsigned int subst_music_len = 0; + +static const char *subst_config_filenames[] = +{ + "doom1-music.cfg", + "doom2-music.cfg", + "tnt-music.cfg", + "heretic-music.cfg", + "hexen-music.cfg", + "strife-music.cfg", +}; + +static boolean music_initialized = false; + +// If this is true, this module initialized SDL sound and has the +// responsibility to shut it down + +static boolean sdl_was_initialized = false; + +static boolean musicpaused = false; +static int current_music_volume; + +char *music_pack_path = ""; +char *timidity_cfg_path = ""; + +static char *temp_timidity_cfg = NULL; + +// If true, we are playing a substitute digital track rather than in-WAD +// MIDI/MUS track, and file_metadata contains loop metadata. +static boolean playing_substitute = false; +static file_metadata_t file_metadata; + +// Position (in samples) that we have reached in the current track. +// This is updated by the TrackPositionCallback function. +static unsigned int current_track_pos; + +// Currently playing music track. +static Mix_Music *current_track_music = NULL; + +// If true, the currently playing track is being played on loop. +static boolean current_track_loop; + +// Given a time string (for LOOP_START/LOOP_END), parse it and return +// the time (in # samples since start of track) it represents. +static unsigned int ParseVorbisTime(unsigned int samplerate_hz, char *value) +{ + char *num_start, *p; + unsigned int result = 0; + char c; + + if (strchr(value, ':') == NULL) + { + return atoi(value); + } + + result = 0; + num_start = value; + + for (p = value; *p != '\0'; ++p) + { + if (*p == '.' || *p == ':') + { + c = *p; *p = '\0'; + result = result * 60 + atoi(num_start); + num_start = p + 1; + *p = c; + } + + if (*p == '.') + { + return result * samplerate_hz + + (unsigned int) (atof(p) * samplerate_hz); + } + } + + return (result * 60 + atoi(num_start)) * samplerate_hz; +} + +// Given a vorbis comment string (eg. "LOOP_START=12345"), set fields +// in the metadata structure as appropriate. +static void ParseVorbisComment(file_metadata_t *metadata, char *comment) +{ + char *eq, *key, *value; + + eq = strchr(comment, '='); + + if (eq == NULL) + { + return; + } + + key = comment; + *eq = '\0'; + value = eq + 1; + + if (!strcmp(key, LOOP_START_TAG)) + { + metadata->start_time = ParseVorbisTime(metadata->samplerate_hz, value); + } + else if (!strcmp(key, LOOP_END_TAG)) + { + metadata->end_time = ParseVorbisTime(metadata->samplerate_hz, value); + } +} + +// Parse a vorbis comments structure, reading from the given file. +static void ParseVorbisComments(file_metadata_t *metadata, FILE *fs) +{ + uint32_t buf; + unsigned int num_comments, i, comment_len; + char *comment; + + // We must have read the sample rate already from an earlier header. + if (metadata->samplerate_hz == 0) + { + return; + } + + // Skip the starting part we don't care about. + if (fread(&buf, 4, 1, fs) < 1) + { + return; + } + if (fseek(fs, LONG(buf), SEEK_CUR) != 0) + { + return; + } + + // Read count field for number of comments. + if (fread(&buf, 4, 1, fs) < 1) + { + return; + } + num_comments = LONG(buf); + + // Read each individual comment. + for (i = 0; i < num_comments; ++i) + { + // Read length of comment. + if (fread(&buf, 4, 1, fs) < 1) + { + return; + } + + comment_len = LONG(buf); + + // Read actual comment data into string buffer. + comment = calloc(1, comment_len + 1); + if (comment == NULL + || fread(comment, 1, comment_len, fs) < comment_len) + { + free(comment); + break; + } + + // Parse comment string. + ParseVorbisComment(metadata, comment); + free(comment); + } +} + +static void ParseFlacStreaminfo(file_metadata_t *metadata, FILE *fs) +{ + byte buf[34]; + + // Read block data. + if (fread(buf, sizeof(buf), 1, fs) < 1) + { + return; + } + + // We only care about sample rate and song length. + metadata->samplerate_hz = (buf[10] << 12) | (buf[11] << 4) + | (buf[12] >> 4); + // Song length is actually a 36 bit field, but 32 bits should be + // enough for everybody. + //metadata->song_length = (buf[14] << 24) | (buf[15] << 16) + // | (buf[16] << 8) | buf[17]; +} + +static void ParseFlacFile(file_metadata_t *metadata, FILE *fs) +{ + byte header[4]; + unsigned int block_type; + size_t block_len; + boolean last_block; + + for (;;) + { + long pos = -1; + + // Read METADATA_BLOCK_HEADER: + if (fread(header, 4, 1, fs) < 1) + { + return; + } + + block_type = header[0] & ~0x80; + last_block = (header[0] & 0x80) != 0; + block_len = (header[1] << 16) | (header[2] << 8) | header[3]; + + pos = ftell(fs); + if (pos < 0) + { + return; + } + + if (block_type == FLAC_STREAMINFO) + { + ParseFlacStreaminfo(metadata, fs); + } + else if (block_type == FLAC_VORBIS_COMMENT) + { + ParseVorbisComments(metadata, fs); + } + + if (last_block) + { + break; + } + + // Seek to start of next block. + if (fseek(fs, pos + block_len, SEEK_SET) != 0) + { + return; + } + } +} + +static void ParseOggIdHeader(file_metadata_t *metadata, FILE *fs) +{ + byte buf[21]; + + if (fread(buf, sizeof(buf), 1, fs) < 1) + { + return; + } + + metadata->samplerate_hz = (buf[8] << 24) | (buf[7] << 16) + | (buf[6] << 8) | buf[5]; +} + +static void ParseOggFile(file_metadata_t *metadata, FILE *fs) +{ + byte buf[7]; + unsigned int offset; + + // Scan through the start of the file looking for headers. They + // begin '[byte]vorbis' where the byte value indicates header type. + memset(buf, 0, sizeof(buf)); + + for (offset = 0; offset < 100 * 1024; ++offset) + { + // buf[] is used as a sliding window. Each iteration, we + // move the buffer one byte to the left and read an extra + // byte onto the end. + memmove(buf, buf + 1, sizeof(buf) - 1); + + if (fread(&buf[6], 1, 1, fs) < 1) + { + return; + } + + if (!memcmp(buf + 1, "vorbis", 6)) + { + switch (buf[0]) + { + case OGG_ID_HEADER: + ParseOggIdHeader(metadata, fs); + break; + case OGG_COMMENT_HEADER: + ParseVorbisComments(metadata, fs); + break; + default: + break; + } + } + } +} + +static void ReadLoopPoints(char *filename, file_metadata_t *metadata) +{ + FILE *fs; + char header[4]; + + metadata->valid = false; + metadata->samplerate_hz = 0; + metadata->start_time = 0; + metadata->end_time = -1; + + fs = fopen(filename, "r"); + + if (fs == NULL) + { + return; + } + + // Check for a recognized file format; use the first four bytes + // of the file. + + if (fread(header, 4, 1, fs) < 1) + { + fclose(fs); + return; + } + + if (memcmp(header, FLAC_HEADER, 4) == 0) + { + ParseFlacFile(metadata, fs); + } + else if (memcmp(header, OGG_HEADER, 4) == 0) + { + ParseOggFile(metadata, fs); + } + + fclose(fs); + + // Only valid if at the very least we read the sample rate. + metadata->valid = metadata->samplerate_hz > 0; + + // If start and end time are both zero, ignore the loop tags. + // This is consistent with other source ports. + if (metadata->start_time == 0 && metadata->end_time == 0) + { + metadata->valid = false; + } +} + +// Given a MUS lump, look up a substitute MUS file to play instead +// (or NULL to just use normal MIDI playback). + +static char *GetSubstituteMusicFile(void *data, size_t data_len) +{ + sha1_context_t context; + sha1_digest_t hash; + char *filename; + unsigned int i; + + // Don't bother doing a hash if we're never going to find anything. + if (subst_music_len == 0) + { + return NULL; + } + + SHA1_Init(&context); + SHA1_Update(&context, data, data_len); + SHA1_Final(hash, &context); + + // Look for a hash that matches. + // The substitute mapping list can (intentionally) contain multiple + // filename mappings for the same hash. This allows us to try + // different files and fall back if our first choice isn't found. + + filename = NULL; + + for (i = 0; i < subst_music_len; ++i) + { + if (memcmp(hash, subst_music[i].hash, sizeof(hash)) == 0) + { + filename = subst_music[i].filename; + + // If the file exists, then use this file in preference to + // any fallbacks. But we always return a filename if it's + // in the list, even if it's just so we can print an error + // message to the user saying it doesn't exist. + if (M_FileExists(filename)) + { + break; + } + } + } + + return filename; +} + +// Add a substitute music file to the lookup list. + +static void AddSubstituteMusic(subst_music_t *subst) +{ + ++subst_music_len; + subst_music = + realloc(subst_music, sizeof(subst_music_t) * subst_music_len); + memcpy(&subst_music[subst_music_len - 1], subst, sizeof(subst_music_t)); +} + +static int ParseHexDigit(char c) +{ + c = tolower(c); + + if (c >= '0' && c <= '9') + { + return c - '0'; + } + else if (c >= 'a' && c <= 'f') + { + return 10 + (c - 'a'); + } + else + { + return -1; + } +} + +static char *GetFullPath(char *base_filename, char *path) +{ + char *basedir, *result; + char *p; + + // Starting with directory separator means we have an absolute path, + // so just return it. + if (path[0] == DIR_SEPARATOR) + { + return M_StringDuplicate(path); + } + + // Paths in the substitute filenames can contain Unix-style / + // path separators, but we should convert this to the separator + // for the native platform. + path = M_StringReplace(path, "/", DIR_SEPARATOR_S); + + // Copy config filename and cut off the filename to just get the + // parent dir. + basedir = M_StringDuplicate(base_filename); + p = strrchr(basedir, DIR_SEPARATOR); + if (p != NULL) + { + p[1] = '\0'; + result = M_StringJoin(basedir, path, NULL); + } + else + { + result = M_StringDuplicate(path); + } + free(basedir); + free(path); + + return result; +} + +// Parse a line from substitute music configuration file; returns error +// message or NULL for no error. + +static char *ParseSubstituteLine(char *filename, char *line) +{ + subst_music_t subst; + char *p; + int hash_index; + + // Strip out comments if present. + p = strchr(line, '#'); + if (p != NULL) + { + while (p > line && isspace(*(p - 1))) + { + --p; + } + *p = '\0'; + } + + // Skip leading spaces. + for (p = line; *p != '\0' && isspace(*p); ++p); + + // Empty line? This includes comment lines now that comments have + // been stripped. + if (*p == '\0') + { + return NULL; + } + + // Read hash. + hash_index = 0; + while (*p != '\0' && *p != '=' && !isspace(*p)) + { + int d1, d2; + + d1 = ParseHexDigit(p[0]); + d2 = ParseHexDigit(p[1]); + + if (d1 < 0 || d2 < 0) + { + return "Invalid hex digit in SHA1 hash"; + } + else if (hash_index >= sizeof(sha1_digest_t)) + { + return "SHA1 hash too long"; + } + + subst.hash[hash_index] = (d1 << 4) | d2; + ++hash_index; + + p += 2; + } + + if (hash_index != sizeof(sha1_digest_t)) + { + return "SHA1 hash too short"; + } + + // Skip spaces. + for (; *p != '\0' && isspace(*p); ++p); + + if (*p != '=') + { + return "Expected '='"; + } + + ++p; + + // Skip spaces. + for (; *p != '\0' && isspace(*p); ++p); + + // We're now at the filename. Cut off trailing space characters. + while (strlen(p) > 0 && isspace(p[strlen(p) - 1])) + { + p[strlen(p) - 1] = '\0'; + } + + if (strlen(p) == 0) + { + return "No filename specified for music substitution"; + } + + // Expand full path and add to our database of substitutes. + subst.filename = GetFullPath(filename, p); + AddSubstituteMusic(&subst); + + return NULL; +} + +// Read a substitute music configuration file. + +static boolean ReadSubstituteConfig(char *filename) +{ + char line[128]; + FILE *fs; + char *error; + int linenum = 1; +// int old_subst_music_len; + + fs = fopen(filename, "r"); + + if (fs == NULL) + { + return false; + } + +// old_subst_music_len = subst_music_len; + + while (!feof(fs)) + { + M_StringCopy(line, "", sizeof(line)); + char* res = fgets(line, sizeof(line), fs); + (void) res; // Explicitly ignore result + + error = ParseSubstituteLine(filename, line); + + if (error != NULL) + { + fprintf(stderr, "%s:%i: Error: %s\n", filename, linenum, error); + } + + ++linenum; + } + + fclose(fs); + + return true; +} + +// Find substitute configs and try to load them. + +static void LoadSubstituteConfigs(void) +{ + char *musicdir; + char *path; + unsigned int i; + + // We can configure the path to music packs using the music_pack_path + // configuration variable. Otherwise we use the current directory, or + // $configdir/music to look for .cfg files. + if (strcmp(music_pack_path, "") != 0) + { + musicdir = M_StringJoin(music_pack_path, DIR_SEPARATOR_S, NULL); + } + else if (!strcmp(configdir, "")) + { + musicdir = M_StringDuplicate(""); + } + else + { + musicdir = M_StringJoin(configdir, "music", DIR_SEPARATOR_S, NULL); + } + + // Load all music packs. We always load all music substitution packs for + // all games. Why? Suppose we have a Doom PWAD that reuses some music from + // Heretic. If we have the Heretic music pack loaded, then we get an + // automatic substitution. + for (i = 0; i < arrlen(subst_config_filenames); ++i) + { + path = M_StringJoin(musicdir, subst_config_filenames[i], NULL); + ReadSubstituteConfig(path); + free(path); + } + + free(musicdir); + + if (subst_music_len > 0) + { + printf("Loaded %i music substitutions from config files.\n", + subst_music_len); + } +} + +// Returns true if the given lump number is a music lump that should +// be included in substitute configs. +// Identifying music lumps by name is not feasible; some games (eg. +// Heretic, Hexen) don't have a common naming pattern for music lumps. + +static boolean IsMusicLump(int lumpnum) +{ + byte *data; + boolean result; + + if (W_LumpLength(lumpnum) < 4) + { + return false; + } + + data = W_CacheLumpNum(lumpnum, PU_STATIC); + + result = memcmp(data, MUS_HEADER_MAGIC, 4) == 0 + || memcmp(data, MID_HEADER_MAGIC, 4) == 0; + + W_ReleaseLumpNum(lumpnum); + + return result; +} + +// Dump an example config file containing checksums for all MIDI music +// found in the WAD directory. + +static void DumpSubstituteConfig(char *filename) +{ + sha1_context_t context; + sha1_digest_t digest; + char name[9]; + byte *data; + FILE *fs; + unsigned int lumpnum; + size_t h; + + fs = fopen(filename, "w"); + + if (fs == NULL) + { + I_Error("Failed to open %s for writing", filename); + return; + } + + fprintf(fs, "# Example %s substitute MIDI file.\n\n", "Chocolate Doom"); + fprintf(fs, "# SHA1 hash = filename\n"); + + for (lumpnum = 0; lumpnum < numlumps; ++lumpnum) + { + strncpy(name, lumpinfo[lumpnum]->name, 8); + name[8] = '\0'; + + if (!IsMusicLump(lumpnum)) + { + continue; + } + + // Calculate hash. + data = W_CacheLumpNum(lumpnum, PU_STATIC); + SHA1_Init(&context); + SHA1_Update(&context, data, W_LumpLength(lumpnum)); + SHA1_Final(digest, &context); + W_ReleaseLumpNum(lumpnum); + + // Print line. + for (h = 0; h < sizeof(sha1_digest_t); ++h) + { + fprintf(fs, "%02x", digest[h]); + } + + fprintf(fs, " = %s.ogg\n", name); + } + + fprintf(fs, "\n"); + fclose(fs); + + printf("Substitute MIDI config file written to %s.\n", filename); + I_Quit(); +} + +// If the temp_timidity_cfg config variable is set, generate a "wrapper" +// config file for Timidity to point to the actual config file. This +// is needed to inject a "dir" command so that the patches are read +// relative to the actual config file. + +static boolean WriteWrapperTimidityConfig(char *write_path) +{ + char *p, *path; + FILE *fstream; + + if (!strcmp(timidity_cfg_path, "")) + { + return false; + } + + fstream = fopen(write_path, "w"); + + if (fstream == NULL) + { + return false; + } + + p = strrchr(timidity_cfg_path, DIR_SEPARATOR); + if (p != NULL) + { + path = M_StringDuplicate(timidity_cfg_path); + path[p - timidity_cfg_path] = '\0'; + fprintf(fstream, "dir %s\n", path); + free(path); + } + + fprintf(fstream, "source %s\n", timidity_cfg_path); + fclose(fstream); + + return true; +} + +void I_InitTimidityConfig(void) +{ + char *env_string; + boolean success; + + temp_timidity_cfg = M_TempFile("timidity.cfg"); + + if (snd_musicdevice == SNDDEVICE_GUS) + { + success = GUS_WriteConfig(temp_timidity_cfg); + } + else + { + success = WriteWrapperTimidityConfig(temp_timidity_cfg); + } + + // Set the TIMIDITY_CFG environment variable to point to the temporary + // config file. + + if (success) + { + env_string = M_StringJoin("TIMIDITY_CFG=", temp_timidity_cfg, NULL); + putenv(env_string); + } + else + { + free(temp_timidity_cfg); + temp_timidity_cfg = NULL; + } +} + +// Remove the temporary config file generated by I_InitTimidityConfig(). + +static void RemoveTimidityConfig(void) +{ + if (temp_timidity_cfg != NULL) + { + remove(temp_timidity_cfg); + free(temp_timidity_cfg); + } +} + +// Shutdown music + +static void I_SDL_ShutdownMusic(void) +{ + if (music_initialized) + { + Mix_HaltMusic(); + music_initialized = false; + + if (sdl_was_initialized) + { + Mix_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + sdl_was_initialized = false; + } + } +} + +static boolean SDLIsInitialized(void) +{ + int freq, channels; + Uint16 format; + + return Mix_QuerySpec(&freq, &format, &channels) != 0; +} + +// Callback function that is invoked to track current track position. +void TrackPositionCallback(int chan, void *stream, int len, void *udata) +{ + // Position is doubled up twice: for 16-bit samples and for stereo. + current_track_pos += len / 4; +} + +// Initialize music subsystem +static boolean I_SDL_InitMusic(void) +{ + int i; + + //! + // @arg + // + // Read all MIDI files from loaded WAD files, dump an example substitution + // music config file to the specified filename and quit. + // + + i = M_CheckParmWithArgs("-dumpsubstconfig", 1); + + if (i > 0) + { + DumpSubstituteConfig(myargv[i + 1]); + } + + // If SDL_mixer is not initialized, we have to initialize it + // and have the responsibility to shut it down later on. + + if (SDLIsInitialized()) + { + music_initialized = true; + } + else + { + if (SDL_Init(SDL_INIT_AUDIO) < 0) + { + fprintf(stderr, "Unable to set up sound.\n"); + } + else if (Mix_OpenAudio(snd_samplerate, AUDIO_S16SYS, 2, 1024) < 0) + { + fprintf(stderr, "Error initializing SDL_mixer: %s\n", + Mix_GetError()); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + } + else + { + SDL_PauseAudio(0); + + sdl_was_initialized = true; + music_initialized = true; + } + } + + // Once initialization is complete, the temporary Timidity config + // file can be removed. + + RemoveTimidityConfig(); + + // If snd_musiccmd is set, we need to call Mix_SetMusicCMD to + // configure an external music playback program. + + if (strlen(snd_musiccmd) > 0) + { + Mix_SetMusicCMD(snd_musiccmd); + } + + // Register an effect function to track the music position. + Mix_RegisterEffect(MIX_CHANNEL_POST, TrackPositionCallback, NULL, NULL); + + // If we're in GENMIDI mode, try to load sound packs. + if (snd_musicdevice == SNDDEVICE_GENMIDI) + { + LoadSubstituteConfigs(); + } + + return music_initialized; +} + +// +// SDL_mixer's native MIDI music playing does not pause properly. +// As a workaround, set the volume to 0 when paused. +// + +static void UpdateMusicVolume(void) +{ + int vol; + + if (musicpaused) + { + vol = 0; + } + else + { + vol = (current_music_volume * MIX_MAX_VOLUME) / 127; + } + + Mix_VolumeMusic(vol); +} + +// Set music volume (0 - 127) + +static void I_SDL_SetMusicVolume(int volume) +{ + // Internal state variable. + current_music_volume = volume; + + UpdateMusicVolume(); +} + +// Start playing a mid + +static void I_SDL_PlaySong(void *handle, boolean looping) +{ + int loops; + + if (!music_initialized) + { + return; + } + + if (handle == NULL) + { + return; + } + + current_track_music = (Mix_Music *) handle; + current_track_loop = looping; + + if (looping) + { + loops = -1; + } + else + { + loops = 1; + } + + // Don't loop when playing substitute music, as we do it + // ourselves instead. + if (playing_substitute && file_metadata.valid) + { + loops = 1; + SDL_LockAudio(); + current_track_pos = 0; // start of track + SDL_UnlockAudio(); + } + + Mix_PlayMusic(current_track_music, loops); +} + +static void I_SDL_PauseSong(void) +{ + if (!music_initialized) + { + return; + } + + musicpaused = true; + + UpdateMusicVolume(); +} + +static void I_SDL_ResumeSong(void) +{ + if (!music_initialized) + { + return; + } + + musicpaused = false; + + UpdateMusicVolume(); +} + +static void I_SDL_StopSong(void) +{ + if (!music_initialized) + { + return; + } + + Mix_HaltMusic(); + + playing_substitute = false; + current_track_music = NULL; +} + +static void I_SDL_UnRegisterSong(void *handle) +{ + Mix_Music *music = (Mix_Music *) handle; + + if (!music_initialized) + { + return; + } + + if (handle == NULL) + { + return; + } + + Mix_FreeMusic(music); +} + +// Determine whether memory block is a .mid file + +static boolean IsMid(byte *mem, int len) +{ + return len > 4 && !memcmp(mem, "MThd", 4); +} + +static boolean ConvertMus(byte *musdata, int len, char *filename) +{ + MEMFILE *instream; + MEMFILE *outstream; + void *outbuf; + size_t outbuf_len; + int result; + + instream = mem_fopen_read(musdata, len); + outstream = mem_fopen_write(); + + result = mus2mid(instream, outstream); + + if (result == 0) + { + mem_get_buf(outstream, &outbuf, &outbuf_len); + + M_WriteFile(filename, outbuf, outbuf_len); + } + + mem_fclose(instream); + mem_fclose(outstream); + + return result; +} + +static void *I_SDL_RegisterSong(void *data, int len) +{ + char *filename; + Mix_Music *music; + + if (!music_initialized) + { + return NULL; + } + + playing_substitute = false; + + // See if we're substituting this MUS for a high-quality replacement. + filename = GetSubstituteMusicFile(data, len); + + if (filename != NULL) + { + music = Mix_LoadMUS(filename); + + if (music == NULL) + { + // Fall through and play MIDI normally, but print an error + // message. + fprintf(stderr, "Failed to load substitute music file: %s: %s\n", + filename, Mix_GetError()); + } + else + { + // Read loop point metadata from the file so that we know where + // to loop the music. + playing_substitute = true; + ReadLoopPoints(filename, &file_metadata); + return music; + } + } + + // MUS files begin with "MUS" + // Reject anything which doesnt have this signature + + filename = M_TempFile("doom.mid"); + + if (IsMid(data, len) && len < MAXMIDLENGTH) + { + M_WriteFile(filename, data, len); + } + else + { + // Assume a MUS file and try to convert + + ConvertMus(data, len, filename); + } + + // Load the MIDI. In an ideal world we'd be using Mix_LoadMUS_RW() + // by now, but Mix_SetMusicCMD() only works with Mix_LoadMUS(), so + // we have to generate a temporary file. + + music = Mix_LoadMUS(filename); + if (music == NULL) + { + // Failed to load + fprintf(stderr, "Error loading midi: %s\n", Mix_GetError()); + } + + // Remove the temporary MIDI file; however, when using an external + // MIDI program we can't delete the file. Otherwise, the program + // won't find the file to play. This means we leave a mess on + // disk :( + + if (strlen(snd_musiccmd) == 0) + { + remove(filename); + } + + free(filename); + + return music; +} + +// Is the song playing? +static boolean I_SDL_MusicIsPlaying(void) +{ + if (!music_initialized) + { + return false; + } + + return Mix_PlayingMusic(); +} + +// Get position in substitute music track, in seconds since start of track. +static double GetMusicPosition(void) +{ + unsigned int music_pos; + int freq; + + Mix_QuerySpec(&freq, NULL, NULL); + + SDL_LockAudio(); + music_pos = current_track_pos; + SDL_UnlockAudio(); + + return (double) music_pos / freq; +} + +static void RestartCurrentTrack(void) +{ + double start = (double) file_metadata.start_time + / file_metadata.samplerate_hz; + + // If the track finished we need to restart it. + if (current_track_music != NULL) + { + Mix_PlayMusic(current_track_music, 1); + } + + Mix_SetMusicPosition(start); + SDL_LockAudio(); + current_track_pos = file_metadata.start_time; + SDL_UnlockAudio(); +} + +// Poll music position; if we have passed the loop point end position +// then we need to go back. +static void I_SDL_PollMusic(void) +{ + // When playing substitute tracks, loop tags only apply if we're playing + // a looping track. Tracks like the title screen music have the loop + // tags ignored. + if (current_track_loop && playing_substitute && file_metadata.valid) + { + double end = (double) file_metadata.end_time + / file_metadata.samplerate_hz; + + // If we have reached the loop end point then we have to take action. + if (file_metadata.end_time >= 0 && GetMusicPosition() >= end) + { + RestartCurrentTrack(); + } + + // Have we reached the actual end of track (not loop end)? + if (!Mix_PlayingMusic()) + { + RestartCurrentTrack(); + } + } +} + +static snddevice_t music_sdl_devices[] = +{ + SNDDEVICE_PAS, + SNDDEVICE_GUS, + SNDDEVICE_WAVEBLASTER, + SNDDEVICE_SOUNDCANVAS, + SNDDEVICE_GENMIDI, + SNDDEVICE_AWE32, +}; + +music_module_t music_sdl_module = +{ + music_sdl_devices, + arrlen(music_sdl_devices), + I_SDL_InitMusic, + I_SDL_ShutdownMusic, + I_SDL_SetMusicVolume, + I_SDL_PauseSong, + I_SDL_ResumeSong, + I_SDL_RegisterSong, + I_SDL_UnRegisterSong, + I_SDL_PlaySong, + I_SDL_StopSong, + I_SDL_MusicIsPlaying, + I_SDL_PollMusic, +}; + diff --git a/client/src/i_sdlsound.c b/client/src/i_sdlsound.c new file mode 100644 index 0000000..b21813e --- /dev/null +++ b/client/src/i_sdlsound.c @@ -0,0 +1,874 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2008 David Flater +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System interface for sound. +// + +#include +#include +#include +#include + +#include "SDL2/SDL.h" +#include "SDL2/SDL_audio.h" +#include "SDL2/SDL_mixer.h" +#include "SDL2/SDL_stdinc.h" +#include "doomtype.h" +#include "i_sound.h" +#include "i_system.h" +#include "m_misc.h" +#include "w_wad.h" +#include "z_zone.h" + +struct allocated_sound_s; + +#define NUM_CHANNELS 16 + +typedef struct allocated_sound_s allocated_sound_t; + +struct allocated_sound_s +{ + sfxinfo_t *sfxinfo; + Mix_Chunk chunk; + int use_count; + int pitch; + allocated_sound_t *prev, *next; +}; + +static boolean sound_initialized = false; + +static allocated_sound_t *channels_playing[NUM_CHANNELS]; + +static int mixer_freq; +static Uint16 mixer_format; +static int mixer_channels; +static boolean use_sfx_prefix; +static boolean (*ExpandSoundData)(sfxinfo_t *sfxinfo, + byte *data, + int samplerate, + int length) = NULL; + +// Doubly-linked list of allocated sounds. +// When a sound is played, it is moved to the head, so that the oldest +// sounds not used recently are at the tail. + +static allocated_sound_t *allocated_sounds_head = NULL; +static allocated_sound_t *allocated_sounds_tail = NULL; +static int allocated_sounds_size = 0; + +int use_libsamplerate = 0; + +// Scale factor used when converting libsamplerate floating point numbers +// to integers. Too high means the sounds can clip; too low means they +// will be too quiet. This is an amount that should avoid clipping most +// of the time: with all the Doom IWAD sound effects, at least. If a PWAD +// is used, clipping might occur. + +float libsamplerate_scale = 0.65f; + +// Hook a sound into the linked list at the head. + +static void AllocatedSoundLink(allocated_sound_t *snd) +{ + snd->prev = NULL; + + snd->next = allocated_sounds_head; + allocated_sounds_head = snd; + + if (allocated_sounds_tail == NULL) + { + allocated_sounds_tail = snd; + } + else + { + snd->next->prev = snd; + } +} + +// Unlink a sound from the linked list. + +static void AllocatedSoundUnlink(allocated_sound_t *snd) +{ + if (snd->prev == NULL) + { + allocated_sounds_head = snd->next; + } + else + { + snd->prev->next = snd->next; + } + + if (snd->next == NULL) + { + allocated_sounds_tail = snd->prev; + } + else + { + snd->next->prev = snd->prev; + } +} + +static void FreeAllocatedSound(allocated_sound_t *snd) +{ + // Unlink from linked list. + + AllocatedSoundUnlink(snd); + + // Keep track of the amount of allocated sound data: + + allocated_sounds_size -= snd->chunk.alen; + + free(snd); +} + +// Search from the tail backwards along the allocated sounds list, find +// and free a sound that is not in use, to free up memory. Return true +// for success. + +static boolean FindAndFreeSound(void) +{ + allocated_sound_t *snd; + + snd = allocated_sounds_tail; + + while (snd != NULL) + { + if (snd->use_count == 0) + { + FreeAllocatedSound(snd); + return true; + } + + snd = snd->prev; + } + + // No available sounds to free... + + return false; +} + +// Enforce SFX cache size limit. We are just about to allocate "len" +// bytes on the heap for a new sound effect, so free up some space +// so that we keep allocated_sounds_size < snd_cachesize + +static void ReserveCacheSpace(size_t len) +{ + if (snd_cachesize <= 0) + { + return; + } + + // Keep freeing sound effects that aren't currently being played, + // until there is enough space for the new sound. + + while (allocated_sounds_size + len > snd_cachesize) + { + // Free a sound. If there is nothing more to free, stop. + + if (!FindAndFreeSound()) + { + break; + } + } +} + +// Allocate a block for a new sound effect. + +static allocated_sound_t *AllocateSound(sfxinfo_t *sfxinfo, size_t len) +{ + allocated_sound_t *snd; + + // Keep allocated sounds within the cache size. + + ReserveCacheSpace(len); + + // Allocate the sound structure and data. The data will immediately + // follow the structure, which acts as a header. + + do + { + snd = malloc(sizeof(allocated_sound_t) + len); + + // Out of memory? Try to free an old sound, then loop round + // and try again. + + if (snd == NULL && !FindAndFreeSound()) + { + return NULL; + } + + } while (snd == NULL); + + // Skip past the chunk structure for the audio buffer + + snd->chunk.abuf = (byte *) (snd + 1); + snd->chunk.alen = len; + snd->chunk.allocated = 1; + snd->chunk.volume = MIX_MAX_VOLUME; + snd->pitch = NORM_PITCH; + + snd->sfxinfo = sfxinfo; + snd->use_count = 0; + + // Keep track of how much memory all these cached sounds are using... + + allocated_sounds_size += len; + + AllocatedSoundLink(snd); + + return snd; +} + +// Lock a sound, to indicate that it may not be freed. + +static void LockAllocatedSound(allocated_sound_t *snd) +{ + // Increase use count, to stop the sound being freed. + + ++snd->use_count; + + //printf("++ %s: Use count=%i\n", snd->sfxinfo->name, snd->use_count); + + // When we use a sound, re-link it into the list at the head, so + // that the oldest sounds fall to the end of the list for freeing. + + AllocatedSoundUnlink(snd); + AllocatedSoundLink(snd); +} + +// Unlock a sound to indicate that it may now be freed. + +static void UnlockAllocatedSound(allocated_sound_t *snd) +{ + if (snd->use_count <= 0) + { + I_Error("Sound effect released more times than it was locked..."); + } + + --snd->use_count; + + //printf("-- %s: Use count=%i\n", snd->sfxinfo->name, snd->use_count); +} + +// Search through the list of allocated sounds and return the one that matches +// the supplied sfxinfo entry and pitch level. + +static allocated_sound_t * GetAllocatedSoundBySfxInfoAndPitch(sfxinfo_t *sfxinfo, int pitch) +{ + allocated_sound_t * p = allocated_sounds_head; + + while (p != NULL) + { + if (p->sfxinfo == sfxinfo && p->pitch == pitch) + { + return p; + } + p = p->next; + } + + return NULL; +} + +// Allocate a new sound chunk and pitch-shift an existing sound up-or-down +// into it. + +static allocated_sound_t * PitchShift(allocated_sound_t *insnd, int pitch) +{ + allocated_sound_t * outsnd; + Sint16 *inp, *outp; + Sint16 *srcbuf, *dstbuf; + Uint32 srclen, dstlen; + + srcbuf = (Sint16 *)insnd->chunk.abuf; + srclen = insnd->chunk.alen; + + // determine ratio pitch:NORM_PITCH and apply to srclen, then invert. + // This is an approximation of vanilla behaviour based on measurements + dstlen = (int)((1 + (1 - (float)pitch / NORM_PITCH)) * srclen); + + // ensure that the new buffer is an even length + if ((dstlen % 2) == 0) + { + dstlen++; + } + + outsnd = AllocateSound(insnd->sfxinfo, dstlen); + + if (!outsnd) + { + return NULL; + } + + outsnd->pitch = pitch; + dstbuf = (Sint16 *)outsnd->chunk.abuf; + + // loop over output buffer. find corresponding input cell, copy over + for (outp = dstbuf; outp < dstbuf + dstlen/2; ++outp) + { + inp = srcbuf + (int)((float)(outp - dstbuf) / dstlen * srclen); + *outp = *inp; + } + + return outsnd; +} + +// When a sound stops, check if it is still playing. If it is not, +// we can mark the sound data as CACHE to be freed back for other +// means. + +static void ReleaseSoundOnChannel(int channel) +{ + allocated_sound_t *snd = channels_playing[channel]; + + Mix_HaltChannel(channel); + + if (snd == NULL) + { + return; + } + + channels_playing[channel] = NULL; + + UnlockAllocatedSound(snd); + + // if the sound is a pitch-shift and it's not in use, immediately + // free it + if (snd->pitch != NORM_PITCH && snd->use_count <= 0) + { + FreeAllocatedSound(snd); + } +} + +static boolean ConvertibleRatio(int freq1, int freq2) +{ + int ratio; + + if (freq1 > freq2) + { + return ConvertibleRatio(freq2, freq1); + } + else if ((freq2 % freq1) != 0) + { + // Not in a direct ratio + + return false; + } + else + { + // Check the ratio is a power of 2 + + ratio = freq2 / freq1; + + while ((ratio & 1) == 0) + { + ratio = ratio >> 1; + } + + return ratio == 1; + } +} + + +// Generic sound expansion function for any sample rate. +// Returns number of clipped samples (always 0). + +static boolean ExpandSoundData_SDL(sfxinfo_t *sfxinfo, + byte *data, + int samplerate, + int length) +{ + SDL_AudioCVT convertor; + allocated_sound_t *snd; + Mix_Chunk *chunk; + uint32_t expanded_length; + + // Calculate the length of the expanded version of the sample. + + expanded_length = (uint32_t) ((((uint64_t) length) * mixer_freq) / samplerate); + + // Double up twice: 8 -> 16 bit and mono -> stereo + + expanded_length *= 4; + + // Allocate a chunk in which to expand the sound + + snd = AllocateSound(sfxinfo, expanded_length); + + if (snd == NULL) + { + return false; + } + + chunk = &snd->chunk; + + // If we can, use the standard / optimized SDL conversion routines. + + if (samplerate <= mixer_freq + && ConvertibleRatio(samplerate, mixer_freq) + && SDL_BuildAudioCVT(&convertor, + AUDIO_U8, 1, samplerate, + mixer_format, mixer_channels, mixer_freq)) + { + convertor.buf = chunk->abuf; + convertor.len = length; + memcpy(convertor.buf, data, length); + + SDL_ConvertAudio(&convertor); + } + else + { + Sint16 *expanded = (Sint16 *) chunk->abuf; + int expanded_length; + int expand_ratio; + int i; + + // Generic expansion if conversion does not work: + // + // SDL's audio conversion only works for rate conversions that are + // powers of 2; if the two formats are not in a direct power of 2 + // ratio, do this naive conversion instead. + + // number of samples in the converted sound + + expanded_length = ((uint64_t) length * mixer_freq) / samplerate; + expand_ratio = (length << 8) / expanded_length; + + for (i=0; i> 8; + + sample = data[src] | (data[src] << 8); + sample -= 32768; + + // expand 8->16 bits, mono->stereo + + expanded[i * 2] = expanded[i * 2 + 1] = sample; + } + + // Perform a low-pass filter on the upscaled sound to filter + // out high-frequency noise from the conversion process. + + { + float rc, dt, alpha; + + // Low-pass filter for cutoff frequency f: + // + // For sampling rate r, dt = 1 / r + // rc = 1 / 2*pi*f + // alpha = dt / (rc + dt) + + // Filter to the half sample rate of the original sound effect + // (maximum frequency, by nyquist) + + dt = 1.0f / mixer_freq; + rc = 1.0f / (3.14f * samplerate); + alpha = dt / (rc + dt); + + // Both channels are processed in parallel, hence [i-2]: + + for (i=2; ilumpnum; + data = W_CacheLumpNum(lumpnum, PU_STATIC); + lumplen = W_LumpLength(lumpnum); + + // Check the header, and ensure this is a valid sound + + if (lumplen < 8 + || data[0] != 0x03 || data[1] != 0x00) + { + // Invalid sound + + return false; + } + + // 16 bit sample rate field, 32 bit length field + + samplerate = (data[3] << 8) | data[2]; + length = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4]; + + // If the header specifies that the length of the sound is greater than + // the length of the lump itself, this is an invalid sound lump + + // We also discard sound lumps that are less than 49 samples long, + // as this is how DMX behaves - although the actual cut-off length + // seems to vary slightly depending on the sample rate. This needs + // further investigation to better understand the correct + // behavior. + + if (length > lumplen - 8 || length <= 48) + { + return false; + } + + // The DMX sound library seems to skip the first 16 and last 16 + // bytes of the lump - reason unknown. + + data += 16; + length -= 32; + + // Sample rate conversion + + if (!ExpandSoundData(sfxinfo, data + 8, samplerate, length)) + { + return false; + } + + // don't need the original lump any more + + W_ReleaseLumpNum(lumpnum); + + return true; +} + +static void GetSfxLumpName(sfxinfo_t *sfx, char *buf, size_t buf_len) +{ + // Linked sfx lumps? Get the lump number for the sound linked to. + + if (sfx->link != NULL) + { + sfx = sfx->link; + } + + // Doom adds a DS* prefix to sound lumps; Heretic and Hexen don't + // do this. + + if (use_sfx_prefix) + { + M_snprintf(buf, buf_len, "ds%s", (sfx->name)); + } + else + { + M_StringCopy(buf, (sfx->name), buf_len); + } +} + + +static void I_SDL_PrecacheSounds(sfxinfo_t *sounds, int num_sounds) +{ + // no-op +} + +// Load a SFX chunk into memory and ensure that it is locked. + +static boolean LockSound(sfxinfo_t *sfxinfo) +{ + // If the sound isn't loaded, load it now + if (GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH) == NULL) + { + if (!CacheSFX(sfxinfo)) + { + return false; + } + } + + LockAllocatedSound(GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH)); + + return true; +} + +// +// Retrieve the raw data lump index +// for a given SFX name. +// + +static int I_SDL_GetSfxLumpNum(sfxinfo_t *sfx) +{ + char namebuf[9]; + + GetSfxLumpName(sfx, namebuf, sizeof(namebuf)); + + return W_GetNumForName(namebuf); +} + +static void I_SDL_UpdateSoundParams(int handle, int vol, int sep) +{ + int left, right; + + if (!sound_initialized || handle < 0 || handle >= NUM_CHANNELS) + { + return; + } + + left = ((254 - sep) * vol) / 127; + right = ((sep) * vol) / 127; + + if (left < 0) left = 0; + else if ( left > 255) left = 255; + if (right < 0) right = 0; + else if (right > 255) right = 255; + + Mix_SetPanning(handle, left, right); +} + +// +// Starting a sound means adding it +// to the current list of active sounds +// in the internal channels. +// As the SFX info struct contains +// e.g. a pointer to the raw data, +// it is ignored. +// As our sound handling does not handle +// priority, it is ignored. +// Pitching (that is, increased speed of playback) +// is set, but currently not used by mixing. +// + +static int I_SDL_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch) +{ + allocated_sound_t *snd; + + if (!sound_initialized || channel < 0 || channel >= NUM_CHANNELS) + { + return -1; + } + + // Release a sound effect if there is already one playing + // on this channel + + ReleaseSoundOnChannel(channel); + + // Get the sound data + + if (!LockSound(sfxinfo)) + { + return -1; + } + + snd = GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, pitch); + + if (snd == NULL) + { + allocated_sound_t *newsnd; + // fetch the base sound effect, un-pitch-shifted + snd = GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH); + + if (snd == NULL) + { + return -1; + } + + if (snd_pitchshift) + { + newsnd = PitchShift(snd, pitch); + + if (newsnd) + { + LockAllocatedSound(newsnd); + UnlockAllocatedSound(snd); + snd = newsnd; + } + } + } + else + { + LockAllocatedSound(snd); + } + + // play sound + + Mix_PlayChannel(channel, &snd->chunk, 0); + + channels_playing[channel] = snd; + + // set separation, etc. + + I_SDL_UpdateSoundParams(channel, vol, sep); + + return channel; +} + +static void I_SDL_StopSound(int handle) +{ + if (!sound_initialized || handle < 0 || handle >= NUM_CHANNELS) + { + return; + } + + // Sound data is no longer needed; release the + // sound data being used for this channel + + ReleaseSoundOnChannel(handle); +} + + +static boolean I_SDL_SoundIsPlaying(int handle) +{ + if (!sound_initialized || handle < 0 || handle >= NUM_CHANNELS) + { + return false; + } + + return Mix_Playing(handle); +} + +// +// Periodically called to update the sound system +// + +static void I_SDL_UpdateSound(void) +{ + int i; + + // Check all channels to see if a sound has finished + + for (i=0; i limit) + { + return (1 << n); + } + } + + // Should never happen? + + return 1024; +} + +static boolean I_SDL_InitSound(boolean _use_sfx_prefix) +{ + int i; + + use_sfx_prefix = _use_sfx_prefix; + + // No sounds yet + + for (i=0; i + +#include "doomtype.h" +#include "gusconf.h" +#include "i_sound.h" +#include "i_video.h" +#include "m_argv.h" +#include "m_config.h" + +// Sound sample rate to use for digital output (Hz) + +int snd_samplerate = 44100; + +// Maximum number of bytes to dedicate to allocated sound effects. +// (Default: 64MB) + +int snd_cachesize = 64 * 1024 * 1024; + +// Config variable that controls the sound buffer size. +// We default to 28ms (1000 / 35fps = 1 buffer per tic). + +int snd_maxslicetime_ms = 28; + +// External command to invoke to play back music. + +char *snd_musiccmd = ""; + +// Whether to vary the pitch of sound effects +// Each game will set the default differently + +int snd_pitchshift = -1; + +// Low-level sound and music modules we are using + +static sound_module_t *sound_module; +static music_module_t *music_module; + +int snd_musicdevice = SNDDEVICE_SB; +int snd_sfxdevice = SNDDEVICE_SB; + +// Sound modules + +extern void I_InitTimidityConfig(void); +extern sound_module_t sound_sdl_module; +extern music_module_t music_sdl_module; + +// For native music module: + +extern char *music_pack_path; +extern char *timidity_cfg_path; + +// Compiled-in sound modules: + +static sound_module_t *sound_modules[] = +{ + &sound_sdl_module, + NULL, +}; + +// Compiled-in music modules: + +static music_module_t *music_modules[] = +{ + &music_sdl_module, + NULL, +}; + +// Check if a sound device is in the given list of devices + +static boolean SndDeviceInList(snddevice_t device, snddevice_t *list, + int len) +{ + int i; + + for (i=0; isound_devices, + sound_modules[i]->num_sound_devices)) + { + // Initialize the module + + if (sound_modules[i]->Init(use_sfx_prefix)) + { + sound_module = sound_modules[i]; + return; + } + } + } +} + +// Initialize music according to snd_musicdevice. + +static void InitMusicModule(void) +{ + int i; + + music_module = NULL; + + for (i=0; music_modules[i] != NULL; ++i) + { + // Is the music device in the list of devices supported + // by this module? + printf("snd_musicdevice\n"); + printf("%d\n", snd_musicdevice); + + if (SndDeviceInList(snd_musicdevice, + music_modules[i]->sound_devices, + music_modules[i]->num_sound_devices)) + { + printf("nope!\n"); + // Initialize the module + + if (music_modules[i]->Init()) + { + music_module = music_modules[i]; + return; + } + } + } +} + +// +// Initializes sound stuff, including volume +// Sets channels, SFX and music volume, +// allocates channel buffer, sets S_sfx lookup. +// + +void I_InitSound(boolean use_sfx_prefix) +{ + boolean nosound, nosfx, nomusic; + + //! + // @vanilla + // + // Disable all sound output. + // + + nosound = M_CheckParm("-nosound") > 0; + + //! + // @vanilla + // + // Disable sound effects. + // + + nosfx = M_CheckParm("-nosfx") > 0; + + //! + // @vanilla + // + // Disable music. + // + + nomusic = M_CheckParm("-nomusic") > 0; + + // Initialize the sound and music subsystems. + + if (!nosound && !screensaver_mode) + { + // This is kind of a hack. If native MIDI is enabled, set up + // the TIMIDITY_CFG environment variable here before SDL_mixer + // is opened. + + if (!nomusic + && (snd_musicdevice == SNDDEVICE_GENMIDI + || snd_musicdevice == SNDDEVICE_GUS)) + { + I_InitTimidityConfig(); + } + + if (!nosfx) + { + InitSfxModule(use_sfx_prefix); + } + + if (!nomusic) + { + InitMusicModule(); + } + } +} + +void I_ShutdownSound(void) +{ + if (sound_module != NULL) + { + sound_module->Shutdown(); + } + + if (music_module != NULL) + { + music_module->Shutdown(); + } +} + +int I_GetSfxLumpNum(sfxinfo_t *sfxinfo) +{ + if (sound_module != NULL) + { + return sound_module->GetSfxLumpNum(sfxinfo); + } + else + { + return 0; + } +} + +void I_UpdateSound(void) +{ + if (sound_module != NULL) + { + sound_module->Update(); + } + + if (music_module != NULL && music_module->Poll != NULL) + { + music_module->Poll(); + } +} + +static void CheckVolumeSeparation(int *vol, int *sep) +{ + if (*sep < 0) + { + *sep = 0; + } + else if (*sep > 254) + { + *sep = 254; + } + + if (*vol < 0) + { + *vol = 0; + } + else if (*vol > 127) + { + *vol = 127; + } +} + +void I_UpdateSoundParams(int channel, int vol, int sep) +{ + if (sound_module != NULL) + { + CheckVolumeSeparation(&vol, &sep); + sound_module->UpdateSoundParams(channel, vol, sep); + } +} + +int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch) +{ + if (sound_module != NULL) + { + CheckVolumeSeparation(&vol, &sep); + return sound_module->StartSound(sfxinfo, channel, vol, sep, pitch); + } + else + { + return 0; + } +} + +void I_StopSound(int channel) +{ + if (sound_module != NULL) + { + sound_module->StopSound(channel); + } +} + +boolean I_SoundIsPlaying(int channel) +{ + if (sound_module != NULL) + { + return sound_module->SoundIsPlaying(channel); + } + else + { + return false; + } +} + +void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds) +{ + if (sound_module != NULL && sound_module->CacheSounds != NULL) + { + sound_module->CacheSounds(sounds, num_sounds); + } +} + +void I_InitMusic(void) +{ +} + +void I_ShutdownMusic(void) +{ + +} + +void I_SetMusicVolume(int volume) +{ + if (music_module != NULL) + { + music_module->SetMusicVolume(volume); + } +} + +void I_PauseSong(void) +{ + if (music_module != NULL) + { + music_module->PauseMusic(); + } +} + +void I_ResumeSong(void) +{ + if (music_module != NULL) + { + music_module->ResumeMusic(); + } +} + +void *I_RegisterSong(void *data, int len) +{ + if (music_module != NULL) + { + return music_module->RegisterSong(data, len); + } + else + { + return NULL; + } +} + +void I_UnRegisterSong(void *handle) +{ + if (music_module != NULL) + { + music_module->UnRegisterSong(handle); + } +} + +void I_PlaySong(void *handle, boolean looping) +{ + if (music_module != NULL) + { + music_module->PlaySong(handle, looping); + } +} + +void I_StopSong(void) +{ + if (music_module != NULL) + { + music_module->StopSong(); + } +} + +boolean I_MusicIsPlaying(void) +{ + if (music_module != NULL) + { + return music_module->MusicIsPlaying(); + } + else + { + return false; + } +} + +void I_BindSoundVariables(void) +{ + extern int use_libsamplerate; + extern float libsamplerate_scale; + + M_BindIntVariable("snd_musicdevice", &snd_musicdevice); + M_BindIntVariable("snd_sfxdevice", &snd_sfxdevice); + M_BindIntVariable("snd_maxslicetime_ms", &snd_maxslicetime_ms); + M_BindStringVariable("snd_musiccmd", &snd_musiccmd); + M_BindIntVariable("snd_samplerate", &snd_samplerate); + M_BindIntVariable("snd_cachesize", &snd_cachesize); + M_BindIntVariable("snd_pitchshift", &snd_pitchshift); + + M_BindStringVariable("music_pack_path", &music_pack_path); + M_BindStringVariable("timidity_cfg_path", &timidity_cfg_path); + M_BindStringVariable("gus_patch_path", &gus_patch_path); + M_BindIntVariable("gus_ram_kb", &gus_ram_kb); + + M_BindIntVariable("use_libsamplerate", &use_libsamplerate); + M_BindFloatVariable("libsamplerate_scale", &libsamplerate_scale); +} + diff --git a/client/src/i_sound.h b/client/src/i_sound.h new file mode 100644 index 0000000..2605414 --- /dev/null +++ b/client/src/i_sound.h @@ -0,0 +1,243 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// The not so system specific sound interface. +// + + +#ifndef __I_SOUND__ +#define __I_SOUND__ + +#include "doomtype.h" + +struct sfxinfo_struct; + +// so that the individual game logic and sound driver code agree +#define NORM_PITCH 127 + +// +// SoundFX struct. +// +typedef struct sfxinfo_struct sfxinfo_t; + +struct sfxinfo_struct +{ + // tag name, used for hexen. + char *tagname; + + // lump name. If we are running with use_sfx_prefix=true, a + // 'DS' (or 'DP' for PC speaker sounds) is prepended to this. + + char name[9]; + + // Sfx priority + int priority; + + // referenced sound if a link + sfxinfo_t *link; + + // pitch if a link (Doom), whether to pitch-shift (Hexen) + int pitch; + + // volume if a link + int volume; + + // this is checked every second to see if sound + // can be thrown out (if 0, then decrement, if -1, + // then throw out, if > 0, then it is in use) + int usefulness; + + // lump number of sfx + int lumpnum; + + // Maximum number of channels that the sound can be played on + // (Heretic) + int numchannels; + + // data used by the low level code + void *driver_data; +}; + +// +// MusicInfo struct. +// +typedef struct +{ + // up to 6-character name + char *name; + + // lump number of music + int lumpnum; + + // music data + void *data; + + // music handle once registered + void *handle; + +} musicinfo_t; + +typedef enum +{ + SNDDEVICE_NONE = 0, + SNDDEVICE_PCSPEAKER = 1, + SNDDEVICE_ADLIB = 2, + SNDDEVICE_SB = 3, + SNDDEVICE_PAS = 4, + SNDDEVICE_GUS = 5, + SNDDEVICE_WAVEBLASTER = 6, + SNDDEVICE_SOUNDCANVAS = 7, + SNDDEVICE_GENMIDI = 8, + SNDDEVICE_AWE32 = 9, + SNDDEVICE_CD = 10, +} snddevice_t; + +// Interface for sound modules + +typedef struct +{ + // List of sound devices that this sound module is used for. + + snddevice_t *sound_devices; + int num_sound_devices; + + // Initialise sound module + // Returns true if successfully initialised + + boolean (*Init)(boolean use_sfx_prefix); + + // Shutdown sound module + + void (*Shutdown)(void); + + // Returns the lump index of the given sound. + + int (*GetSfxLumpNum)(sfxinfo_t *sfxinfo); + + // Called periodically to update the subsystem. + + void (*Update)(void); + + // Update the sound settings on the given channel. + + void (*UpdateSoundParams)(int channel, int vol, int sep); + + // Start a sound on a given channel. Returns the channel id + // or -1 on failure. + + int (*StartSound)(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch); + + // Stop the sound playing on the given channel. + + void (*StopSound)(int channel); + + // Query if a sound is playing on the given channel + + boolean (*SoundIsPlaying)(int channel); + + // Called on startup to precache sound effects (if necessary) + + void (*CacheSounds)(sfxinfo_t *sounds, int num_sounds); + +} sound_module_t; + +void I_InitSound(boolean use_sfx_prefix); +void I_ShutdownSound(void); +int I_GetSfxLumpNum(sfxinfo_t *sfxinfo); +void I_UpdateSound(void); +void I_UpdateSoundParams(int channel, int vol, int sep); +int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch); +void I_StopSound(int channel); +boolean I_SoundIsPlaying(int channel); +void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds); + +// Interface for music modules + +typedef struct +{ + // List of sound devices that this music module is used for. + + snddevice_t *sound_devices; + int num_sound_devices; + + // Initialise the music subsystem + + boolean (*Init)(void); + + // Shutdown the music subsystem + + void (*Shutdown)(void); + + // Set music volume - range 0-127 + + void (*SetMusicVolume)(int volume); + + // Pause music + + void (*PauseMusic)(void); + + // Un-pause music + + void (*ResumeMusic)(void); + + // Register a song handle from data + // Returns a handle that can be used to play the song + + void *(*RegisterSong)(void *data, int len); + + // Un-register (free) song data + + void (*UnRegisterSong)(void *handle); + + // Play the song + + void (*PlaySong)(void *handle, boolean looping); + + // Stop playing the current song. + + void (*StopSong)(void); + + // Query if music is playing. + + boolean (*MusicIsPlaying)(void); + + // Invoked periodically to poll. + + void (*Poll)(void); +} music_module_t; + +void I_InitMusic(void); +void I_ShutdownMusic(void); +void I_SetMusicVolume(int volume); +void I_PauseSong(void); +void I_ResumeSong(void); +void *I_RegisterSong(void *data, int len); +void I_UnRegisterSong(void *handle); +void I_PlaySong(void *handle, boolean looping); +void I_StopSong(void); +boolean I_MusicIsPlaying(void); + +extern int snd_sfxdevice; +extern int snd_musicdevice; +extern int snd_samplerate; +extern int snd_cachesize; +extern int snd_maxslicetime_ms; +extern char *snd_musiccmd; +extern int snd_pitchshift; + +void I_BindSoundVariables(void); + +#endif + diff --git a/client/src/i_swap.h b/client/src/i_swap.h new file mode 100644 index 0000000..8208980 --- /dev/null +++ b/client/src/i_swap.h @@ -0,0 +1,43 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Endianess handling, swapping 16bit and 32bit. +// + + +#ifndef __I_SWAP__ +#define __I_SWAP__ + +#include "SDL2/SDL_endian.h" + +// Endianess handling. +// WAD files are stored little endian. + +// Just use SDL's endianness swapping functions. + +// These are deliberately cast to signed values; this is the behaviour +// of the macros in the original source and some code relies on it. + +#define SHORT(x) ((signed short) SDL_SwapLE16(x)) +#define LONG(x) ((signed int) SDL_SwapLE32(x)) + +// Defines for checking the endianness of the system. + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN +#define SYS_BIG_ENDIAN +#endif + +#endif + diff --git a/client/src/i_system.c b/client/src/i_system.c new file mode 100644 index 0000000..0cd2d21 --- /dev/null +++ b/client/src/i_system.c @@ -0,0 +1,409 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// + + + +#include +#include +#include +#include +#include + +#include "SDL2/SDL.h" +#include "SDL2/SDL_messagebox.h" +#include "doomtype.h" +#include "i_system.h" +#include "m_argv.h" +#include "m_misc.h" + +struct atexit_listentry_s; + +#define DEFAULT_RAM 16 /* MiB */ +#define MIN_RAM 4 /* MiB */ + + +typedef struct atexit_listentry_s atexit_listentry_t; + +struct atexit_listentry_s +{ + atexit_func_t func; + boolean run_on_error; + atexit_listentry_t *next; +}; + +static atexit_listentry_t *exit_funcs = NULL; + +void I_AtExit(atexit_func_t func, boolean run_on_error) +{ + atexit_listentry_t *entry; + + entry = malloc(sizeof(*entry)); + + entry->func = func; + entry->run_on_error = run_on_error; + entry->next = exit_funcs; + exit_funcs = entry; +} + +// Tactile feedback function, probably used for the Logitech Cyberman + +void I_Tactile(int on, int off, int total) +{ +} + +// Zone memory auto-allocation function that allocates the zone size +// by trying progressively smaller zone sizes until one is found that +// works. + +static byte *AutoAllocMemory(int *size, int default_ram, int min_ram) +{ + byte *zonemem; + + // Allocate the zone memory. This loop tries progressively smaller + // zone sizes until a size is found that can be allocated. + // If we used the -mb command line parameter, only the parameter + // provided is accepted. + + zonemem = NULL; + + while (zonemem == NULL) + { + // We need a reasonable minimum amount of RAM to start. + + if (default_ram < min_ram) + { + I_Error("Unable to allocate %i MiB of RAM for zone", default_ram); + } + + // Try to allocate the zone memory. + + *size = default_ram * 1024 * 1024; + + zonemem = malloc(*size); + + // Failed to allocate? Reduce zone size until we reach a size + // that is acceptable. + + if (zonemem == NULL) + { + default_ram -= 1; + } + } + + return zonemem; +} + +byte *I_ZoneBase (int *size) +{ + byte *zonemem; + int min_ram, default_ram; + int p; + + //! + // @arg + // + // Specify the heap size, in MiB (default 16). + // + + p = M_CheckParmWithArgs("-mb", 1); + + if (p > 0) + { + default_ram = atoi(myargv[p+1]); + min_ram = default_ram; + } + else + { + default_ram = DEFAULT_RAM; + min_ram = MIN_RAM; + } + + zonemem = AutoAllocMemory(size, default_ram, min_ram); + + printf("zone memory: %p, %x allocated for zone\n", + zonemem, *size); + + return zonemem; +} + +void I_PrintBanner(char *msg) +{ + int i; + int spaces = 35 - (strlen(msg) / 2); + + for (i=0; ifunc(); + entry = entry->next; + } + + SDL_Quit(); + + exit(0); +} + + + +// +// I_Error +// + +static boolean already_quitting = false; + +void I_Error (char *error, ...) +{ + char msgbuf[512]; + va_list argptr; + atexit_listentry_t *entry; + boolean exit_gui_popup; + + if (already_quitting) + { + fprintf(stderr, "Warning: recursive call to I_Error detected.\n"); + exit(-1); + } + else + { + already_quitting = true; + } + + // Message first. + va_start(argptr, error); + //fprintf(stderr, "\nError: "); + vfprintf(stderr, error, argptr); + fprintf(stderr, "\n\n"); + va_end(argptr); + fflush(stderr); + + // Write a copy of the message into buffer. + va_start(argptr, error); + memset(msgbuf, 0, sizeof(msgbuf)); + M_vsnprintf(msgbuf, sizeof(msgbuf), error, argptr); + va_end(argptr); + + // Shutdown. Here might be other errors. + + entry = exit_funcs; + + while (entry != NULL) + { + if (entry->run_on_error) + { + entry->func(); + } + + entry = entry->next; + } + + exit_gui_popup = !M_ParmExists("-nogui"); + + // Pop up a GUI dialog box to show the error message, if the + // game was not run from the console (and the user will + // therefore be unable to otherwise see the message). + if (exit_gui_popup && !I_ConsoleStdout()) + { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, + "Chocolate Doom 3", msgbuf, NULL); + } + + // abort(); + + SDL_Quit(); + + exit(-1); +} + +// +// Read Access Violation emulation. +// +// From PrBoom+, by entryway. +// + +// C:\>debug +// -d 0:0 +// +// DOS 6.22: +// 0000:0000 (57 92 19 00) F4 06 70 00-(16 00) +// DOS 7.1: +// 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00) +// Win98: +// 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00) +// DOSBox under XP: +// 0000:0000 (00 00 00 F1) ?? ?? ?? 00-(07 00) + +#define DOS_MEM_DUMP_SIZE 10 + +static const unsigned char mem_dump_dos622[DOS_MEM_DUMP_SIZE] = { + 0x57, 0x92, 0x19, 0x00, 0xF4, 0x06, 0x70, 0x00, 0x16, 0x00}; +static const unsigned char mem_dump_win98[DOS_MEM_DUMP_SIZE] = { + 0x9E, 0x0F, 0xC9, 0x00, 0x65, 0x04, 0x70, 0x00, 0x16, 0x00}; +static const unsigned char mem_dump_dosbox[DOS_MEM_DUMP_SIZE] = { + 0x00, 0x00, 0x00, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00}; +static unsigned char mem_dump_custom[DOS_MEM_DUMP_SIZE]; + +static const unsigned char *dos_mem_dump = mem_dump_dos622; + +boolean I_GetMemoryValue(unsigned int offset, void *value, int size) +{ + static boolean firsttime = true; + + if (firsttime) + { + int p, i, val; + + firsttime = false; + i = 0; + + //! + // @category compat + // @arg + // + // Specify DOS version to emulate for NULL pointer dereference + // emulation. Supported versions are: dos622, dos71, dosbox. + // The default is to emulate DOS 7.1 (Windows 98). + // + + p = M_CheckParmWithArgs("-setmem", 1); + + if (p > 0) + { + if (!strcasecmp(myargv[p + 1], "dos622")) + { + dos_mem_dump = mem_dump_dos622; + } + if (!strcasecmp(myargv[p + 1], "dos71")) + { + dos_mem_dump = mem_dump_win98; + } + else if (!strcasecmp(myargv[p + 1], "dosbox")) + { + dos_mem_dump = mem_dump_dosbox; + } + else + { + for (i = 0; i < DOS_MEM_DUMP_SIZE; ++i) + { + ++p; + + if (p >= myargc || myargv[p][0] == '-') + { + break; + } + + M_StrToInt(myargv[p], &val); + mem_dump_custom[i++] = (unsigned char) val; + } + + dos_mem_dump = mem_dump_custom; + } + } + } + + switch (size) + { + case 1: + *((unsigned char *) value) = dos_mem_dump[offset]; + return true; + case 2: + *((unsigned short *) value) = dos_mem_dump[offset] + | (dos_mem_dump[offset + 1] << 8); + return true; + case 4: + *((unsigned int *) value) = dos_mem_dump[offset] + | (dos_mem_dump[offset + 1] << 8) + | (dos_mem_dump[offset + 2] << 16) + | (dos_mem_dump[offset + 3] << 24); + return true; + } + + return false; +} + diff --git a/client/src/i_system.h b/client/src/i_system.h new file mode 100644 index 0000000..f0a1c29 --- /dev/null +++ b/client/src/i_system.h @@ -0,0 +1,84 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + + +#ifndef __I_SYSTEM__ +#define __I_SYSTEM__ + +#include "d_ticcmd.h" +#include "doomtype.h" + + +typedef void (*atexit_func_t)(void); + +// Called by DoomMain. +void I_Init (void); + +// Called by startup code +// to get the ammount of memory to malloc +// for the zone management. +byte* I_ZoneBase (int *size); + +boolean I_ConsoleStdout(void); + + +// Asynchronous interrupt functions should maintain private queues +// that are read by the synchronous functions +// to be converted into events. + +// Either returns a null ticcmd, +// or calls a loadable driver to build it. +// This ticcmd will then be modified by the gameloop +// for normal input. +ticcmd_t* I_BaseTiccmd (void); + + +// Called by M_Responder when quit is selected. +// Clean exit, displays sell blurb. +void I_Quit (void); + +void I_Error (char *error, ...); + +void I_Tactile (int on, int off, int total); + +boolean I_GetMemoryValue(unsigned int offset, void *value, int size); + +// Schedule a function to be called when the program exits. +// If run_if_error is true, the function is called if the exit +// is due to an error (I_Error) + +void I_AtExit(atexit_func_t func, boolean run_if_error); + +// Add all system-specific config file variable bindings. + +void I_BindVariables(void); + +// Print startup banner copyright message. + +void I_PrintStartupBanner(char *gamedescription); + +// Print a centered text banner displaying the given string. + +void I_PrintBanner(char *text); + +// Print a dividing line for startup banners. + +void I_PrintDivider(void); + +#endif + diff --git a/client/src/i_timer.c b/client/src/i_timer.c new file mode 100644 index 0000000..04dcbac --- /dev/null +++ b/client/src/i_timer.c @@ -0,0 +1,80 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Timer functions. +// + +#include "SDL2/SDL.h" +#include "SDL2/SDL_stdinc.h" +#include "SDL2/SDL_timer.h" +#include "i_timer.h" + +// +// I_GetTime +// returns time in 1/35th second tics +// + +static Uint32 basetime = 0; + +int I_GetTime (void) +{ + Uint32 ticks; + + ticks = SDL_GetTicks(); + + if (basetime == 0) + basetime = ticks; + + ticks -= basetime; + + return (ticks * TICRATE) / 1000; +} + +// +// Same as I_GetTime, but returns time in milliseconds +// + +int I_GetTimeMS(void) +{ + Uint32 ticks; + + ticks = SDL_GetTicks(); + + if (basetime == 0) + basetime = ticks; + + return ticks - basetime; +} + +// Sleep for a specified number of ms + +void I_Sleep(int ms) +{ + SDL_Delay(ms); +} + +void I_WaitVBL(int count) +{ + I_Sleep((count * 1000) / 70); +} + + +void I_InitTimer(void) +{ + // initialize timer + + SDL_Init(SDL_INIT_TIMER); +} + diff --git a/client/src/i_timer.h b/client/src/i_timer.h new file mode 100644 index 0000000..9b3dbb8 --- /dev/null +++ b/client/src/i_timer.h @@ -0,0 +1,42 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System-specific timer interface +// + + +#ifndef __I_TIMER__ +#define __I_TIMER__ + +#define TICRATE 35 + +// Called by D_DoomLoop, +// returns current time in tics. +int I_GetTime (void); + +// returns current time in ms +int I_GetTimeMS (void); + +// Pause for a specified number of ms +void I_Sleep(int ms); + +// Initialize timer +void I_InitTimer(void); + +// Wait for vertical retrace or pause a bit. +void I_WaitVBL(int count); + +#endif + diff --git a/client/src/i_video.c b/client/src/i_video.c new file mode 100644 index 0000000..15f4765 --- /dev/null +++ b/client/src/i_video.c @@ -0,0 +1,1469 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// DOOM graphics stuff for SDL. +// + + +#include +#include +#include +#include +#include + +#include "SDL2/SDL.h" +#include "SDL2/SDL_error.h" +#include "SDL2/SDL_events.h" +#include "SDL2/SDL_hints.h" +#include "SDL2/SDL_keyboard.h" +#include "SDL2/SDL_keycode.h" +#include "SDL2/SDL_mouse.h" +#include "SDL2/SDL_opengl.h" +#include "SDL2/SDL_pixels.h" +#include "SDL2/SDL_platform.h" +#include "SDL2/SDL_rect.h" +#include "SDL2/SDL_render.h" +#include "SDL2/SDL_scancode.h" +#include "SDL2/SDL_stdinc.h" +#include "SDL2/SDL_surface.h" +#include "SDL2/SDL_timer.h" +#include "SDL2/SDL_version.h" +#include "SDL2/SDL_video.h" +#include "d_event.h" +#include "d_loop.h" +#include "doomtype.h" +#include "i_input.h" +#include "i_joystick.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" +#include "m_argv.h" +#include "m_config.h" +#include "m_misc.h" +#include "tables.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +#include "streaming.h" + +// These are (1) the window (or the full screen) that our game is rendered to +// and (2) the renderer that scales the texture (see below) into this window. + +static SDL_Window *screen; +static SDL_Renderer *renderer; + +// Window title + +static char *window_title = ""; + +// These are (1) the 320x200x8 paletted buffer that we draw to (i.e. the one +// that holds I_VideoBuffer), (2) the 320x200x32 RGBA intermediate buffer that +// we blit the former buffer to, (3) the intermediate 320x200 texture that we +// load the RGBA buffer to and that we render into another texture (4) which +// is upscaled by an integer factor UPSCALE using "nearest" scaling and which +// in turn is finally rendered to screen using "linear" scaling. + +static SDL_Surface *screenbuffer = NULL; +static SDL_Surface *rgbabuffer = NULL; +static SDL_Texture *texture = NULL; +static SDL_Texture *texture_upscaled = NULL; + +static SDL_Rect blit_rect = { + 0, + 0, + SCREENWIDTH, + SCREENHEIGHT +}; + +static uint32_t pixel_format; + +// palette + +static SDL_Color palette[256]; +static boolean palette_to_set; + +// display has been set up? + +static boolean initialized = false; + +// disable mouse? + +static boolean nomouse = false; +int usemouse = 1; + +// Save screenshots in PNG format. + +int png_screenshots = 0; + +// SDL video driver name + +char *video_driver = ""; + +// Window position: + +char *window_position = "center"; + +// SDL display number on which to run. + +int video_display = 0; + +// Screen width and height, from configuration file. + +int window_width = SCREENWIDTH * 2; +int window_height = SCREENHEIGHT_4_3 * 2; + +// Fullscreen mode, 0x0 for SDL_WINDOW_FULLSCREEN_DESKTOP. + +int fullscreen_width = 0, fullscreen_height = 0; + +// Maximum number of pixels to use for intermediate scale buffer. + +static int max_scaling_buffer_pixels = 16000000; + +// Run in full screen mode? (int type for config code) + +int fullscreen = true; + +// Aspect ratio correction mode + +int aspect_ratio_correct = true; + +// Force integer scales for resolution-independent rendering + +int integer_scaling = false; + +// VGA Porch palette change emulation + +int vga_porch_flash = false; + +// Force software rendering, for systems which lack effective hardware +// acceleration + +int force_software_renderer = false; + +// Time to wait for the screen to settle on startup before starting the +// game (ms) + +static int startup_delay = 10; + +// Grab the mouse? (int type for config code). nograbmouse_override allows +// this to be temporarily disabled via the command line. + +static int grabmouse = true; +static boolean nograbmouse_override = false; + +// The screen buffer; this is modified to draw things to the screen + +pixel_t *I_VideoBuffer = NULL; + +// If true, game is running as a screensaver + +boolean screensaver_mode = false; + +// Flag indicating whether the screen is currently visible: +// when the screen isnt visible, don't render the screen + +boolean screenvisible = true; + +// If true, we display dots at the bottom of the screen to +// indicate FPS. + +static boolean display_fps_dots; + +// If this is true, the screen is rendered but not blitted to the +// video buffer. + +static boolean noblit; + +// Callback function to invoke to determine whether to grab the +// mouse pointer. + +static grabmouse_callback_t grabmouse_callback = NULL; + +// Does the window currently have focus? + +static boolean window_focused = true; + +// Window resize state. + +static boolean need_resize = false; +static unsigned int last_resize_time; +#define RESIZE_DELAY 500 + +// Gamma correction level to use + +int usegamma = 0; + +// Joystick/gamepad hysteresis +unsigned int joywait = 0; + +static boolean MouseShouldBeGrabbed() +{ + // never grab the mouse when in screensaver mode + + if (screensaver_mode) + return false; + + // if the window doesn't have focus, never grab it + + if (!window_focused) + return false; + + // always grab the mouse when full screen (dont want to + // see the mouse pointer) + + if (fullscreen) + return true; + + // Don't grab the mouse if mouse input is disabled + + if (!usemouse || nomouse) + return false; + + // if we specify not to grab the mouse, never grab + + if (nograbmouse_override || !grabmouse) + return false; + + // Invoke the grabmouse callback function to determine whether + // the mouse should be grabbed + + if (grabmouse_callback != NULL) + { + return grabmouse_callback(); + } + else + { + return true; + } +} + +void I_SetGrabMouseCallback(grabmouse_callback_t func) +{ + grabmouse_callback = func; +} + +// Set the variable controlling FPS dots. + +void I_DisplayFPSDots(boolean dots_on) +{ + display_fps_dots = dots_on; +} + +static void SetShowCursor(boolean show) +{ + if (!screensaver_mode) + { + // When the cursor is hidden, grab the input. + // Relative mode implicitly hides the cursor. + SDL_SetRelativeMouseMode(!show); + SDL_GetRelativeMouseState(NULL, NULL); + } +} + +void I_ShutdownGraphics(void) +{ + if (initialized) + { + SetShowCursor(true); + + SDL_QuitSubSystem(SDL_INIT_VIDEO); + + initialized = false; + } +} + + + +// +// I_StartFrame +// +void I_StartFrame (void) +{ + // er? + +} + +// Returns base screen height - either SCREENHEIGHT_4_3 or SCREENHEIGHT, +// dependent on aspect_ratio_correct value. +static int EffectiveScreenHeight(void) +{ + if (aspect_ratio_correct) + { + return SCREENHEIGHT_4_3; + } + else + { + return SCREENHEIGHT; + } +} + +// Adjust window_width / window_height variables to be an an aspect +// ratio consistent with the aspect_ratio_correct variable. +static void AdjustWindowSize(void) +{ + int h; + + h = EffectiveScreenHeight(); + + if (window_width * h <= window_height * SCREENWIDTH) + { + // We round up window_height if the ratio is not exact; this leaves + // the result stable. + window_height = (window_width * h + SCREENWIDTH - 1) / SCREENWIDTH; + } + else + { + window_width = window_height * SCREENWIDTH / h; + } +} + +static void HandleWindowEvent(SDL_WindowEvent *event) +{ + int i; + + switch (event->event) + { +#if 0 // SDL2-TODO + case SDL_ACTIVEEVENT: + // need to update our focus state + UpdateFocus(); + break; +#endif + case SDL_WINDOWEVENT_EXPOSED: + palette_to_set = true; + break; + + case SDL_WINDOWEVENT_RESIZED: + need_resize = true; + last_resize_time = SDL_GetTicks(); + break; + + // Don't render the screen when the window is minimized: + + case SDL_WINDOWEVENT_MINIMIZED: + screenvisible = false; + break; + + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + screenvisible = true; + break; + + // Update the value of window_focused when we get a focus event + // + // We try to make ourselves be well-behaved: the grab on the mouse + // is removed if we lose focus (such as a popup window appearing), + // and we dont move the mouse around if we aren't focused either. + + case SDL_WINDOWEVENT_FOCUS_GAINED: + window_focused = true; + break; + + case SDL_WINDOWEVENT_FOCUS_LOST: + window_focused = false; + break; + + // We want to save the user's preferred monitor to use for running the + // game, so that next time we're run we start on the same display. So + // every time the window is moved, find which display we're now on and + // update the video_display config variable. + + case SDL_WINDOWEVENT_MOVED: + i = SDL_GetWindowDisplayIndex(screen); + if (i >= 0) + { + video_display = i; + } + break; + + default: + break; + } +} + +static boolean ToggleFullScreenKeyShortcut(SDL_Keysym *sym) +{ + Uint16 flags = (KMOD_LALT | KMOD_RALT); +#if defined(__MACOSX__) + flags |= (KMOD_LGUI | KMOD_RGUI); +#endif + return sym->scancode == SDL_SCANCODE_RETURN && (sym->mod & flags) != 0; +} + +static void I_ToggleFullScreen(void) +{ + unsigned int flags = 0; + + // TODO: Consider implementing fullscreen toggle for SDL_WINDOW_FULLSCREEN + // (mode-changing) setup. This is hard because we have to shut down and + // restart again. + if (fullscreen_width != 0 || fullscreen_height != 0) + { + return; + } + + fullscreen = !fullscreen; + + if (fullscreen) + { + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + + SDL_SetWindowFullscreen(screen, flags); + + if (!fullscreen) + { + AdjustWindowSize(); + SDL_SetWindowSize(screen, window_width, window_height); + } +} + +void I_GetEvent(void) +{ + extern void I_HandleKeyboardEvent(SDL_Event *sdlevent); + extern void I_HandleMouseEvent(SDL_Event *sdlevent); + SDL_Event sdlevent; + + SDL_PumpEvents(); + + while (SDL_PollEvent(&sdlevent)) + { + switch (sdlevent.type) + { + case SDL_KEYDOWN: + if (ToggleFullScreenKeyShortcut(&sdlevent.key.keysym)) + { + I_ToggleFullScreen(); + break; + } + // deliberate fall-though + + case SDL_KEYUP: + I_HandleKeyboardEvent(&sdlevent); + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEWHEEL: + if (usemouse && !nomouse && window_focused) + { + I_HandleMouseEvent(&sdlevent); + } + break; + + case SDL_QUIT: + if (screensaver_mode) + { + I_Quit(); + } + else + { + event_t event; + event.type = ev_quit; + D_PostEvent(&event); + } + break; + + case SDL_WINDOWEVENT: + if (sdlevent.window.windowID == SDL_GetWindowID(screen)) + { + HandleWindowEvent(&sdlevent.window); + } + break; + + default: + break; + } + } +} + +// +// I_StartTic +// +void I_StartTic (void) +{ + if (!initialized) + { + return; + } + + I_GetEvent(); + + if (usemouse && !nomouse) + { + I_ReadMouse(); + } + + if (joywait < I_GetTime()) + { + I_UpdateJoystick(); + } +} + + +// +// I_UpdateNoBlit +// +void I_UpdateNoBlit (void) +{ + // what is this? +} + +static void UpdateGrab(void) +{ + static boolean currently_grabbed = false; + boolean grab; + + grab = MouseShouldBeGrabbed(); + + if (screensaver_mode) + { + // Hide the cursor in screensaver mode + + SetShowCursor(false); + } + else if (grab && !currently_grabbed) + { + SetShowCursor(false); + } + else if (!grab && currently_grabbed) + { + int screen_w, screen_h; + + SetShowCursor(true); + + // When releasing the mouse from grab, warp the mouse cursor to + // the bottom-right of the screen. This is a minimally distracting + // place for it to appear - we may only have released the grab + // because we're at an end of level intermission screen, for + // example. + + SDL_GetWindowSize(screen, &screen_w, &screen_h); + SDL_WarpMouseInWindow(screen, screen_w - 16, screen_h - 16); + SDL_GetRelativeMouseState(NULL, NULL); + } + + currently_grabbed = grab; +} + +static void LimitTextureSize(int *w_upscale, int *h_upscale) +{ + SDL_RendererInfo rinfo; + int orig_w, orig_h; + + orig_w = *w_upscale; + orig_h = *h_upscale; + + // Query renderer and limit to maximum texture dimensions of hardware: + if (SDL_GetRendererInfo(renderer, &rinfo) != 0) + { + I_Error("CreateUpscaledTexture: SDL_GetRendererInfo() call failed: %s", + SDL_GetError()); + } + + while (*w_upscale * SCREENWIDTH > rinfo.max_texture_width) + { + --*w_upscale; + } + while (*h_upscale * SCREENHEIGHT > rinfo.max_texture_height) + { + --*h_upscale; + } + + if ((*w_upscale < 1 && rinfo.max_texture_width > 0) || + (*h_upscale < 1 && rinfo.max_texture_height > 0)) + { + I_Error("CreateUpscaledTexture: Can't create a texture big enough for " + "the whole screen! Maximum texture size %dx%d", + rinfo.max_texture_width, rinfo.max_texture_height); + } + + // We limit the amount of texture memory used for the intermediate buffer. + // By default we limit to 1600x1200, which gives pretty good results, but + // we allow the user to override this and use more if they want to use + // even more (or less, if their graphics card can't handle it). + + if (max_scaling_buffer_pixels < SCREENWIDTH * SCREENHEIGHT) + { + I_Error("CreateUpscaledTexture: max_scaling_buffer_pixels too small " + "to create a texture buffer: %d < %d", + max_scaling_buffer_pixels, SCREENWIDTH * SCREENHEIGHT); + } + + while (*w_upscale * *h_upscale * SCREENWIDTH * SCREENHEIGHT + > max_scaling_buffer_pixels) + { + if (*w_upscale > *h_upscale) + { + --*w_upscale; + } + else + { + --*h_upscale; + } + } + + if (*w_upscale != orig_w || *h_upscale != orig_h) + { + printf("CreateUpscaledTexture: Limited texture size to %dx%d " + "(max %d pixels, max texture size %dx%d)\n", + *w_upscale * SCREENWIDTH, *h_upscale * SCREENHEIGHT, + max_scaling_buffer_pixels, + rinfo.max_texture_width, rinfo.max_texture_height); + } +} + +static void CreateUpscaledTexture(boolean force) +{ + const int actualheight = EffectiveScreenHeight(); + int w, h; + int h_upscale, w_upscale; + static int h_upscale_old, w_upscale_old; + + // Get the size of the renderer output. The units this gives us will be + // real world pixels, which are not necessarily equivalent to the screen's + // window size (because of highdpi). + if (SDL_GetRendererOutputSize(renderer, &w, &h) != 0) + { + I_Error("Failed to get renderer output size: %s", SDL_GetError()); + } + + // When the screen or window dimensions do not match the aspect ratio + // of the texture, the rendered area is scaled down to fit. Calculate + // the actual dimensions of the rendered area. + + if (w * actualheight < h * SCREENWIDTH) + { + // Tall window. + + h = w * actualheight / SCREENWIDTH; + } + else + { + // Wide window. + + w = h * SCREENWIDTH / actualheight; + } + + // Pick texture size the next integer multiple of the screen dimensions. + // If one screen dimension matches an integer multiple of the original + // resolution, there is no need to overscale in this direction. + + w_upscale = (w + SCREENWIDTH - 1) / SCREENWIDTH; + h_upscale = (h + SCREENHEIGHT - 1) / SCREENHEIGHT; + + // Minimum texture dimensions of 320x200. + + if (w_upscale < 1) + { + w_upscale = 1; + } + if (h_upscale < 1) + { + h_upscale = 1; + } + + LimitTextureSize(&w_upscale, &h_upscale); + + // Create a new texture only if the upscale factors have actually changed. + + if (h_upscale == h_upscale_old && w_upscale == w_upscale_old && !force) + { + return; + } + + h_upscale_old = h_upscale; + w_upscale_old = w_upscale; + + if (texture_upscaled) + { + SDL_DestroyTexture(texture_upscaled); + } + + // Set the scaling quality for rendering the upscaled texture to "linear", + // which looks much softer and smoother than "nearest" but does a better + // job at downscaling from the upscaled texture to screen. + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); + + texture_upscaled = SDL_CreateTexture(renderer, + pixel_format, + SDL_TEXTUREACCESS_TARGET, + w_upscale*SCREENWIDTH, + h_upscale*SCREENHEIGHT); +} + +// +// I_FinishUpdate +// +void I_FinishUpdate (void) +{ + static int lasttic; + int tics; + int i; + + if (!initialized) + return; + + if (noblit) + return; + + if (need_resize) + { + if (SDL_GetTicks() > last_resize_time + RESIZE_DELAY) + { + int flags; + // When the window is resized (we're not in fullscreen mode), + // save the new window size. + flags = SDL_GetWindowFlags(screen); + if ((flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) + { + SDL_GetWindowSize(screen, &window_width, &window_height); + + // Adjust the window by resizing again so that the window + // is the right aspect ratio. + AdjustWindowSize(); + SDL_SetWindowSize(screen, window_width, window_height); + } + CreateUpscaledTexture(false); + need_resize = false; + palette_to_set = true; + } + else + { + return; + } + } + + UpdateGrab(); + +#if 0 // SDL2-TODO + // Don't update the screen if the window isn't visible. + // Not doing this breaks under Windows when we alt-tab away + // while fullscreen. + + if (!(SDL_GetAppState() & SDL_APPACTIVE)) + return; +#endif + + // draws little dots on the bottom of the screen + + if (display_fps_dots) + { + i = I_GetTime(); + tics = i - lasttic; + lasttic = i; + if (tics > 20) tics = 20; + + for (i=0 ; iformat->palette, palette, 0, 256); + palette_to_set = false; + + if (vga_porch_flash) + { + // "flash" the pillars/letterboxes with palette changes, emulating + // VGA "porch" behaviour (GitHub issue #832) + SDL_SetRenderDrawColor(renderer, palette[0].r, palette[0].g, + palette[0].b, SDL_ALPHA_OPAQUE); + } + } + + // Blit from the paletted 8-bit screen buffer to the intermediate + // 32-bit RGBA buffer that we can load into the texture. + + SDL_LowerBlit(screenbuffer, &blit_rect, rgbabuffer, &blit_rect); + + // Update the intermediate texture with the contents of the RGBA buffer. + + SDL_UpdateTexture(texture, NULL, rgbabuffer->pixels, rgbabuffer->pitch); + + // Make sure the pillarboxes are kept clear each frame. + + SDL_RenderClear(renderer); + + // Render this intermediate texture into the upscaled texture + // using "nearest" integer scaling. + + SDL_SetRenderTarget(renderer, texture_upscaled); + SDL_RenderCopy(renderer, texture, NULL, NULL); + + // Finally, render this upscaled texture to screen using linear scaling. + + SDL_SetRenderTarget(renderer, NULL); + SDL_RenderCopy(renderer, texture_upscaled, NULL, NULL); + + // Draw! + + SDL_RenderPresent(renderer); +} + + +// +// I_ReadScreen +// +void I_ReadScreen (pixel_t* scr) +{ + memcpy(scr, I_VideoBuffer, SCREENWIDTH*SCREENHEIGHT*sizeof(*scr)); +} + + +// +// I_SetPalette +// +void I_SetPalette (byte *doompalette) +{ + int i; + + for (i=0; i<256; ++i) + { + // Zero out the bottom two bits of each channel - the PC VGA + // controller only supports 6 bits of accuracy. + + palette[i].r = gammatable[usegamma][*doompalette++] & ~3; + palette[i].g = gammatable[usegamma][*doompalette++] & ~3; + palette[i].b = gammatable[usegamma][*doompalette++] & ~3; + } + + palette_to_set = true; +} + +// Given an RGB value, find the closest matching palette index. + +int I_GetPaletteIndex(int r, int g, int b) +{ + int best, best_diff, diff; + int i; + + best = 0; best_diff = INT_MAX; + + for (i = 0; i < 256; ++i) + { + diff = (r - palette[i].r) * (r - palette[i].r) + + (g - palette[i].g) * (g - palette[i].g) + + (b - palette[i].b) * (b - palette[i].b); + + if (diff < best_diff) + { + best = i; + best_diff = diff; + } + + if (diff == 0) + { + break; + } + } + + return best; +} + +// +// Set the window title +// + +void I_SetWindowTitle(char *title) +{ + window_title = title; +} + +// +// Call the SDL function to set the window title, based on +// the title set with I_SetWindowTitle. +// + +void I_InitWindowTitle(void) +{ + char *buf; + + buf = M_StringJoin(window_title, " - ", "Chocolate Doom 3", NULL); + SDL_SetWindowTitle(screen, buf); + free(buf); +} + +// Set video size to a particular scale factor (1x, 2x, 3x, etc.) + +static void SetScaleFactor(int factor) +{ + // Pick 320x200 or 320x240, depending on aspect ratio correct + + window_width = factor * SCREENWIDTH; + window_height = factor * EffectiveScreenHeight(); + fullscreen = false; +} + +void I_GraphicsCheckCommandLine(void) +{ + int i; + + //! + // @category video + // @vanilla + // + // Disable blitting the screen. + // + + noblit = M_CheckParm ("-noblit"); + + //! + // @category video + // + // Don't grab the mouse when running in windowed mode. + // + + nograbmouse_override = M_ParmExists("-nograbmouse"); + + // default to fullscreen mode, allow override with command line + // nofullscreen because we love prboom + + //! + // @category video + // + // Run in a window. + // + + if (M_CheckParm("-window") || M_CheckParm("-nofullscreen")) + { + fullscreen = false; + } + + //! + // @category video + // + // Run in fullscreen mode. + // + + if (M_CheckParm("-fullscreen")) + { + fullscreen = true; + } + + //! + // @category video + // + // Disable the mouse. + // + + nomouse = M_CheckParm("-nomouse") > 0; + + //! + // @category video + // @arg + // + // Specify the screen width, in pixels. Implies -window. + // + + i = M_CheckParmWithArgs("-width", 1); + + if (i > 0) + { + window_width = atoi(myargv[i + 1]); + window_height = window_width * 2; + AdjustWindowSize(); + fullscreen = false; + } + + //! + // @category video + // @arg + // + // Specify the screen height, in pixels. Implies -window. + // + + i = M_CheckParmWithArgs("-height", 1); + + if (i > 0) + { + window_height = atoi(myargv[i + 1]); + window_width = window_height * 2; + AdjustWindowSize(); + fullscreen = false; + } + + //! + // @category video + // @arg + // + // Specify the dimensions of the window. Implies -window. + // + + i = M_CheckParmWithArgs("-geometry", 1); + + if (i > 0) + { + int w, h, s; + + s = sscanf(myargv[i + 1], "%ix%i", &w, &h); + if (s == 2) + { + window_width = w; + window_height = h; + fullscreen = false; + } + } + + //! + // @category video + // + // Don't scale up the screen. Implies -window. + // + + if (M_CheckParm("-1")) + { + SetScaleFactor(1); + } + + //! + // @category video + // + // Double up the screen to 2x its normal size. Implies -window. + // + + if (M_CheckParm("-2")) + { + SetScaleFactor(2); + } + + //! + // @category video + // + // Double up the screen to 3x its normal size. Implies -window. + // + + if (M_CheckParm("-3")) + { + SetScaleFactor(3); + } +} + +// Check if we have been invoked as a screensaver by xscreensaver. + +void I_CheckIsScreensaver(void) +{ + char *env; + + env = getenv("XSCREENSAVER_WINDOW"); + + if (env != NULL) + { + screensaver_mode = true; + } +} + +static void SetSDLVideoDriver(void) +{ + // Allow a default value for the SDL video driver to be specified + // in the configuration file. + + if (strcmp(video_driver, "") != 0) + { + char *env_string; + + env_string = M_StringJoin("SDL_VIDEODRIVER=", video_driver, NULL); + putenv(env_string); + free(env_string); + } +} + +// Check the display bounds of the display referred to by 'video_display' and +// set x and y to a location that places the window in the center of that +// display. +static void CenterWindow(int *x, int *y, int w, int h) +{ + SDL_Rect bounds; + + if (SDL_GetDisplayBounds(video_display, &bounds) < 0) + { + fprintf(stderr, "CenterWindow: Failed to read display bounds " + "for display #%d!\n", video_display); + return; + } + + *x = bounds.x + SDL_max((bounds.w - w) / 2, 0); + *y = bounds.y + SDL_max((bounds.h - h) / 2, 0); +} + +void I_GetWindowPosition(int *x, int *y, int w, int h) +{ + // Check that video_display corresponds to a display that really exists, + // and if it doesn't, reset it. + if (video_display < 0 || video_display >= SDL_GetNumVideoDisplays()) + { + fprintf(stderr, + "I_GetWindowPosition: We were configured to run on display #%d, " + "but it no longer exists (max %d). Moving to display 0.\n", + video_display, SDL_GetNumVideoDisplays() - 1); + video_display = 0; + } + + // in fullscreen mode, the window "position" still matters, because + // we use it to control which display we run fullscreen on. + + if (fullscreen) + { + CenterWindow(x, y, w, h); + return; + } + + // in windowed mode, the desired window position can be specified + // in the configuration file. + + if (window_position == NULL || !strcmp(window_position, "")) + { + *x = *y = SDL_WINDOWPOS_UNDEFINED; + } + else if (!strcmp(window_position, "center")) + { + // Note: SDL has a SDL_WINDOWPOS_CENTER, but this is useless for our + // purposes, since we also want to control which display we appear on. + // So we have to do this ourselves. + CenterWindow(x, y, w, h); + } + else if (sscanf(window_position, "%i,%i", x, y) != 2) + { + // invalid format: revert to default + fprintf(stderr, "I_GetWindowPosition: invalid window_position setting\n"); + *x = *y = SDL_WINDOWPOS_UNDEFINED; + } +} + +static void SetVideoMode(void) +{ + int w, h; + int x, y; + unsigned int rmask, gmask, bmask, amask; + int unused_bpp; + int window_flags = 0, renderer_flags = 0; + SDL_DisplayMode mode; + + w = window_width; + h = window_height; + + // In windowed mode, the window can be resized while the game is + // running. + window_flags = SDL_WINDOW_RESIZABLE; + + // Set the highdpi flag - this makes a big difference on Macs with + // retina displays, especially when using small window sizes. + window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; + + if (fullscreen) + { + if (fullscreen_width == 0 && fullscreen_height == 0) + { + // This window_flags means "Never change the screen resolution! + // Instead, draw to the entire screen by scaling the texture + // appropriately". + window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + else + { + w = fullscreen_width; + h = fullscreen_height; + window_flags |= SDL_WINDOW_FULLSCREEN; + } + } + + I_GetWindowPosition(&x, &y, w, h); + + // Create window and renderer contexts. We set the window title + // later anyway and leave the window position "undefined". If + // "window_flags" contains the fullscreen flag (see above), then + // w and h are ignored. + + if (screen == NULL) + { + screen = SDL_CreateWindow(NULL, x, y, w, h, window_flags); + + if (screen == NULL) + { + I_Error("Error creating window for video startup: %s", + SDL_GetError()); + } + + pixel_format = SDL_GetWindowPixelFormat(screen); + + SDL_SetWindowMinimumSize(screen, SCREENWIDTH, EffectiveScreenHeight()); + + I_InitWindowTitle(); + } + + // The SDL_RENDERER_TARGETTEXTURE flag is required to render the + // intermediate texture into the upscaled texture. + renderer_flags = SDL_RENDERER_TARGETTEXTURE; + + if (SDL_GetCurrentDisplayMode(video_display, &mode) != 0) + { + I_Error("Could not get display mode for video display #%d: %s", + video_display, SDL_GetError()); + } + + // Turn on vsync if we aren't in a -timedemo + if (!singletics && mode.refresh_rate > 0) + { + renderer_flags |= SDL_RENDERER_PRESENTVSYNC; + } + + if (force_software_renderer) + { + renderer_flags |= SDL_RENDERER_SOFTWARE; + } + + if (renderer != NULL) + { + SDL_DestroyRenderer(renderer); + } + + renderer = SDL_CreateRenderer(screen, -1, renderer_flags); + + if (renderer == NULL) + { + I_Error("Error creating renderer for screen window: %s", + SDL_GetError()); + } + + // Important: Set the "logical size" of the rendering context. At the same + // time this also defines the aspect ratio that is preserved while scaling + // and stretching the texture into the window. + + SDL_RenderSetLogicalSize(renderer, + SCREENWIDTH, + EffectiveScreenHeight()); + + // Force integer scales for resolution-independent rendering. + +#if SDL_VERSION_ATLEAST(2, 0, 5) + SDL_RenderSetIntegerScale(renderer, integer_scaling); +#endif + + // Blank out the full screen area in case there is any junk in + // the borders that won't otherwise be overwritten. + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); + + // Create the 8-bit paletted and the 32-bit RGBA screenbuffer surfaces. + + if (screenbuffer == NULL) + { + screenbuffer = SDL_CreateRGBSurface(0, + SCREENWIDTH, SCREENHEIGHT, 8, + 0, 0, 0, 0); + SDL_FillRect(screenbuffer, NULL, 0); + } + + // Format of rgbabuffer must match the screen pixel format because we + // import the surface data into the texture. + if (rgbabuffer == NULL) + { + SDL_PixelFormatEnumToMasks(pixel_format, &unused_bpp, + &rmask, &gmask, &bmask, &amask); + rgbabuffer = SDL_CreateRGBSurface(0, + SCREENWIDTH, SCREENHEIGHT, 32, + rmask, gmask, bmask, amask); + SDL_FillRect(rgbabuffer, NULL, 0); + } + + if (texture != NULL) + { + SDL_DestroyTexture(texture); + } + + // Set the scaling quality for rendering the intermediate texture into + // the upscaled texture to "nearest", which is gritty and pixelated and + // resembles software scaling pretty well. + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); + + // Create the intermediate texture that the RGBA surface gets loaded into. + // The SDL_TEXTUREACCESS_STREAMING flag means that this texture's content + // is going to change frequently. + + texture = SDL_CreateTexture(renderer, + pixel_format, + SDL_TEXTUREACCESS_STREAMING, + SCREENWIDTH, SCREENHEIGHT); + + // Initially create the upscaled texture for rendering to screen + + CreateUpscaledTexture(true); +} + +static const char *hw_emu_warning = +"===========================================================================\n" +"WARNING: it looks like you are using a software GL implementation.\n" +"To improve performance, try setting force_software_renderer in your\n" +"configuration file.\n" +"===========================================================================\n"; + +static void CheckGLVersion(void) +{ + const char * version; + typedef const GLubyte* (APIENTRY * glStringFn_t)(GLenum); + glStringFn_t glfp = (glStringFn_t)SDL_GL_GetProcAddress("glGetString"); + + if (glfp) + { + version = (const char *)glfp(GL_VERSION); + + if (version && strstr(version, "Mesa")) + { + printf("%s", hw_emu_warning); + } + } +} + +void I_InitGraphics(void) +{ + SDL_Event dummy; + byte *doompal; + char *env; + + // Pass through the XSCREENSAVER_WINDOW environment variable to + // SDL_WINDOWID, to embed the SDL window into the Xscreensaver + // window. + + env = getenv("XSCREENSAVER_WINDOW"); + + if (env != NULL) + { + char winenv[30]; + int winid; + + sscanf(env, "0x%x", &winid); + M_snprintf(winenv, sizeof(winenv), "SDL_WINDOWID=%i", winid); + + putenv(winenv); + } + + SetSDLVideoDriver(); + + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { + I_Error("Failed to initialize video: %s", SDL_GetError()); + } + + // When in screensaver mode, run full screen and auto detect + // screen dimensions (don't change video mode) + if (screensaver_mode) + { + fullscreen = true; + } + + // Create the game window; this may switch graphic modes depending + // on configuration. + AdjustWindowSize(); + SetVideoMode(); + + // We might have poor performance if we are using an emulated + // HW accelerator. Check for Mesa and warn if we're using it. + CheckGLVersion(); + + // Start with a clear black screen + // (screen will be flipped after we set the palette) + + SDL_FillRect(screenbuffer, NULL, 0); + + // Set the palette + + doompal = W_CacheLumpName(("PLAYPAL"), PU_CACHE); + I_SetPalette(doompal); + SDL_SetPaletteColors(screenbuffer->format->palette, palette, 0, 256); + + // SDL2-TODO UpdateFocus(); + UpdateGrab(); + + // On some systems, it takes a second or so for the screen to settle + // after changing modes. We include the option to add a delay when + // setting the screen mode, so that the game doesn't start immediately + // with the player unable to see anything. + + if (fullscreen && !screensaver_mode) + { + SDL_Delay(startup_delay); + } + + // The actual 320x200 canvas that we draw to. This is the pixel buffer of + // the 8-bit paletted screen buffer that gets blit on an intermediate + // 32-bit RGBA screen buffer that gets loaded into a texture that gets + // finally rendered into our window or full screen in I_FinishUpdate(). + + I_VideoBuffer = screenbuffer->pixels; + V_RestoreBuffer(); + + // Clear the screen to black. + + memset(I_VideoBuffer, 0, SCREENWIDTH * SCREENHEIGHT); + + // clear out any events waiting at the start and center the mouse + + while (SDL_PollEvent(&dummy)); + + initialized = true; + + // Call I_ShutdownGraphics on quit + + I_AtExit(I_ShutdownGraphics, true); +} + +// Bind all variables controlling video options into the configuration +// file system. +void I_BindVideoVariables(void) +{ + M_BindIntVariable("use_mouse", &usemouse); + M_BindIntVariable("fullscreen", &fullscreen); + M_BindIntVariable("video_display", &video_display); + M_BindIntVariable("aspect_ratio_correct", &aspect_ratio_correct); + M_BindIntVariable("integer_scaling", &integer_scaling); + M_BindIntVariable("vga_porch_flash", &vga_porch_flash); + M_BindIntVariable("startup_delay", &startup_delay); + M_BindIntVariable("fullscreen_width", &fullscreen_width); + M_BindIntVariable("fullscreen_height", &fullscreen_height); + M_BindIntVariable("force_software_renderer", &force_software_renderer); + M_BindIntVariable("max_scaling_buffer_pixels", &max_scaling_buffer_pixels); + M_BindIntVariable("window_width", &window_width); + M_BindIntVariable("window_height", &window_height); + M_BindIntVariable("grabmouse", &grabmouse); + M_BindStringVariable("video_driver", &video_driver); + M_BindStringVariable("window_position", &window_position); + M_BindIntVariable("usegamma", &usegamma); + M_BindIntVariable("png_screenshots", &png_screenshots); +} diff --git a/client/src/i_video.h b/client/src/i_video.h new file mode 100644 index 0000000..6e5c2e0 --- /dev/null +++ b/client/src/i_video.h @@ -0,0 +1,103 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + + +#ifndef __I_VIDEO__ +#define __I_VIDEO__ + +#include "doomtype.h" + +// Screen width and height. + +#define SCREENWIDTH 320 +#define SCREENHEIGHT 200 + +// Screen height used when aspect_ratio_correct=true. + +#define SCREENHEIGHT_4_3 240 + +typedef boolean (*grabmouse_callback_t)(void); + +// Called by D_DoomMain, +// determines the hardware configuration +// and sets up the video mode +void I_InitGraphics (void); + +void I_GraphicsCheckCommandLine(void); + +void I_ShutdownGraphics(void); + +// Takes full 8 bit values. +void I_SetPalette (byte* palette); +int I_GetPaletteIndex(int r, int g, int b); + +void I_UpdateNoBlit (void); +void I_FinishUpdate (void); + +void I_ReadScreen (pixel_t* scr); + +void I_BeginRead (void); + +void I_SetWindowTitle(char *title); + +void I_CheckIsScreensaver(void); +void I_SetGrabMouseCallback(grabmouse_callback_t func); + +void I_DisplayFPSDots(boolean dots_on); +void I_BindVideoVariables(void); + +void I_InitWindowTitle(void); +void I_InitWindowIcon(void); + +// Called before processing any tics in a frame (just after displaying a frame). +// Time consuming syncronous operations are performed here (joystick reading). + +void I_StartFrame (void); + +// Called before processing each tic in a frame. +// Quick syncronous operations are performed here. + +void I_StartTic (void); + +// Enable the loading disk image displayed when reading from disk. + +void I_EnableLoadingDisk(int xoffs, int yoffs); + +extern char *video_driver; +extern boolean screenvisible; + +extern int vanilla_keyboard_mapping; +extern boolean screensaver_mode; +extern int usegamma; +extern pixel_t *I_VideoBuffer; + +extern int screen_width; +extern int screen_height; +extern int fullscreen; +extern int aspect_ratio_correct; +extern int integer_scaling; +extern int vga_porch_flash; +extern int force_software_renderer; + +extern char *window_position; +void I_GetWindowPosition(int *x, int *y, int w, int h); + +// Joystic/gamepad hysteresis +extern unsigned int joywait; + +#endif diff --git a/client/src/i_videohr.c b/client/src/i_videohr.c new file mode 100644 index 0000000..4f9827e --- /dev/null +++ b/client/src/i_videohr.c @@ -0,0 +1,264 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// SDL emulation of VGA 640x480x4 planar video mode, +// for Hexen startup loading screen. +// + +#include + +#include "SDL2/SDL.h" +#include "SDL2/SDL_events.h" +#include "SDL2/SDL_keyboard.h" +#include "SDL2/SDL_keycode.h" +#include "SDL2/SDL_pixels.h" +#include "SDL2/SDL_rect.h" +#include "SDL2/SDL_surface.h" +#include "SDL2/SDL_video.h" +#include "doomtype.h" +#include "i_timer.h" +#include "i_video.h" + +// Palette fade-in takes two seconds + +#define FADE_TIME 2000 + +#define HR_SCREENWIDTH 640 +#define HR_SCREENHEIGHT 480 + +static SDL_Window *hr_screen = NULL; +static SDL_Surface *hr_surface = NULL; +static char *window_title = ""; + +boolean I_SetVideoModeHR(void) +{ + int x, y; + + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { + return false; + } + + I_GetWindowPosition(&x, &y, HR_SCREENWIDTH, HR_SCREENHEIGHT); + + // Create screen surface at the native desktop pixel depth (bpp=0), + // as we cannot trust true 8-bit to reliably work nowadays. + hr_screen = SDL_CreateWindow(window_title, x, y, + HR_SCREENWIDTH, HR_SCREENHEIGHT, + 0); + + if (hr_screen == NULL) + { + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return false; + } + + // We do all actual drawing into an intermediate surface. + hr_surface = SDL_CreateRGBSurface(0, HR_SCREENWIDTH, HR_SCREENHEIGHT, + 8, 0, 0, 0, 0); + + return true; +} + +void I_SetWindowTitleHR(char *title) +{ + window_title = title; +} + +void I_UnsetVideoModeHR(void) +{ + if (hr_screen != NULL) + { + SDL_QuitSubSystem(SDL_INIT_VIDEO); + hr_screen = NULL; + SDL_FreeSurface(hr_surface); + hr_surface = NULL; + } +} + +void I_ClearScreenHR(void) +{ + SDL_Rect area = { 0, 0, HR_SCREENWIDTH, HR_SCREENHEIGHT }; + + SDL_FillRect(hr_surface, &area, 0); +} + +void I_SlamBlockHR(int x, int y, int w, int h, const byte *src) +{ + SDL_Rect blit_rect; + const byte *srcptrs[4]; + byte srcbits[4]; + byte *dest; + int x1, y1; + int i; + int bit; + + // Set up source pointers to read from source buffer - each 4-bit + // pixel has its bits split into four sub-buffers + + for (i=0; i<4; ++i) + { + srcptrs[i] = src + (i * w * h / 8); + } + + if (SDL_LockSurface(hr_surface) < 0) + { + return; + } + + // Draw each pixel + + bit = 0; + + for (y1=y; y1pixels) + y1 * hr_surface->pitch + x; + + for (x1=x; x1> (7 - (bit % 8))) & 0x1; + } + + // Reassemble the pixel value + + *dest = (srcbits[0] << 0) + | (srcbits[1] << 1) + | (srcbits[2] << 2) + | (srcbits[3] << 3); + + // Next pixel! + + ++dest; + ++bit; + } + } + + SDL_UnlockSurface(hr_surface); + + // Update the region we drew. + blit_rect.x = x; + blit_rect.y = y; + blit_rect.w = w; + blit_rect.h = h; + SDL_BlitSurface(hr_surface, &blit_rect, + SDL_GetWindowSurface(hr_screen), &blit_rect); + SDL_UpdateWindowSurfaceRects(hr_screen, &blit_rect, 1); +} + +void I_SlamHR(const byte *buffer) +{ + I_SlamBlockHR(0, 0, HR_SCREENWIDTH, HR_SCREENHEIGHT, buffer); +} + +void I_InitPaletteHR(void) +{ + // ... +} + +void I_SetPaletteHR(const byte *palette) +{ + SDL_Rect screen_rect = {0, 0, HR_SCREENWIDTH, HR_SCREENHEIGHT}; + SDL_Color sdlpal[16]; + int i; + + for (i=0; i<16; ++i) + { + sdlpal[i].r = palette[i * 3 + 0] * 4; + sdlpal[i].g = palette[i * 3 + 1] * 4; + sdlpal[i].b = palette[i * 3 + 2] * 4; + } + + // After setting colors, update the screen. + SDL_SetPaletteColors(hr_surface->format->palette, sdlpal, 0, 16); + SDL_BlitSurface(hr_surface, &screen_rect, + SDL_GetWindowSurface(hr_screen), &screen_rect); + SDL_UpdateWindowSurfaceRects(hr_screen, &screen_rect, 1); +} + +void I_FadeToPaletteHR(const byte *palette) +{ + byte tmppal[16 * 3]; + int starttime; + int elapsed; + int i; + + starttime = I_GetTimeMS(); + + for (;;) + { + elapsed = I_GetTimeMS() - starttime; + + if (elapsed >= FADE_TIME) + { + break; + } + + // Generate the fake palette + + for (i=0; i<16 * 3; ++i) + { + tmppal[i] = (palette[i] * elapsed) / FADE_TIME; + } + + I_SetPaletteHR(tmppal); + SDL_UpdateWindowSurface(hr_screen); + + // Sleep a bit + + I_Sleep(10); + } + + // Set the final palette + + I_SetPaletteHR(palette); +} + +void I_BlackPaletteHR(void) +{ + byte blackpal[16 * 3]; + + memset(blackpal, 0, sizeof(blackpal)); + + I_SetPaletteHR(blackpal); +} + +// Check if the user has hit the escape key to abort startup. +boolean I_CheckAbortHR(void) +{ + SDL_Event ev; + boolean result = false; + + // Not initialized? + if (hr_surface == NULL) + { + return false; + } + + while (SDL_PollEvent(&ev)) + { + if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_ESCAPE) + { + result = true; + } + } + + return result; +} + diff --git a/client/src/i_videohr.h b/client/src/i_videohr.h new file mode 100644 index 0000000..cbda2ba --- /dev/null +++ b/client/src/i_videohr.h @@ -0,0 +1,35 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// SDL emulation of VGA 640x480x4 planar video mode, +// for Hexen startup loading screen. +// + +#ifndef I_VIDEOHR_H +#define I_VIDEOHR_H + +boolean I_SetVideoModeHR(void); +void I_UnsetVideoModeHR(void); +void I_SetWindowTitleHR(char *title); +void I_ClearScreenHR(void); +void I_SlamBlockHR(int x, int y, int w, int h, const byte *src); +void I_SlamHR(const byte *buffer); +void I_InitPaletteHR(void); +void I_SetPaletteHR(const byte *palette); +void I_FadeToPaletteHR(const byte *palette); +void I_BlackPaletteHR(void); +boolean I_CheckAbortHR(void); + +#endif /* #ifndef I_VIDEOHR_H */ + diff --git a/client/src/info.c b/client/src/info.c new file mode 100644 index 0000000..11c20b0 --- /dev/null +++ b/client/src/info.c @@ -0,0 +1,4797 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Thing frame/state LUT, +// generated by multigen utilitiy. +// This one is the original DOOM version, preserved. +// + +#include + +#include "info.h" +#include "m_fixed.h" +#include "p_mobj.h" +// Data. +#include "sounds.h" + +char *sprnames[] = { + "TROO", "SHTG", "PUNG", "PISG", "PISF", "SHTF", "SHT2", "CHGG", "CHGF", + "MISG", "MISF", "SAWG", "PLSG", "PLSF", "BFGG", "BFGF", "BLUD", "PUFF", + "BAL1", "BAL2", "PLSS", "PLSE", "MISL", "BFS1", "BFE1", "BFE2", "TFOG", + "IFOG", "PLAY", "POSS", "SPOS", "VILE", "FIRE", "FATB", "FBXP", "SKEL", + "MANF", "FATT", "CPOS", "SARG", "HEAD", "BAL7", "BOSS", "BOS2", "SKUL", + "SPID", "BSPI", "APLS", "APBX", "CYBR", "PAIN", "SSWV", "KEEN", "BBRN", + "BOSF", "ARM1", "ARM2", "BAR1", "BEXP", "FCAN", "BON1", "BON2", "BKEY", + "RKEY", "YKEY", "BSKU", "RSKU", "YSKU", "STIM", "MEDI", "SOUL", "PINV", + "PSTR", "PINS", "MEGA", "SUIT", "PMAP", "PVIS", "CLIP", "AMMO", "ROCK", + "BROK", "CELL", "CELP", "SHEL", "SBOX", "BPAK", "BFUG", "MGUN", "CSAW", + "LAUN", "PLAS", "SHOT", "SGN2", "COLU", "SMT2", "GOR1", "POL2", "POL5", + "POL4", "POL3", "POL1", "POL6", "GOR2", "GOR3", "GOR4", "GOR5", "SMIT", + "COL1", "COL2", "COL3", "COL4", "CAND", "CBRA", "COL6", "TRE1", "TRE2", + "ELEC", "CEYE", "FSKU", "COL5", "TBLU", "TGRN", "TRED", "SMBT", "SMGT", + "SMRT", "HDB1", "HDB2", "HDB3", "HDB4", "HDB5", "HDB6", "POB1", "POB2", + "BRS1", "TLMP", "TLP2", NULL}; + +// Doesn't work with g++, needs actionf_p1 +void A_Light0(); +void A_WeaponReady(); +void A_Lower(); +void A_Raise(); +void A_Punch(); +void A_ReFire(); +void A_FirePistol(); +void A_Light1(); +void A_FireShotgun(); +void A_Light2(); +void A_FireShotgun2(); +void A_CheckReload(); +void A_OpenShotgun2(); +void A_LoadShotgun2(); +void A_CloseShotgun2(); +void A_FireCGun(); +void A_GunFlash(); +void A_FireMissile(); +void A_Saw(); +void A_FirePlasma(); +void A_BFGsound(); +void A_FireBFG(); +void A_BFGSpray(); +void A_Explode(); +void A_Pain(); +void A_PlayerScream(); +void A_Fall(); +void A_XScream(); +void A_Look(); +void A_Chase(); +void A_FaceTarget(); +void A_PosAttack(); +void A_Scream(); +void A_SPosAttack(); +void A_VileChase(); +void A_VileStart(); +void A_VileTarget(); +void A_VileAttack(); +void A_StartFire(); +void A_Fire(); +void A_FireCrackle(); +void A_Tracer(); +void A_SkelWhoosh(); +void A_SkelFist(); +void A_SkelMissile(); +void A_FatRaise(); +void A_FatAttack1(); +void A_FatAttack2(); +void A_FatAttack3(); +void A_BossDeath(); +void A_CPosAttack(); +void A_CPosRefire(); +void A_TroopAttack(); +void A_SargAttack(); +void A_HeadAttack(); +void A_BruisAttack(); +void A_SkullAttack(); +void A_Metal(); +void A_SpidRefire(); +void A_BabyMetal(); +void A_BspiAttack(); +void A_Hoof(); +void A_CyberAttack(); +void A_PainAttack(); +void A_PainDie(); +void A_KeenDie(); +void A_BrainPain(); +void A_BrainScream(); +void A_BrainDie(); +void A_BrainAwake(); +void A_BrainSpit(); +void A_SpawnSound(); +void A_SpawnFly(); +void A_BrainExplode(); + +state_t states[NUMSTATES] = { + {SPR_TROO, 0, -1, {NULL}, S_NULL, 0, 0}, // S_NULL + {SPR_SHTG, 4, 0, {A_Light0}, S_NULL, 0, 0}, // S_LIGHTDONE + {SPR_PUNG, 0, 1, {A_WeaponReady}, S_PUNCH, 0, 0}, // S_PUNCH + {SPR_PUNG, 0, 1, {A_Lower}, S_PUNCHDOWN, 0, 0}, // S_PUNCHDOWN + {SPR_PUNG, 0, 1, {A_Raise}, S_PUNCHUP, 0, 0}, // S_PUNCHUP + {SPR_PUNG, 1, 4, {NULL}, S_PUNCH2, 0, 0}, // S_PUNCH1 + {SPR_PUNG, 2, 4, {A_Punch}, S_PUNCH3, 0, 0}, // S_PUNCH2 + {SPR_PUNG, 3, 5, {NULL}, S_PUNCH4, 0, 0}, // S_PUNCH3 + {SPR_PUNG, 2, 4, {NULL}, S_PUNCH5, 0, 0}, // S_PUNCH4 + {SPR_PUNG, 1, 5, {A_ReFire}, S_PUNCH, 0, 0}, // S_PUNCH5 + {SPR_PISG, 0, 1, {A_WeaponReady}, S_PISTOL, 0, 0}, // S_PISTOL + {SPR_PISG, 0, 1, {A_Lower}, S_PISTOLDOWN, 0, 0}, // S_PISTOLDOWN + {SPR_PISG, 0, 1, {A_Raise}, S_PISTOLUP, 0, 0}, // S_PISTOLUP + {SPR_PISG, 0, 4, {NULL}, S_PISTOL2, 0, 0}, // S_PISTOL1 + {SPR_PISG, 1, 6, {A_FirePistol}, S_PISTOL3, 0, 0}, // S_PISTOL2 + {SPR_PISG, 2, 4, {NULL}, S_PISTOL4, 0, 0}, // S_PISTOL3 + {SPR_PISG, 1, 5, {A_ReFire}, S_PISTOL, 0, 0}, // S_PISTOL4 + {SPR_PISF, 32768, 7, {A_Light1}, S_LIGHTDONE, 0, 0}, // S_PISTOLFLASH + {SPR_SHTG, 0, 1, {A_WeaponReady}, S_SGUN, 0, 0}, // S_SGUN + {SPR_SHTG, 0, 1, {A_Lower}, S_SGUNDOWN, 0, 0}, // S_SGUNDOWN + {SPR_SHTG, 0, 1, {A_Raise}, S_SGUNUP, 0, 0}, // S_SGUNUP + {SPR_SHTG, 0, 3, {NULL}, S_SGUN2, 0, 0}, // S_SGUN1 + {SPR_SHTG, 0, 7, {A_FireShotgun}, S_SGUN3, 0, 0}, // S_SGUN2 + {SPR_SHTG, 1, 5, {NULL}, S_SGUN4, 0, 0}, // S_SGUN3 + {SPR_SHTG, 2, 5, {NULL}, S_SGUN5, 0, 0}, // S_SGUN4 + {SPR_SHTG, 3, 4, {NULL}, S_SGUN6, 0, 0}, // S_SGUN5 + {SPR_SHTG, 2, 5, {NULL}, S_SGUN7, 0, 0}, // S_SGUN6 + {SPR_SHTG, 1, 5, {NULL}, S_SGUN8, 0, 0}, // S_SGUN7 + {SPR_SHTG, 0, 3, {NULL}, S_SGUN9, 0, 0}, // S_SGUN8 + {SPR_SHTG, 0, 7, {A_ReFire}, S_SGUN, 0, 0}, // S_SGUN9 + {SPR_SHTF, 32768, 4, {A_Light1}, S_SGUNFLASH2, 0, 0}, // S_SGUNFLASH1 + {SPR_SHTF, 32769, 3, {A_Light2}, S_LIGHTDONE, 0, 0}, // S_SGUNFLASH2 + {SPR_SHT2, 0, 1, {A_WeaponReady}, S_DSGUN, 0, 0}, // S_DSGUN + {SPR_SHT2, 0, 1, {A_Lower}, S_DSGUNDOWN, 0, 0}, // S_DSGUNDOWN + {SPR_SHT2, 0, 1, {A_Raise}, S_DSGUNUP, 0, 0}, // S_DSGUNUP + {SPR_SHT2, 0, 3, {NULL}, S_DSGUN2, 0, 0}, // S_DSGUN1 + {SPR_SHT2, 0, 7, {A_FireShotgun2}, S_DSGUN3, 0, 0}, // S_DSGUN2 + {SPR_SHT2, 1, 7, {NULL}, S_DSGUN4, 0, 0}, // S_DSGUN3 + {SPR_SHT2, 2, 7, {A_CheckReload}, S_DSGUN5, 0, 0}, // S_DSGUN4 + {SPR_SHT2, 3, 7, {A_OpenShotgun2}, S_DSGUN6, 0, 0}, // S_DSGUN5 + {SPR_SHT2, 4, 7, {NULL}, S_DSGUN7, 0, 0}, // S_DSGUN6 + {SPR_SHT2, 5, 7, {A_LoadShotgun2}, S_DSGUN8, 0, 0}, // S_DSGUN7 + {SPR_SHT2, 6, 6, {NULL}, S_DSGUN9, 0, 0}, // S_DSGUN8 + {SPR_SHT2, 7, 6, {A_CloseShotgun2}, S_DSGUN10, 0, 0}, // S_DSGUN9 + {SPR_SHT2, 0, 5, {A_ReFire}, S_DSGUN, 0, 0}, // S_DSGUN10 + {SPR_SHT2, 1, 7, {NULL}, S_DSNR2, 0, 0}, // S_DSNR1 + {SPR_SHT2, 0, 3, {NULL}, S_DSGUNDOWN, 0, 0}, // S_DSNR2 + {SPR_SHT2, 32776, 5, {A_Light1}, S_DSGUNFLASH2, 0, 0}, // S_DSGUNFLASH1 + {SPR_SHT2, 32777, 4, {A_Light2}, S_LIGHTDONE, 0, 0}, // S_DSGUNFLASH2 + {SPR_CHGG, 0, 1, {A_WeaponReady}, S_CHAIN, 0, 0}, // S_CHAIN + {SPR_CHGG, 0, 1, {A_Lower}, S_CHAINDOWN, 0, 0}, // S_CHAINDOWN + {SPR_CHGG, 0, 1, {A_Raise}, S_CHAINUP, 0, 0}, // S_CHAINUP + {SPR_CHGG, 0, 4, {A_FireCGun}, S_CHAIN2, 0, 0}, // S_CHAIN1 + {SPR_CHGG, 1, 4, {A_FireCGun}, S_CHAIN3, 0, 0}, // S_CHAIN2 + {SPR_CHGG, 1, 0, {A_ReFire}, S_CHAIN, 0, 0}, // S_CHAIN3 + {SPR_CHGF, 32768, 5, {A_Light1}, S_LIGHTDONE, 0, 0}, // S_CHAINFLASH1 + {SPR_CHGF, 32769, 5, {A_Light2}, S_LIGHTDONE, 0, 0}, // S_CHAINFLASH2 + {SPR_MISG, 0, 1, {A_WeaponReady}, S_MISSILE, 0, 0}, // S_MISSILE + {SPR_MISG, 0, 1, {A_Lower}, S_MISSILEDOWN, 0, 0}, // S_MISSILEDOWN + {SPR_MISG, 0, 1, {A_Raise}, S_MISSILEUP, 0, 0}, // S_MISSILEUP + {SPR_MISG, 1, 8, {A_GunFlash}, S_MISSILE2, 0, 0}, // S_MISSILE1 + {SPR_MISG, 1, 12, {A_FireMissile}, S_MISSILE3, 0, 0}, // S_MISSILE2 + {SPR_MISG, 1, 0, {A_ReFire}, S_MISSILE, 0, 0}, // S_MISSILE3 + {SPR_MISF, 32768, 3, {A_Light1}, S_MISSILEFLASH2, 0, 0}, // S_MISSILEFLASH1 + {SPR_MISF, 32769, 4, {NULL}, S_MISSILEFLASH3, 0, 0}, // S_MISSILEFLASH2 + {SPR_MISF, 32770, 4, {A_Light2}, S_MISSILEFLASH4, 0, 0}, // S_MISSILEFLASH3 + {SPR_MISF, 32771, 4, {A_Light2}, S_LIGHTDONE, 0, 0}, // S_MISSILEFLASH4 + {SPR_SAWG, 2, 4, {A_WeaponReady}, S_SAWB, 0, 0}, // S_SAW + {SPR_SAWG, 3, 4, {A_WeaponReady}, S_SAW, 0, 0}, // S_SAWB + {SPR_SAWG, 2, 1, {A_Lower}, S_SAWDOWN, 0, 0}, // S_SAWDOWN + {SPR_SAWG, 2, 1, {A_Raise}, S_SAWUP, 0, 0}, // S_SAWUP + {SPR_SAWG, 0, 4, {A_Saw}, S_SAW2, 0, 0}, // S_SAW1 + {SPR_SAWG, 1, 4, {A_Saw}, S_SAW3, 0, 0}, // S_SAW2 + {SPR_SAWG, 1, 0, {A_ReFire}, S_SAW, 0, 0}, // S_SAW3 + {SPR_PLSG, 0, 1, {A_WeaponReady}, S_PLASMA, 0, 0}, // S_PLASMA + {SPR_PLSG, 0, 1, {A_Lower}, S_PLASMADOWN, 0, 0}, // S_PLASMADOWN + {SPR_PLSG, 0, 1, {A_Raise}, S_PLASMAUP, 0, 0}, // S_PLASMAUP + {SPR_PLSG, 0, 3, {A_FirePlasma}, S_PLASMA2, 0, 0}, // S_PLASMA1 + {SPR_PLSG, 1, 20, {A_ReFire}, S_PLASMA, 0, 0}, // S_PLASMA2 + {SPR_PLSF, 32768, 4, {A_Light1}, S_LIGHTDONE, 0, 0}, // S_PLASMAFLASH1 + {SPR_PLSF, 32769, 4, {A_Light1}, S_LIGHTDONE, 0, 0}, // S_PLASMAFLASH2 + {SPR_BFGG, 0, 1, {A_WeaponReady}, S_BFG, 0, 0}, // S_BFG + {SPR_BFGG, 0, 1, {A_Lower}, S_BFGDOWN, 0, 0}, // S_BFGDOWN + {SPR_BFGG, 0, 1, {A_Raise}, S_BFGUP, 0, 0}, // S_BFGUP + {SPR_BFGG, 0, 20, {A_BFGsound}, S_BFG2, 0, 0}, // S_BFG1 + {SPR_BFGG, 1, 10, {A_GunFlash}, S_BFG3, 0, 0}, // S_BFG2 + {SPR_BFGG, 1, 10, {A_FireBFG}, S_BFG4, 0, 0}, // S_BFG3 + {SPR_BFGG, 1, 20, {A_ReFire}, S_BFG, 0, 0}, // S_BFG4 + {SPR_BFGF, 32768, 11, {A_Light1}, S_BFGFLASH2, 0, 0}, // S_BFGFLASH1 + {SPR_BFGF, 32769, 6, {A_Light2}, S_LIGHTDONE, 0, 0}, // S_BFGFLASH2 + {SPR_BLUD, 2, 8, {NULL}, S_BLOOD2, 0, 0}, // S_BLOOD1 + {SPR_BLUD, 1, 8, {NULL}, S_BLOOD3, 0, 0}, // S_BLOOD2 + {SPR_BLUD, 0, 8, {NULL}, S_NULL, 0, 0}, // S_BLOOD3 + {SPR_PUFF, 32768, 4, {NULL}, S_PUFF2, 0, 0}, // S_PUFF1 + {SPR_PUFF, 1, 4, {NULL}, S_PUFF3, 0, 0}, // S_PUFF2 + {SPR_PUFF, 2, 4, {NULL}, S_PUFF4, 0, 0}, // S_PUFF3 + {SPR_PUFF, 3, 4, {NULL}, S_NULL, 0, 0}, // S_PUFF4 + {SPR_BAL1, 32768, 4, {NULL}, S_TBALL2, 0, 0}, // S_TBALL1 + {SPR_BAL1, 32769, 4, {NULL}, S_TBALL1, 0, 0}, // S_TBALL2 + {SPR_BAL1, 32770, 6, {NULL}, S_TBALLX2, 0, 0}, // S_TBALLX1 + {SPR_BAL1, 32771, 6, {NULL}, S_TBALLX3, 0, 0}, // S_TBALLX2 + {SPR_BAL1, 32772, 6, {NULL}, S_NULL, 0, 0}, // S_TBALLX3 + {SPR_BAL2, 32768, 4, {NULL}, S_RBALL2, 0, 0}, // S_RBALL1 + {SPR_BAL2, 32769, 4, {NULL}, S_RBALL1, 0, 0}, // S_RBALL2 + {SPR_BAL2, 32770, 6, {NULL}, S_RBALLX2, 0, 0}, // S_RBALLX1 + {SPR_BAL2, 32771, 6, {NULL}, S_RBALLX3, 0, 0}, // S_RBALLX2 + {SPR_BAL2, 32772, 6, {NULL}, S_NULL, 0, 0}, // S_RBALLX3 + {SPR_PLSS, 32768, 6, {NULL}, S_PLASBALL2, 0, 0}, // S_PLASBALL + {SPR_PLSS, 32769, 6, {NULL}, S_PLASBALL, 0, 0}, // S_PLASBALL2 + {SPR_PLSE, 32768, 4, {NULL}, S_PLASEXP2, 0, 0}, // S_PLASEXP + {SPR_PLSE, 32769, 4, {NULL}, S_PLASEXP3, 0, 0}, // S_PLASEXP2 + {SPR_PLSE, 32770, 4, {NULL}, S_PLASEXP4, 0, 0}, // S_PLASEXP3 + {SPR_PLSE, 32771, 4, {NULL}, S_PLASEXP5, 0, 0}, // S_PLASEXP4 + {SPR_PLSE, 32772, 4, {NULL}, S_NULL, 0, 0}, // S_PLASEXP5 + {SPR_MISL, 32768, 1, {NULL}, S_ROCKET, 0, 0}, // S_ROCKET + {SPR_BFS1, 32768, 4, {NULL}, S_BFGSHOT2, 0, 0}, // S_BFGSHOT + {SPR_BFS1, 32769, 4, {NULL}, S_BFGSHOT, 0, 0}, // S_BFGSHOT2 + {SPR_BFE1, 32768, 8, {NULL}, S_BFGLAND2, 0, 0}, // S_BFGLAND + {SPR_BFE1, 32769, 8, {NULL}, S_BFGLAND3, 0, 0}, // S_BFGLAND2 + {SPR_BFE1, 32770, 8, {A_BFGSpray}, S_BFGLAND4, 0, 0}, // S_BFGLAND3 + {SPR_BFE1, 32771, 8, {NULL}, S_BFGLAND5, 0, 0}, // S_BFGLAND4 + {SPR_BFE1, 32772, 8, {NULL}, S_BFGLAND6, 0, 0}, // S_BFGLAND5 + {SPR_BFE1, 32773, 8, {NULL}, S_NULL, 0, 0}, // S_BFGLAND6 + {SPR_BFE2, 32768, 8, {NULL}, S_BFGEXP2, 0, 0}, // S_BFGEXP + {SPR_BFE2, 32769, 8, {NULL}, S_BFGEXP3, 0, 0}, // S_BFGEXP2 + {SPR_BFE2, 32770, 8, {NULL}, S_BFGEXP4, 0, 0}, // S_BFGEXP3 + {SPR_BFE2, 32771, 8, {NULL}, S_NULL, 0, 0}, // S_BFGEXP4 + {SPR_MISL, 32769, 8, {A_Explode}, S_EXPLODE2, 0, 0}, // S_EXPLODE1 + {SPR_MISL, 32770, 6, {NULL}, S_EXPLODE3, 0, 0}, // S_EXPLODE2 + {SPR_MISL, 32771, 4, {NULL}, S_NULL, 0, 0}, // S_EXPLODE3 + {SPR_TFOG, 32768, 6, {NULL}, S_TFOG01, 0, 0}, // S_TFOG + {SPR_TFOG, 32769, 6, {NULL}, S_TFOG02, 0, 0}, // S_TFOG01 + {SPR_TFOG, 32768, 6, {NULL}, S_TFOG2, 0, 0}, // S_TFOG02 + {SPR_TFOG, 32769, 6, {NULL}, S_TFOG3, 0, 0}, // S_TFOG2 + {SPR_TFOG, 32770, 6, {NULL}, S_TFOG4, 0, 0}, // S_TFOG3 + {SPR_TFOG, 32771, 6, {NULL}, S_TFOG5, 0, 0}, // S_TFOG4 + {SPR_TFOG, 32772, 6, {NULL}, S_TFOG6, 0, 0}, // S_TFOG5 + {SPR_TFOG, 32773, 6, {NULL}, S_TFOG7, 0, 0}, // S_TFOG6 + {SPR_TFOG, 32774, 6, {NULL}, S_TFOG8, 0, 0}, // S_TFOG7 + {SPR_TFOG, 32775, 6, {NULL}, S_TFOG9, 0, 0}, // S_TFOG8 + {SPR_TFOG, 32776, 6, {NULL}, S_TFOG10, 0, 0}, // S_TFOG9 + {SPR_TFOG, 32777, 6, {NULL}, S_NULL, 0, 0}, // S_TFOG10 + {SPR_IFOG, 32768, 6, {NULL}, S_IFOG01, 0, 0}, // S_IFOG + {SPR_IFOG, 32769, 6, {NULL}, S_IFOG02, 0, 0}, // S_IFOG01 + {SPR_IFOG, 32768, 6, {NULL}, S_IFOG2, 0, 0}, // S_IFOG02 + {SPR_IFOG, 32769, 6, {NULL}, S_IFOG3, 0, 0}, // S_IFOG2 + {SPR_IFOG, 32770, 6, {NULL}, S_IFOG4, 0, 0}, // S_IFOG3 + {SPR_IFOG, 32771, 6, {NULL}, S_IFOG5, 0, 0}, // S_IFOG4 + {SPR_IFOG, 32772, 6, {NULL}, S_NULL, 0, 0}, // S_IFOG5 + {SPR_PLAY, 0, -1, {NULL}, S_NULL, 0, 0}, // S_PLAY + {SPR_PLAY, 0, 4, {NULL}, S_PLAY_RUN2, 0, 0}, // S_PLAY_RUN1 + {SPR_PLAY, 1, 4, {NULL}, S_PLAY_RUN3, 0, 0}, // S_PLAY_RUN2 + {SPR_PLAY, 2, 4, {NULL}, S_PLAY_RUN4, 0, 0}, // S_PLAY_RUN3 + {SPR_PLAY, 3, 4, {NULL}, S_PLAY_RUN1, 0, 0}, // S_PLAY_RUN4 + {SPR_PLAY, 4, 12, {NULL}, S_PLAY, 0, 0}, // S_PLAY_ATK1 + {SPR_PLAY, 32773, 6, {NULL}, S_PLAY_ATK1, 0, 0}, // S_PLAY_ATK2 + {SPR_PLAY, 6, 4, {NULL}, S_PLAY_PAIN2, 0, 0}, // S_PLAY_PAIN + {SPR_PLAY, 6, 4, {A_Pain}, S_PLAY, 0, 0}, // S_PLAY_PAIN2 + {SPR_PLAY, 7, 10, {NULL}, S_PLAY_DIE2, 0, 0}, // S_PLAY_DIE1 + {SPR_PLAY, 8, 10, {A_PlayerScream}, S_PLAY_DIE3, 0, 0}, // S_PLAY_DIE2 + {SPR_PLAY, 9, 10, {A_Fall}, S_PLAY_DIE4, 0, 0}, // S_PLAY_DIE3 + {SPR_PLAY, 10, 10, {NULL}, S_PLAY_DIE5, 0, 0}, // S_PLAY_DIE4 + {SPR_PLAY, 11, 10, {NULL}, S_PLAY_DIE6, 0, 0}, // S_PLAY_DIE5 + {SPR_PLAY, 12, 10, {NULL}, S_PLAY_DIE7, 0, 0}, // S_PLAY_DIE6 + {SPR_PLAY, 13, -1, {NULL}, S_NULL, 0, 0}, // S_PLAY_DIE7 + {SPR_PLAY, 14, 5, {NULL}, S_PLAY_XDIE2, 0, 0}, // S_PLAY_XDIE1 + {SPR_PLAY, 15, 5, {A_XScream}, S_PLAY_XDIE3, 0, 0}, // S_PLAY_XDIE2 + {SPR_PLAY, 16, 5, {A_Fall}, S_PLAY_XDIE4, 0, 0}, // S_PLAY_XDIE3 + {SPR_PLAY, 17, 5, {NULL}, S_PLAY_XDIE5, 0, 0}, // S_PLAY_XDIE4 + {SPR_PLAY, 18, 5, {NULL}, S_PLAY_XDIE6, 0, 0}, // S_PLAY_XDIE5 + {SPR_PLAY, 19, 5, {NULL}, S_PLAY_XDIE7, 0, 0}, // S_PLAY_XDIE6 + {SPR_PLAY, 20, 5, {NULL}, S_PLAY_XDIE8, 0, 0}, // S_PLAY_XDIE7 + {SPR_PLAY, 21, 5, {NULL}, S_PLAY_XDIE9, 0, 0}, // S_PLAY_XDIE8 + {SPR_PLAY, 22, -1, {NULL}, S_NULL, 0, 0}, // S_PLAY_XDIE9 + {SPR_POSS, 0, 10, {A_Look}, S_POSS_STND2, 0, 0}, // S_POSS_STND + {SPR_POSS, 1, 10, {A_Look}, S_POSS_STND, 0, 0}, // S_POSS_STND2 + {SPR_POSS, 0, 4, {A_Chase}, S_POSS_RUN2, 0, 0}, // S_POSS_RUN1 + {SPR_POSS, 0, 4, {A_Chase}, S_POSS_RUN3, 0, 0}, // S_POSS_RUN2 + {SPR_POSS, 1, 4, {A_Chase}, S_POSS_RUN4, 0, 0}, // S_POSS_RUN3 + {SPR_POSS, 1, 4, {A_Chase}, S_POSS_RUN5, 0, 0}, // S_POSS_RUN4 + {SPR_POSS, 2, 4, {A_Chase}, S_POSS_RUN6, 0, 0}, // S_POSS_RUN5 + {SPR_POSS, 2, 4, {A_Chase}, S_POSS_RUN7, 0, 0}, // S_POSS_RUN6 + {SPR_POSS, 3, 4, {A_Chase}, S_POSS_RUN8, 0, 0}, // S_POSS_RUN7 + {SPR_POSS, 3, 4, {A_Chase}, S_POSS_RUN1, 0, 0}, // S_POSS_RUN8 + {SPR_POSS, 4, 10, {A_FaceTarget}, S_POSS_ATK2, 0, 0}, // S_POSS_ATK1 + {SPR_POSS, 5, 8, {A_PosAttack}, S_POSS_ATK3, 0, 0}, // S_POSS_ATK2 + {SPR_POSS, 4, 8, {NULL}, S_POSS_RUN1, 0, 0}, // S_POSS_ATK3 + {SPR_POSS, 6, 3, {NULL}, S_POSS_PAIN2, 0, 0}, // S_POSS_PAIN + {SPR_POSS, 6, 3, {A_Pain}, S_POSS_RUN1, 0, 0}, // S_POSS_PAIN2 + {SPR_POSS, 7, 5, {NULL}, S_POSS_DIE2, 0, 0}, // S_POSS_DIE1 + {SPR_POSS, 8, 5, {A_Scream}, S_POSS_DIE3, 0, 0}, // S_POSS_DIE2 + {SPR_POSS, 9, 5, {A_Fall}, S_POSS_DIE4, 0, 0}, // S_POSS_DIE3 + {SPR_POSS, 10, 5, {NULL}, S_POSS_DIE5, 0, 0}, // S_POSS_DIE4 + {SPR_POSS, 11, -1, {NULL}, S_NULL, 0, 0}, // S_POSS_DIE5 + {SPR_POSS, 12, 5, {NULL}, S_POSS_XDIE2, 0, 0}, // S_POSS_XDIE1 + {SPR_POSS, 13, 5, {A_XScream}, S_POSS_XDIE3, 0, 0}, // S_POSS_XDIE2 + {SPR_POSS, 14, 5, {A_Fall}, S_POSS_XDIE4, 0, 0}, // S_POSS_XDIE3 + {SPR_POSS, 15, 5, {NULL}, S_POSS_XDIE5, 0, 0}, // S_POSS_XDIE4 + {SPR_POSS, 16, 5, {NULL}, S_POSS_XDIE6, 0, 0}, // S_POSS_XDIE5 + {SPR_POSS, 17, 5, {NULL}, S_POSS_XDIE7, 0, 0}, // S_POSS_XDIE6 + {SPR_POSS, 18, 5, {NULL}, S_POSS_XDIE8, 0, 0}, // S_POSS_XDIE7 + {SPR_POSS, 19, 5, {NULL}, S_POSS_XDIE9, 0, 0}, // S_POSS_XDIE8 + {SPR_POSS, 20, -1, {NULL}, S_NULL, 0, 0}, // S_POSS_XDIE9 + {SPR_POSS, 10, 5, {NULL}, S_POSS_RAISE2, 0, 0}, // S_POSS_RAISE1 + {SPR_POSS, 9, 5, {NULL}, S_POSS_RAISE3, 0, 0}, // S_POSS_RAISE2 + {SPR_POSS, 8, 5, {NULL}, S_POSS_RAISE4, 0, 0}, // S_POSS_RAISE3 + {SPR_POSS, 7, 5, {NULL}, S_POSS_RUN1, 0, 0}, // S_POSS_RAISE4 + {SPR_SPOS, 0, 10, {A_Look}, S_SPOS_STND2, 0, 0}, // S_SPOS_STND + {SPR_SPOS, 1, 10, {A_Look}, S_SPOS_STND, 0, 0}, // S_SPOS_STND2 + {SPR_SPOS, 0, 3, {A_Chase}, S_SPOS_RUN2, 0, 0}, // S_SPOS_RUN1 + {SPR_SPOS, 0, 3, {A_Chase}, S_SPOS_RUN3, 0, 0}, // S_SPOS_RUN2 + {SPR_SPOS, 1, 3, {A_Chase}, S_SPOS_RUN4, 0, 0}, // S_SPOS_RUN3 + {SPR_SPOS, 1, 3, {A_Chase}, S_SPOS_RUN5, 0, 0}, // S_SPOS_RUN4 + {SPR_SPOS, 2, 3, {A_Chase}, S_SPOS_RUN6, 0, 0}, // S_SPOS_RUN5 + {SPR_SPOS, 2, 3, {A_Chase}, S_SPOS_RUN7, 0, 0}, // S_SPOS_RUN6 + {SPR_SPOS, 3, 3, {A_Chase}, S_SPOS_RUN8, 0, 0}, // S_SPOS_RUN7 + {SPR_SPOS, 3, 3, {A_Chase}, S_SPOS_RUN1, 0, 0}, // S_SPOS_RUN8 + {SPR_SPOS, 4, 10, {A_FaceTarget}, S_SPOS_ATK2, 0, 0}, // S_SPOS_ATK1 + {SPR_SPOS, 32773, 10, {A_SPosAttack}, S_SPOS_ATK3, 0, 0}, // S_SPOS_ATK2 + {SPR_SPOS, 4, 10, {NULL}, S_SPOS_RUN1, 0, 0}, // S_SPOS_ATK3 + {SPR_SPOS, 6, 3, {NULL}, S_SPOS_PAIN2, 0, 0}, // S_SPOS_PAIN + {SPR_SPOS, 6, 3, {A_Pain}, S_SPOS_RUN1, 0, 0}, // S_SPOS_PAIN2 + {SPR_SPOS, 7, 5, {NULL}, S_SPOS_DIE2, 0, 0}, // S_SPOS_DIE1 + {SPR_SPOS, 8, 5, {A_Scream}, S_SPOS_DIE3, 0, 0}, // S_SPOS_DIE2 + {SPR_SPOS, 9, 5, {A_Fall}, S_SPOS_DIE4, 0, 0}, // S_SPOS_DIE3 + {SPR_SPOS, 10, 5, {NULL}, S_SPOS_DIE5, 0, 0}, // S_SPOS_DIE4 + {SPR_SPOS, 11, -1, {NULL}, S_NULL, 0, 0}, // S_SPOS_DIE5 + {SPR_SPOS, 12, 5, {NULL}, S_SPOS_XDIE2, 0, 0}, // S_SPOS_XDIE1 + {SPR_SPOS, 13, 5, {A_XScream}, S_SPOS_XDIE3, 0, 0}, // S_SPOS_XDIE2 + {SPR_SPOS, 14, 5, {A_Fall}, S_SPOS_XDIE4, 0, 0}, // S_SPOS_XDIE3 + {SPR_SPOS, 15, 5, {NULL}, S_SPOS_XDIE5, 0, 0}, // S_SPOS_XDIE4 + {SPR_SPOS, 16, 5, {NULL}, S_SPOS_XDIE6, 0, 0}, // S_SPOS_XDIE5 + {SPR_SPOS, 17, 5, {NULL}, S_SPOS_XDIE7, 0, 0}, // S_SPOS_XDIE6 + {SPR_SPOS, 18, 5, {NULL}, S_SPOS_XDIE8, 0, 0}, // S_SPOS_XDIE7 + {SPR_SPOS, 19, 5, {NULL}, S_SPOS_XDIE9, 0, 0}, // S_SPOS_XDIE8 + {SPR_SPOS, 20, -1, {NULL}, S_NULL, 0, 0}, // S_SPOS_XDIE9 + {SPR_SPOS, 11, 5, {NULL}, S_SPOS_RAISE2, 0, 0}, // S_SPOS_RAISE1 + {SPR_SPOS, 10, 5, {NULL}, S_SPOS_RAISE3, 0, 0}, // S_SPOS_RAISE2 + {SPR_SPOS, 9, 5, {NULL}, S_SPOS_RAISE4, 0, 0}, // S_SPOS_RAISE3 + {SPR_SPOS, 8, 5, {NULL}, S_SPOS_RAISE5, 0, 0}, // S_SPOS_RAISE4 + {SPR_SPOS, 7, 5, {NULL}, S_SPOS_RUN1, 0, 0}, // S_SPOS_RAISE5 + {SPR_VILE, 0, 10, {A_Look}, S_VILE_STND2, 0, 0}, // S_VILE_STND + {SPR_VILE, 1, 10, {A_Look}, S_VILE_STND, 0, 0}, // S_VILE_STND2 + {SPR_VILE, 0, 2, {A_VileChase}, S_VILE_RUN2, 0, 0}, // S_VILE_RUN1 + {SPR_VILE, 0, 2, {A_VileChase}, S_VILE_RUN3, 0, 0}, // S_VILE_RUN2 + {SPR_VILE, 1, 2, {A_VileChase}, S_VILE_RUN4, 0, 0}, // S_VILE_RUN3 + {SPR_VILE, 1, 2, {A_VileChase}, S_VILE_RUN5, 0, 0}, // S_VILE_RUN4 + {SPR_VILE, 2, 2, {A_VileChase}, S_VILE_RUN6, 0, 0}, // S_VILE_RUN5 + {SPR_VILE, 2, 2, {A_VileChase}, S_VILE_RUN7, 0, 0}, // S_VILE_RUN6 + {SPR_VILE, 3, 2, {A_VileChase}, S_VILE_RUN8, 0, 0}, // S_VILE_RUN7 + {SPR_VILE, 3, 2, {A_VileChase}, S_VILE_RUN9, 0, 0}, // S_VILE_RUN8 + {SPR_VILE, 4, 2, {A_VileChase}, S_VILE_RUN10, 0, 0}, // S_VILE_RUN9 + {SPR_VILE, 4, 2, {A_VileChase}, S_VILE_RUN11, 0, 0}, // S_VILE_RUN10 + {SPR_VILE, 5, 2, {A_VileChase}, S_VILE_RUN12, 0, 0}, // S_VILE_RUN11 + {SPR_VILE, 5, 2, {A_VileChase}, S_VILE_RUN1, 0, 0}, // S_VILE_RUN12 + {SPR_VILE, 32774, 0, {A_VileStart}, S_VILE_ATK2, 0, 0}, // S_VILE_ATK1 + {SPR_VILE, 32774, 10, {A_FaceTarget}, S_VILE_ATK3, 0, 0}, // S_VILE_ATK2 + {SPR_VILE, 32775, 8, {A_VileTarget}, S_VILE_ATK4, 0, 0}, // S_VILE_ATK3 + {SPR_VILE, 32776, 8, {A_FaceTarget}, S_VILE_ATK5, 0, 0}, // S_VILE_ATK4 + {SPR_VILE, 32777, 8, {A_FaceTarget}, S_VILE_ATK6, 0, 0}, // S_VILE_ATK5 + {SPR_VILE, 32778, 8, {A_FaceTarget}, S_VILE_ATK7, 0, 0}, // S_VILE_ATK6 + {SPR_VILE, 32779, 8, {A_FaceTarget}, S_VILE_ATK8, 0, 0}, // S_VILE_ATK7 + {SPR_VILE, 32780, 8, {A_FaceTarget}, S_VILE_ATK9, 0, 0}, // S_VILE_ATK8 + {SPR_VILE, 32781, 8, {A_FaceTarget}, S_VILE_ATK10, 0, 0}, // S_VILE_ATK9 + {SPR_VILE, 32782, 8, {A_VileAttack}, S_VILE_ATK11, 0, 0}, // S_VILE_ATK10 + {SPR_VILE, 32783, 20, {NULL}, S_VILE_RUN1, 0, 0}, // S_VILE_ATK11 + {SPR_VILE, 32794, 10, {NULL}, S_VILE_HEAL2, 0, 0}, // S_VILE_HEAL1 + {SPR_VILE, 32795, 10, {NULL}, S_VILE_HEAL3, 0, 0}, // S_VILE_HEAL2 + {SPR_VILE, 32796, 10, {NULL}, S_VILE_RUN1, 0, 0}, // S_VILE_HEAL3 + {SPR_VILE, 16, 5, {NULL}, S_VILE_PAIN2, 0, 0}, // S_VILE_PAIN + {SPR_VILE, 16, 5, {A_Pain}, S_VILE_RUN1, 0, 0}, // S_VILE_PAIN2 + {SPR_VILE, 16, 7, {NULL}, S_VILE_DIE2, 0, 0}, // S_VILE_DIE1 + {SPR_VILE, 17, 7, {A_Scream}, S_VILE_DIE3, 0, 0}, // S_VILE_DIE2 + {SPR_VILE, 18, 7, {A_Fall}, S_VILE_DIE4, 0, 0}, // S_VILE_DIE3 + {SPR_VILE, 19, 7, {NULL}, S_VILE_DIE5, 0, 0}, // S_VILE_DIE4 + {SPR_VILE, 20, 7, {NULL}, S_VILE_DIE6, 0, 0}, // S_VILE_DIE5 + {SPR_VILE, 21, 7, {NULL}, S_VILE_DIE7, 0, 0}, // S_VILE_DIE6 + {SPR_VILE, 22, 7, {NULL}, S_VILE_DIE8, 0, 0}, // S_VILE_DIE7 + {SPR_VILE, 23, 5, {NULL}, S_VILE_DIE9, 0, 0}, // S_VILE_DIE8 + {SPR_VILE, 24, 5, {NULL}, S_VILE_DIE10, 0, 0}, // S_VILE_DIE9 + {SPR_VILE, 25, -1, {NULL}, S_NULL, 0, 0}, // S_VILE_DIE10 + {SPR_FIRE, 32768, 2, {A_StartFire}, S_FIRE2, 0, 0}, // S_FIRE1 + {SPR_FIRE, 32769, 2, {A_Fire}, S_FIRE3, 0, 0}, // S_FIRE2 + {SPR_FIRE, 32768, 2, {A_Fire}, S_FIRE4, 0, 0}, // S_FIRE3 + {SPR_FIRE, 32769, 2, {A_Fire}, S_FIRE5, 0, 0}, // S_FIRE4 + {SPR_FIRE, 32770, 2, {A_FireCrackle}, S_FIRE6, 0, 0}, // S_FIRE5 + {SPR_FIRE, 32769, 2, {A_Fire}, S_FIRE7, 0, 0}, // S_FIRE6 + {SPR_FIRE, 32770, 2, {A_Fire}, S_FIRE8, 0, 0}, // S_FIRE7 + {SPR_FIRE, 32769, 2, {A_Fire}, S_FIRE9, 0, 0}, // S_FIRE8 + {SPR_FIRE, 32770, 2, {A_Fire}, S_FIRE10, 0, 0}, // S_FIRE9 + {SPR_FIRE, 32771, 2, {A_Fire}, S_FIRE11, 0, 0}, // S_FIRE10 + {SPR_FIRE, 32770, 2, {A_Fire}, S_FIRE12, 0, 0}, // S_FIRE11 + {SPR_FIRE, 32771, 2, {A_Fire}, S_FIRE13, 0, 0}, // S_FIRE12 + {SPR_FIRE, 32770, 2, {A_Fire}, S_FIRE14, 0, 0}, // S_FIRE13 + {SPR_FIRE, 32771, 2, {A_Fire}, S_FIRE15, 0, 0}, // S_FIRE14 + {SPR_FIRE, 32772, 2, {A_Fire}, S_FIRE16, 0, 0}, // S_FIRE15 + {SPR_FIRE, 32771, 2, {A_Fire}, S_FIRE17, 0, 0}, // S_FIRE16 + {SPR_FIRE, 32772, 2, {A_Fire}, S_FIRE18, 0, 0}, // S_FIRE17 + {SPR_FIRE, 32771, 2, {A_Fire}, S_FIRE19, 0, 0}, // S_FIRE18 + {SPR_FIRE, 32772, 2, {A_FireCrackle}, S_FIRE20, 0, 0}, // S_FIRE19 + {SPR_FIRE, 32773, 2, {A_Fire}, S_FIRE21, 0, 0}, // S_FIRE20 + {SPR_FIRE, 32772, 2, {A_Fire}, S_FIRE22, 0, 0}, // S_FIRE21 + {SPR_FIRE, 32773, 2, {A_Fire}, S_FIRE23, 0, 0}, // S_FIRE22 + {SPR_FIRE, 32772, 2, {A_Fire}, S_FIRE24, 0, 0}, // S_FIRE23 + {SPR_FIRE, 32773, 2, {A_Fire}, S_FIRE25, 0, 0}, // S_FIRE24 + {SPR_FIRE, 32774, 2, {A_Fire}, S_FIRE26, 0, 0}, // S_FIRE25 + {SPR_FIRE, 32775, 2, {A_Fire}, S_FIRE27, 0, 0}, // S_FIRE26 + {SPR_FIRE, 32774, 2, {A_Fire}, S_FIRE28, 0, 0}, // S_FIRE27 + {SPR_FIRE, 32775, 2, {A_Fire}, S_FIRE29, 0, 0}, // S_FIRE28 + {SPR_FIRE, 32774, 2, {A_Fire}, S_FIRE30, 0, 0}, // S_FIRE29 + {SPR_FIRE, 32775, 2, {A_Fire}, S_NULL, 0, 0}, // S_FIRE30 + {SPR_PUFF, 1, 4, {NULL}, S_SMOKE2, 0, 0}, // S_SMOKE1 + {SPR_PUFF, 2, 4, {NULL}, S_SMOKE3, 0, 0}, // S_SMOKE2 + {SPR_PUFF, 1, 4, {NULL}, S_SMOKE4, 0, 0}, // S_SMOKE3 + {SPR_PUFF, 2, 4, {NULL}, S_SMOKE5, 0, 0}, // S_SMOKE4 + {SPR_PUFF, 3, 4, {NULL}, S_NULL, 0, 0}, // S_SMOKE5 + {SPR_FATB, 32768, 2, {A_Tracer}, S_TRACER2, 0, 0}, // S_TRACER + {SPR_FATB, 32769, 2, {A_Tracer}, S_TRACER, 0, 0}, // S_TRACER2 + {SPR_FBXP, 32768, 8, {NULL}, S_TRACEEXP2, 0, 0}, // S_TRACEEXP1 + {SPR_FBXP, 32769, 6, {NULL}, S_TRACEEXP3, 0, 0}, // S_TRACEEXP2 + {SPR_FBXP, 32770, 4, {NULL}, S_NULL, 0, 0}, // S_TRACEEXP3 + {SPR_SKEL, 0, 10, {A_Look}, S_SKEL_STND2, 0, 0}, // S_SKEL_STND + {SPR_SKEL, 1, 10, {A_Look}, S_SKEL_STND, 0, 0}, // S_SKEL_STND2 + {SPR_SKEL, 0, 2, {A_Chase}, S_SKEL_RUN2, 0, 0}, // S_SKEL_RUN1 + {SPR_SKEL, 0, 2, {A_Chase}, S_SKEL_RUN3, 0, 0}, // S_SKEL_RUN2 + {SPR_SKEL, 1, 2, {A_Chase}, S_SKEL_RUN4, 0, 0}, // S_SKEL_RUN3 + {SPR_SKEL, 1, 2, {A_Chase}, S_SKEL_RUN5, 0, 0}, // S_SKEL_RUN4 + {SPR_SKEL, 2, 2, {A_Chase}, S_SKEL_RUN6, 0, 0}, // S_SKEL_RUN5 + {SPR_SKEL, 2, 2, {A_Chase}, S_SKEL_RUN7, 0, 0}, // S_SKEL_RUN6 + {SPR_SKEL, 3, 2, {A_Chase}, S_SKEL_RUN8, 0, 0}, // S_SKEL_RUN7 + {SPR_SKEL, 3, 2, {A_Chase}, S_SKEL_RUN9, 0, 0}, // S_SKEL_RUN8 + {SPR_SKEL, 4, 2, {A_Chase}, S_SKEL_RUN10, 0, 0}, // S_SKEL_RUN9 + {SPR_SKEL, 4, 2, {A_Chase}, S_SKEL_RUN11, 0, 0}, // S_SKEL_RUN10 + {SPR_SKEL, 5, 2, {A_Chase}, S_SKEL_RUN12, 0, 0}, // S_SKEL_RUN11 + {SPR_SKEL, 5, 2, {A_Chase}, S_SKEL_RUN1, 0, 0}, // S_SKEL_RUN12 + {SPR_SKEL, 6, 0, {A_FaceTarget}, S_SKEL_FIST2, 0, 0}, // S_SKEL_FIST1 + {SPR_SKEL, 6, 6, {A_SkelWhoosh}, S_SKEL_FIST3, 0, 0}, // S_SKEL_FIST2 + {SPR_SKEL, 7, 6, {A_FaceTarget}, S_SKEL_FIST4, 0, 0}, // S_SKEL_FIST3 + {SPR_SKEL, 8, 6, {A_SkelFist}, S_SKEL_RUN1, 0, 0}, // S_SKEL_FIST4 + {SPR_SKEL, 32777, 0, {A_FaceTarget}, S_SKEL_MISS2, 0, 0}, // S_SKEL_MISS1 + {SPR_SKEL, 32777, 10, {A_FaceTarget}, S_SKEL_MISS3, 0, 0}, // S_SKEL_MISS2 + {SPR_SKEL, 10, 10, {A_SkelMissile}, S_SKEL_MISS4, 0, 0}, // S_SKEL_MISS3 + {SPR_SKEL, 10, 10, {A_FaceTarget}, S_SKEL_RUN1, 0, 0}, // S_SKEL_MISS4 + {SPR_SKEL, 11, 5, {NULL}, S_SKEL_PAIN2, 0, 0}, // S_SKEL_PAIN + {SPR_SKEL, 11, 5, {A_Pain}, S_SKEL_RUN1, 0, 0}, // S_SKEL_PAIN2 + {SPR_SKEL, 11, 7, {NULL}, S_SKEL_DIE2, 0, 0}, // S_SKEL_DIE1 + {SPR_SKEL, 12, 7, {NULL}, S_SKEL_DIE3, 0, 0}, // S_SKEL_DIE2 + {SPR_SKEL, 13, 7, {A_Scream}, S_SKEL_DIE4, 0, 0}, // S_SKEL_DIE3 + {SPR_SKEL, 14, 7, {A_Fall}, S_SKEL_DIE5, 0, 0}, // S_SKEL_DIE4 + {SPR_SKEL, 15, 7, {NULL}, S_SKEL_DIE6, 0, 0}, // S_SKEL_DIE5 + {SPR_SKEL, 16, -1, {NULL}, S_NULL, 0, 0}, // S_SKEL_DIE6 + {SPR_SKEL, 16, 5, {NULL}, S_SKEL_RAISE2, 0, 0}, // S_SKEL_RAISE1 + {SPR_SKEL, 15, 5, {NULL}, S_SKEL_RAISE3, 0, 0}, // S_SKEL_RAISE2 + {SPR_SKEL, 14, 5, {NULL}, S_SKEL_RAISE4, 0, 0}, // S_SKEL_RAISE3 + {SPR_SKEL, 13, 5, {NULL}, S_SKEL_RAISE5, 0, 0}, // S_SKEL_RAISE4 + {SPR_SKEL, 12, 5, {NULL}, S_SKEL_RAISE6, 0, 0}, // S_SKEL_RAISE5 + {SPR_SKEL, 11, 5, {NULL}, S_SKEL_RUN1, 0, 0}, // S_SKEL_RAISE6 + {SPR_MANF, 32768, 4, {NULL}, S_FATSHOT2, 0, 0}, // S_FATSHOT1 + {SPR_MANF, 32769, 4, {NULL}, S_FATSHOT1, 0, 0}, // S_FATSHOT2 + {SPR_MISL, 32769, 8, {NULL}, S_FATSHOTX2, 0, 0}, // S_FATSHOTX1 + {SPR_MISL, 32770, 6, {NULL}, S_FATSHOTX3, 0, 0}, // S_FATSHOTX2 + {SPR_MISL, 32771, 4, {NULL}, S_NULL, 0, 0}, // S_FATSHOTX3 + {SPR_FATT, 0, 15, {A_Look}, S_FATT_STND2, 0, 0}, // S_FATT_STND + {SPR_FATT, 1, 15, {A_Look}, S_FATT_STND, 0, 0}, // S_FATT_STND2 + {SPR_FATT, 0, 4, {A_Chase}, S_FATT_RUN2, 0, 0}, // S_FATT_RUN1 + {SPR_FATT, 0, 4, {A_Chase}, S_FATT_RUN3, 0, 0}, // S_FATT_RUN2 + {SPR_FATT, 1, 4, {A_Chase}, S_FATT_RUN4, 0, 0}, // S_FATT_RUN3 + {SPR_FATT, 1, 4, {A_Chase}, S_FATT_RUN5, 0, 0}, // S_FATT_RUN4 + {SPR_FATT, 2, 4, {A_Chase}, S_FATT_RUN6, 0, 0}, // S_FATT_RUN5 + {SPR_FATT, 2, 4, {A_Chase}, S_FATT_RUN7, 0, 0}, // S_FATT_RUN6 + {SPR_FATT, 3, 4, {A_Chase}, S_FATT_RUN8, 0, 0}, // S_FATT_RUN7 + {SPR_FATT, 3, 4, {A_Chase}, S_FATT_RUN9, 0, 0}, // S_FATT_RUN8 + {SPR_FATT, 4, 4, {A_Chase}, S_FATT_RUN10, 0, 0}, // S_FATT_RUN9 + {SPR_FATT, 4, 4, {A_Chase}, S_FATT_RUN11, 0, 0}, // S_FATT_RUN10 + {SPR_FATT, 5, 4, {A_Chase}, S_FATT_RUN12, 0, 0}, // S_FATT_RUN11 + {SPR_FATT, 5, 4, {A_Chase}, S_FATT_RUN1, 0, 0}, // S_FATT_RUN12 + {SPR_FATT, 6, 20, {A_FatRaise}, S_FATT_ATK2, 0, 0}, // S_FATT_ATK1 + {SPR_FATT, 32775, 10, {A_FatAttack1}, S_FATT_ATK3, 0, 0}, // S_FATT_ATK2 + {SPR_FATT, 8, 5, {A_FaceTarget}, S_FATT_ATK4, 0, 0}, // S_FATT_ATK3 + {SPR_FATT, 6, 5, {A_FaceTarget}, S_FATT_ATK5, 0, 0}, // S_FATT_ATK4 + {SPR_FATT, 32775, 10, {A_FatAttack2}, S_FATT_ATK6, 0, 0}, // S_FATT_ATK5 + {SPR_FATT, 8, 5, {A_FaceTarget}, S_FATT_ATK7, 0, 0}, // S_FATT_ATK6 + {SPR_FATT, 6, 5, {A_FaceTarget}, S_FATT_ATK8, 0, 0}, // S_FATT_ATK7 + {SPR_FATT, 32775, 10, {A_FatAttack3}, S_FATT_ATK9, 0, 0}, // S_FATT_ATK8 + {SPR_FATT, 8, 5, {A_FaceTarget}, S_FATT_ATK10, 0, 0}, // S_FATT_ATK9 + {SPR_FATT, 6, 5, {A_FaceTarget}, S_FATT_RUN1, 0, 0}, // S_FATT_ATK10 + {SPR_FATT, 9, 3, {NULL}, S_FATT_PAIN2, 0, 0}, // S_FATT_PAIN + {SPR_FATT, 9, 3, {A_Pain}, S_FATT_RUN1, 0, 0}, // S_FATT_PAIN2 + {SPR_FATT, 10, 6, {NULL}, S_FATT_DIE2, 0, 0}, // S_FATT_DIE1 + {SPR_FATT, 11, 6, {A_Scream}, S_FATT_DIE3, 0, 0}, // S_FATT_DIE2 + {SPR_FATT, 12, 6, {A_Fall}, S_FATT_DIE4, 0, 0}, // S_FATT_DIE3 + {SPR_FATT, 13, 6, {NULL}, S_FATT_DIE5, 0, 0}, // S_FATT_DIE4 + {SPR_FATT, 14, 6, {NULL}, S_FATT_DIE6, 0, 0}, // S_FATT_DIE5 + {SPR_FATT, 15, 6, {NULL}, S_FATT_DIE7, 0, 0}, // S_FATT_DIE6 + {SPR_FATT, 16, 6, {NULL}, S_FATT_DIE8, 0, 0}, // S_FATT_DIE7 + {SPR_FATT, 17, 6, {NULL}, S_FATT_DIE9, 0, 0}, // S_FATT_DIE8 + {SPR_FATT, 18, 6, {NULL}, S_FATT_DIE10, 0, 0}, // S_FATT_DIE9 + {SPR_FATT, 19, -1, {A_BossDeath}, S_NULL, 0, 0}, // S_FATT_DIE10 + {SPR_FATT, 17, 5, {NULL}, S_FATT_RAISE2, 0, 0}, // S_FATT_RAISE1 + {SPR_FATT, 16, 5, {NULL}, S_FATT_RAISE3, 0, 0}, // S_FATT_RAISE2 + {SPR_FATT, 15, 5, {NULL}, S_FATT_RAISE4, 0, 0}, // S_FATT_RAISE3 + {SPR_FATT, 14, 5, {NULL}, S_FATT_RAISE5, 0, 0}, // S_FATT_RAISE4 + {SPR_FATT, 13, 5, {NULL}, S_FATT_RAISE6, 0, 0}, // S_FATT_RAISE5 + {SPR_FATT, 12, 5, {NULL}, S_FATT_RAISE7, 0, 0}, // S_FATT_RAISE6 + {SPR_FATT, 11, 5, {NULL}, S_FATT_RAISE8, 0, 0}, // S_FATT_RAISE7 + {SPR_FATT, 10, 5, {NULL}, S_FATT_RUN1, 0, 0}, // S_FATT_RAISE8 + {SPR_CPOS, 0, 10, {A_Look}, S_CPOS_STND2, 0, 0}, // S_CPOS_STND + {SPR_CPOS, 1, 10, {A_Look}, S_CPOS_STND, 0, 0}, // S_CPOS_STND2 + {SPR_CPOS, 0, 3, {A_Chase}, S_CPOS_RUN2, 0, 0}, // S_CPOS_RUN1 + {SPR_CPOS, 0, 3, {A_Chase}, S_CPOS_RUN3, 0, 0}, // S_CPOS_RUN2 + {SPR_CPOS, 1, 3, {A_Chase}, S_CPOS_RUN4, 0, 0}, // S_CPOS_RUN3 + {SPR_CPOS, 1, 3, {A_Chase}, S_CPOS_RUN5, 0, 0}, // S_CPOS_RUN4 + {SPR_CPOS, 2, 3, {A_Chase}, S_CPOS_RUN6, 0, 0}, // S_CPOS_RUN5 + {SPR_CPOS, 2, 3, {A_Chase}, S_CPOS_RUN7, 0, 0}, // S_CPOS_RUN6 + {SPR_CPOS, 3, 3, {A_Chase}, S_CPOS_RUN8, 0, 0}, // S_CPOS_RUN7 + {SPR_CPOS, 3, 3, {A_Chase}, S_CPOS_RUN1, 0, 0}, // S_CPOS_RUN8 + {SPR_CPOS, 4, 10, {A_FaceTarget}, S_CPOS_ATK2, 0, 0}, // S_CPOS_ATK1 + {SPR_CPOS, 32773, 4, {A_CPosAttack}, S_CPOS_ATK3, 0, 0}, // S_CPOS_ATK2 + {SPR_CPOS, 32772, 4, {A_CPosAttack}, S_CPOS_ATK4, 0, 0}, // S_CPOS_ATK3 + {SPR_CPOS, 5, 1, {A_CPosRefire}, S_CPOS_ATK2, 0, 0}, // S_CPOS_ATK4 + {SPR_CPOS, 6, 3, {NULL}, S_CPOS_PAIN2, 0, 0}, // S_CPOS_PAIN + {SPR_CPOS, 6, 3, {A_Pain}, S_CPOS_RUN1, 0, 0}, // S_CPOS_PAIN2 + {SPR_CPOS, 7, 5, {NULL}, S_CPOS_DIE2, 0, 0}, // S_CPOS_DIE1 + {SPR_CPOS, 8, 5, {A_Scream}, S_CPOS_DIE3, 0, 0}, // S_CPOS_DIE2 + {SPR_CPOS, 9, 5, {A_Fall}, S_CPOS_DIE4, 0, 0}, // S_CPOS_DIE3 + {SPR_CPOS, 10, 5, {NULL}, S_CPOS_DIE5, 0, 0}, // S_CPOS_DIE4 + {SPR_CPOS, 11, 5, {NULL}, S_CPOS_DIE6, 0, 0}, // S_CPOS_DIE5 + {SPR_CPOS, 12, 5, {NULL}, S_CPOS_DIE7, 0, 0}, // S_CPOS_DIE6 + {SPR_CPOS, 13, -1, {NULL}, S_NULL, 0, 0}, // S_CPOS_DIE7 + {SPR_CPOS, 14, 5, {NULL}, S_CPOS_XDIE2, 0, 0}, // S_CPOS_XDIE1 + {SPR_CPOS, 15, 5, {A_XScream}, S_CPOS_XDIE3, 0, 0}, // S_CPOS_XDIE2 + {SPR_CPOS, 16, 5, {A_Fall}, S_CPOS_XDIE4, 0, 0}, // S_CPOS_XDIE3 + {SPR_CPOS, 17, 5, {NULL}, S_CPOS_XDIE5, 0, 0}, // S_CPOS_XDIE4 + {SPR_CPOS, 18, 5, {NULL}, S_CPOS_XDIE6, 0, 0}, // S_CPOS_XDIE5 + {SPR_CPOS, 19, -1, {NULL}, S_NULL, 0, 0}, // S_CPOS_XDIE6 + {SPR_CPOS, 13, 5, {NULL}, S_CPOS_RAISE2, 0, 0}, // S_CPOS_RAISE1 + {SPR_CPOS, 12, 5, {NULL}, S_CPOS_RAISE3, 0, 0}, // S_CPOS_RAISE2 + {SPR_CPOS, 11, 5, {NULL}, S_CPOS_RAISE4, 0, 0}, // S_CPOS_RAISE3 + {SPR_CPOS, 10, 5, {NULL}, S_CPOS_RAISE5, 0, 0}, // S_CPOS_RAISE4 + {SPR_CPOS, 9, 5, {NULL}, S_CPOS_RAISE6, 0, 0}, // S_CPOS_RAISE5 + {SPR_CPOS, 8, 5, {NULL}, S_CPOS_RAISE7, 0, 0}, // S_CPOS_RAISE6 + {SPR_CPOS, 7, 5, {NULL}, S_CPOS_RUN1, 0, 0}, // S_CPOS_RAISE7 + {SPR_TROO, 0, 10, {A_Look}, S_TROO_STND2, 0, 0}, // S_TROO_STND + {SPR_TROO, 1, 10, {A_Look}, S_TROO_STND, 0, 0}, // S_TROO_STND2 + {SPR_TROO, 0, 3, {A_Chase}, S_TROO_RUN2, 0, 0}, // S_TROO_RUN1 + {SPR_TROO, 0, 3, {A_Chase}, S_TROO_RUN3, 0, 0}, // S_TROO_RUN2 + {SPR_TROO, 1, 3, {A_Chase}, S_TROO_RUN4, 0, 0}, // S_TROO_RUN3 + {SPR_TROO, 1, 3, {A_Chase}, S_TROO_RUN5, 0, 0}, // S_TROO_RUN4 + {SPR_TROO, 2, 3, {A_Chase}, S_TROO_RUN6, 0, 0}, // S_TROO_RUN5 + {SPR_TROO, 2, 3, {A_Chase}, S_TROO_RUN7, 0, 0}, // S_TROO_RUN6 + {SPR_TROO, 3, 3, {A_Chase}, S_TROO_RUN8, 0, 0}, // S_TROO_RUN7 + {SPR_TROO, 3, 3, {A_Chase}, S_TROO_RUN1, 0, 0}, // S_TROO_RUN8 + {SPR_TROO, 4, 8, {A_FaceTarget}, S_TROO_ATK2, 0, 0}, // S_TROO_ATK1 + {SPR_TROO, 5, 8, {A_FaceTarget}, S_TROO_ATK3, 0, 0}, // S_TROO_ATK2 + {SPR_TROO, 6, 6, {A_TroopAttack}, S_TROO_RUN1, 0, 0}, // S_TROO_ATK3 + {SPR_TROO, 7, 2, {NULL}, S_TROO_PAIN2, 0, 0}, // S_TROO_PAIN + {SPR_TROO, 7, 2, {A_Pain}, S_TROO_RUN1, 0, 0}, // S_TROO_PAIN2 + {SPR_TROO, 8, 8, {NULL}, S_TROO_DIE2, 0, 0}, // S_TROO_DIE1 + {SPR_TROO, 9, 8, {A_Scream}, S_TROO_DIE3, 0, 0}, // S_TROO_DIE2 + {SPR_TROO, 10, 6, {NULL}, S_TROO_DIE4, 0, 0}, // S_TROO_DIE3 + {SPR_TROO, 11, 6, {A_Fall}, S_TROO_DIE5, 0, 0}, // S_TROO_DIE4 + {SPR_TROO, 12, -1, {NULL}, S_NULL, 0, 0}, // S_TROO_DIE5 + {SPR_TROO, 13, 5, {NULL}, S_TROO_XDIE2, 0, 0}, // S_TROO_XDIE1 + {SPR_TROO, 14, 5, {A_XScream}, S_TROO_XDIE3, 0, 0}, // S_TROO_XDIE2 + {SPR_TROO, 15, 5, {NULL}, S_TROO_XDIE4, 0, 0}, // S_TROO_XDIE3 + {SPR_TROO, 16, 5, {A_Fall}, S_TROO_XDIE5, 0, 0}, // S_TROO_XDIE4 + {SPR_TROO, 17, 5, {NULL}, S_TROO_XDIE6, 0, 0}, // S_TROO_XDIE5 + {SPR_TROO, 18, 5, {NULL}, S_TROO_XDIE7, 0, 0}, // S_TROO_XDIE6 + {SPR_TROO, 19, 5, {NULL}, S_TROO_XDIE8, 0, 0}, // S_TROO_XDIE7 + {SPR_TROO, 20, -1, {NULL}, S_NULL, 0, 0}, // S_TROO_XDIE8 + {SPR_TROO, 12, 8, {NULL}, S_TROO_RAISE2, 0, 0}, // S_TROO_RAISE1 + {SPR_TROO, 11, 8, {NULL}, S_TROO_RAISE3, 0, 0}, // S_TROO_RAISE2 + {SPR_TROO, 10, 6, {NULL}, S_TROO_RAISE4, 0, 0}, // S_TROO_RAISE3 + {SPR_TROO, 9, 6, {NULL}, S_TROO_RAISE5, 0, 0}, // S_TROO_RAISE4 + {SPR_TROO, 8, 6, {NULL}, S_TROO_RUN1, 0, 0}, // S_TROO_RAISE5 + {SPR_SARG, 0, 10, {A_Look}, S_SARG_STND2, 0, 0}, // S_SARG_STND + {SPR_SARG, 1, 10, {A_Look}, S_SARG_STND, 0, 0}, // S_SARG_STND2 + {SPR_SARG, 0, 2, {A_Chase}, S_SARG_RUN2, 0, 0}, // S_SARG_RUN1 + {SPR_SARG, 0, 2, {A_Chase}, S_SARG_RUN3, 0, 0}, // S_SARG_RUN2 + {SPR_SARG, 1, 2, {A_Chase}, S_SARG_RUN4, 0, 0}, // S_SARG_RUN3 + {SPR_SARG, 1, 2, {A_Chase}, S_SARG_RUN5, 0, 0}, // S_SARG_RUN4 + {SPR_SARG, 2, 2, {A_Chase}, S_SARG_RUN6, 0, 0}, // S_SARG_RUN5 + {SPR_SARG, 2, 2, {A_Chase}, S_SARG_RUN7, 0, 0}, // S_SARG_RUN6 + {SPR_SARG, 3, 2, {A_Chase}, S_SARG_RUN8, 0, 0}, // S_SARG_RUN7 + {SPR_SARG, 3, 2, {A_Chase}, S_SARG_RUN1, 0, 0}, // S_SARG_RUN8 + {SPR_SARG, 4, 8, {A_FaceTarget}, S_SARG_ATK2, 0, 0}, // S_SARG_ATK1 + {SPR_SARG, 5, 8, {A_FaceTarget}, S_SARG_ATK3, 0, 0}, // S_SARG_ATK2 + {SPR_SARG, 6, 8, {A_SargAttack}, S_SARG_RUN1, 0, 0}, // S_SARG_ATK3 + {SPR_SARG, 7, 2, {NULL}, S_SARG_PAIN2, 0, 0}, // S_SARG_PAIN + {SPR_SARG, 7, 2, {A_Pain}, S_SARG_RUN1, 0, 0}, // S_SARG_PAIN2 + {SPR_SARG, 8, 8, {NULL}, S_SARG_DIE2, 0, 0}, // S_SARG_DIE1 + {SPR_SARG, 9, 8, {A_Scream}, S_SARG_DIE3, 0, 0}, // S_SARG_DIE2 + {SPR_SARG, 10, 4, {NULL}, S_SARG_DIE4, 0, 0}, // S_SARG_DIE3 + {SPR_SARG, 11, 4, {A_Fall}, S_SARG_DIE5, 0, 0}, // S_SARG_DIE4 + {SPR_SARG, 12, 4, {NULL}, S_SARG_DIE6, 0, 0}, // S_SARG_DIE5 + {SPR_SARG, 13, -1, {NULL}, S_NULL, 0, 0}, // S_SARG_DIE6 + {SPR_SARG, 13, 5, {NULL}, S_SARG_RAISE2, 0, 0}, // S_SARG_RAISE1 + {SPR_SARG, 12, 5, {NULL}, S_SARG_RAISE3, 0, 0}, // S_SARG_RAISE2 + {SPR_SARG, 11, 5, {NULL}, S_SARG_RAISE4, 0, 0}, // S_SARG_RAISE3 + {SPR_SARG, 10, 5, {NULL}, S_SARG_RAISE5, 0, 0}, // S_SARG_RAISE4 + {SPR_SARG, 9, 5, {NULL}, S_SARG_RAISE6, 0, 0}, // S_SARG_RAISE5 + {SPR_SARG, 8, 5, {NULL}, S_SARG_RUN1, 0, 0}, // S_SARG_RAISE6 + {SPR_HEAD, 0, 10, {A_Look}, S_HEAD_STND, 0, 0}, // S_HEAD_STND + {SPR_HEAD, 0, 3, {A_Chase}, S_HEAD_RUN1, 0, 0}, // S_HEAD_RUN1 + {SPR_HEAD, 1, 5, {A_FaceTarget}, S_HEAD_ATK2, 0, 0}, // S_HEAD_ATK1 + {SPR_HEAD, 2, 5, {A_FaceTarget}, S_HEAD_ATK3, 0, 0}, // S_HEAD_ATK2 + {SPR_HEAD, 32771, 5, {A_HeadAttack}, S_HEAD_RUN1, 0, 0}, // S_HEAD_ATK3 + {SPR_HEAD, 4, 3, {NULL}, S_HEAD_PAIN2, 0, 0}, // S_HEAD_PAIN + {SPR_HEAD, 4, 3, {A_Pain}, S_HEAD_PAIN3, 0, 0}, // S_HEAD_PAIN2 + {SPR_HEAD, 5, 6, {NULL}, S_HEAD_RUN1, 0, 0}, // S_HEAD_PAIN3 + {SPR_HEAD, 6, 8, {NULL}, S_HEAD_DIE2, 0, 0}, // S_HEAD_DIE1 + {SPR_HEAD, 7, 8, {A_Scream}, S_HEAD_DIE3, 0, 0}, // S_HEAD_DIE2 + {SPR_HEAD, 8, 8, {NULL}, S_HEAD_DIE4, 0, 0}, // S_HEAD_DIE3 + {SPR_HEAD, 9, 8, {NULL}, S_HEAD_DIE5, 0, 0}, // S_HEAD_DIE4 + {SPR_HEAD, 10, 8, {A_Fall}, S_HEAD_DIE6, 0, 0}, // S_HEAD_DIE5 + {SPR_HEAD, 11, -1, {NULL}, S_NULL, 0, 0}, // S_HEAD_DIE6 + {SPR_HEAD, 11, 8, {NULL}, S_HEAD_RAISE2, 0, 0}, // S_HEAD_RAISE1 + {SPR_HEAD, 10, 8, {NULL}, S_HEAD_RAISE3, 0, 0}, // S_HEAD_RAISE2 + {SPR_HEAD, 9, 8, {NULL}, S_HEAD_RAISE4, 0, 0}, // S_HEAD_RAISE3 + {SPR_HEAD, 8, 8, {NULL}, S_HEAD_RAISE5, 0, 0}, // S_HEAD_RAISE4 + {SPR_HEAD, 7, 8, {NULL}, S_HEAD_RAISE6, 0, 0}, // S_HEAD_RAISE5 + {SPR_HEAD, 6, 8, {NULL}, S_HEAD_RUN1, 0, 0}, // S_HEAD_RAISE6 + {SPR_BAL7, 32768, 4, {NULL}, S_BRBALL2, 0, 0}, // S_BRBALL1 + {SPR_BAL7, 32769, 4, {NULL}, S_BRBALL1, 0, 0}, // S_BRBALL2 + {SPR_BAL7, 32770, 6, {NULL}, S_BRBALLX2, 0, 0}, // S_BRBALLX1 + {SPR_BAL7, 32771, 6, {NULL}, S_BRBALLX3, 0, 0}, // S_BRBALLX2 + {SPR_BAL7, 32772, 6, {NULL}, S_NULL, 0, 0}, // S_BRBALLX3 + {SPR_BOSS, 0, 10, {A_Look}, S_BOSS_STND2, 0, 0}, // S_BOSS_STND + {SPR_BOSS, 1, 10, {A_Look}, S_BOSS_STND, 0, 0}, // S_BOSS_STND2 + {SPR_BOSS, 0, 3, {A_Chase}, S_BOSS_RUN2, 0, 0}, // S_BOSS_RUN1 + {SPR_BOSS, 0, 3, {A_Chase}, S_BOSS_RUN3, 0, 0}, // S_BOSS_RUN2 + {SPR_BOSS, 1, 3, {A_Chase}, S_BOSS_RUN4, 0, 0}, // S_BOSS_RUN3 + {SPR_BOSS, 1, 3, {A_Chase}, S_BOSS_RUN5, 0, 0}, // S_BOSS_RUN4 + {SPR_BOSS, 2, 3, {A_Chase}, S_BOSS_RUN6, 0, 0}, // S_BOSS_RUN5 + {SPR_BOSS, 2, 3, {A_Chase}, S_BOSS_RUN7, 0, 0}, // S_BOSS_RUN6 + {SPR_BOSS, 3, 3, {A_Chase}, S_BOSS_RUN8, 0, 0}, // S_BOSS_RUN7 + {SPR_BOSS, 3, 3, {A_Chase}, S_BOSS_RUN1, 0, 0}, // S_BOSS_RUN8 + {SPR_BOSS, 4, 8, {A_FaceTarget}, S_BOSS_ATK2, 0, 0}, // S_BOSS_ATK1 + {SPR_BOSS, 5, 8, {A_FaceTarget}, S_BOSS_ATK3, 0, 0}, // S_BOSS_ATK2 + {SPR_BOSS, 6, 8, {A_BruisAttack}, S_BOSS_RUN1, 0, 0}, // S_BOSS_ATK3 + {SPR_BOSS, 7, 2, {NULL}, S_BOSS_PAIN2, 0, 0}, // S_BOSS_PAIN + {SPR_BOSS, 7, 2, {A_Pain}, S_BOSS_RUN1, 0, 0}, // S_BOSS_PAIN2 + {SPR_BOSS, 8, 8, {NULL}, S_BOSS_DIE2, 0, 0}, // S_BOSS_DIE1 + {SPR_BOSS, 9, 8, {A_Scream}, S_BOSS_DIE3, 0, 0}, // S_BOSS_DIE2 + {SPR_BOSS, 10, 8, {NULL}, S_BOSS_DIE4, 0, 0}, // S_BOSS_DIE3 + {SPR_BOSS, 11, 8, {A_Fall}, S_BOSS_DIE5, 0, 0}, // S_BOSS_DIE4 + {SPR_BOSS, 12, 8, {NULL}, S_BOSS_DIE6, 0, 0}, // S_BOSS_DIE5 + {SPR_BOSS, 13, 8, {NULL}, S_BOSS_DIE7, 0, 0}, // S_BOSS_DIE6 + {SPR_BOSS, 14, -1, {A_BossDeath}, S_NULL, 0, 0}, // S_BOSS_DIE7 + {SPR_BOSS, 14, 8, {NULL}, S_BOSS_RAISE2, 0, 0}, // S_BOSS_RAISE1 + {SPR_BOSS, 13, 8, {NULL}, S_BOSS_RAISE3, 0, 0}, // S_BOSS_RAISE2 + {SPR_BOSS, 12, 8, {NULL}, S_BOSS_RAISE4, 0, 0}, // S_BOSS_RAISE3 + {SPR_BOSS, 11, 8, {NULL}, S_BOSS_RAISE5, 0, 0}, // S_BOSS_RAISE4 + {SPR_BOSS, 10, 8, {NULL}, S_BOSS_RAISE6, 0, 0}, // S_BOSS_RAISE5 + {SPR_BOSS, 9, 8, {NULL}, S_BOSS_RAISE7, 0, 0}, // S_BOSS_RAISE6 + {SPR_BOSS, 8, 8, {NULL}, S_BOSS_RUN1, 0, 0}, // S_BOSS_RAISE7 + {SPR_BOS2, 0, 10, {A_Look}, S_BOS2_STND2, 0, 0}, // S_BOS2_STND + {SPR_BOS2, 1, 10, {A_Look}, S_BOS2_STND, 0, 0}, // S_BOS2_STND2 + {SPR_BOS2, 0, 3, {A_Chase}, S_BOS2_RUN2, 0, 0}, // S_BOS2_RUN1 + {SPR_BOS2, 0, 3, {A_Chase}, S_BOS2_RUN3, 0, 0}, // S_BOS2_RUN2 + {SPR_BOS2, 1, 3, {A_Chase}, S_BOS2_RUN4, 0, 0}, // S_BOS2_RUN3 + {SPR_BOS2, 1, 3, {A_Chase}, S_BOS2_RUN5, 0, 0}, // S_BOS2_RUN4 + {SPR_BOS2, 2, 3, {A_Chase}, S_BOS2_RUN6, 0, 0}, // S_BOS2_RUN5 + {SPR_BOS2, 2, 3, {A_Chase}, S_BOS2_RUN7, 0, 0}, // S_BOS2_RUN6 + {SPR_BOS2, 3, 3, {A_Chase}, S_BOS2_RUN8, 0, 0}, // S_BOS2_RUN7 + {SPR_BOS2, 3, 3, {A_Chase}, S_BOS2_RUN1, 0, 0}, // S_BOS2_RUN8 + {SPR_BOS2, 4, 8, {A_FaceTarget}, S_BOS2_ATK2, 0, 0}, // S_BOS2_ATK1 + {SPR_BOS2, 5, 8, {A_FaceTarget}, S_BOS2_ATK3, 0, 0}, // S_BOS2_ATK2 + {SPR_BOS2, 6, 8, {A_BruisAttack}, S_BOS2_RUN1, 0, 0}, // S_BOS2_ATK3 + {SPR_BOS2, 7, 2, {NULL}, S_BOS2_PAIN2, 0, 0}, // S_BOS2_PAIN + {SPR_BOS2, 7, 2, {A_Pain}, S_BOS2_RUN1, 0, 0}, // S_BOS2_PAIN2 + {SPR_BOS2, 8, 8, {NULL}, S_BOS2_DIE2, 0, 0}, // S_BOS2_DIE1 + {SPR_BOS2, 9, 8, {A_Scream}, S_BOS2_DIE3, 0, 0}, // S_BOS2_DIE2 + {SPR_BOS2, 10, 8, {NULL}, S_BOS2_DIE4, 0, 0}, // S_BOS2_DIE3 + {SPR_BOS2, 11, 8, {A_Fall}, S_BOS2_DIE5, 0, 0}, // S_BOS2_DIE4 + {SPR_BOS2, 12, 8, {NULL}, S_BOS2_DIE6, 0, 0}, // S_BOS2_DIE5 + {SPR_BOS2, 13, 8, {NULL}, S_BOS2_DIE7, 0, 0}, // S_BOS2_DIE6 + {SPR_BOS2, 14, -1, {NULL}, S_NULL, 0, 0}, // S_BOS2_DIE7 + {SPR_BOS2, 14, 8, {NULL}, S_BOS2_RAISE2, 0, 0}, // S_BOS2_RAISE1 + {SPR_BOS2, 13, 8, {NULL}, S_BOS2_RAISE3, 0, 0}, // S_BOS2_RAISE2 + {SPR_BOS2, 12, 8, {NULL}, S_BOS2_RAISE4, 0, 0}, // S_BOS2_RAISE3 + {SPR_BOS2, 11, 8, {NULL}, S_BOS2_RAISE5, 0, 0}, // S_BOS2_RAISE4 + {SPR_BOS2, 10, 8, {NULL}, S_BOS2_RAISE6, 0, 0}, // S_BOS2_RAISE5 + {SPR_BOS2, 9, 8, {NULL}, S_BOS2_RAISE7, 0, 0}, // S_BOS2_RAISE6 + {SPR_BOS2, 8, 8, {NULL}, S_BOS2_RUN1, 0, 0}, // S_BOS2_RAISE7 + {SPR_SKUL, 32768, 10, {A_Look}, S_SKULL_STND2, 0, 0}, // S_SKULL_STND + {SPR_SKUL, 32769, 10, {A_Look}, S_SKULL_STND, 0, 0}, // S_SKULL_STND2 + {SPR_SKUL, 32768, 6, {A_Chase}, S_SKULL_RUN2, 0, 0}, // S_SKULL_RUN1 + {SPR_SKUL, 32769, 6, {A_Chase}, S_SKULL_RUN1, 0, 0}, // S_SKULL_RUN2 + {SPR_SKUL, 32770, 10, {A_FaceTarget}, S_SKULL_ATK2, 0, 0}, // S_SKULL_ATK1 + {SPR_SKUL, 32771, 4, {A_SkullAttack}, S_SKULL_ATK3, 0, 0}, // S_SKULL_ATK2 + {SPR_SKUL, 32770, 4, {NULL}, S_SKULL_ATK4, 0, 0}, // S_SKULL_ATK3 + {SPR_SKUL, 32771, 4, {NULL}, S_SKULL_ATK3, 0, 0}, // S_SKULL_ATK4 + {SPR_SKUL, 32772, 3, {NULL}, S_SKULL_PAIN2, 0, 0}, // S_SKULL_PAIN + {SPR_SKUL, 32772, 3, {A_Pain}, S_SKULL_RUN1, 0, 0}, // S_SKULL_PAIN2 + {SPR_SKUL, 32773, 6, {NULL}, S_SKULL_DIE2, 0, 0}, // S_SKULL_DIE1 + {SPR_SKUL, 32774, 6, {A_Scream}, S_SKULL_DIE3, 0, 0}, // S_SKULL_DIE2 + {SPR_SKUL, 32775, 6, {NULL}, S_SKULL_DIE4, 0, 0}, // S_SKULL_DIE3 + {SPR_SKUL, 32776, 6, {A_Fall}, S_SKULL_DIE5, 0, 0}, // S_SKULL_DIE4 + {SPR_SKUL, 9, 6, {NULL}, S_SKULL_DIE6, 0, 0}, // S_SKULL_DIE5 + {SPR_SKUL, 10, 6, {NULL}, S_NULL, 0, 0}, // S_SKULL_DIE6 + {SPR_SPID, 0, 10, {A_Look}, S_SPID_STND2, 0, 0}, // S_SPID_STND + {SPR_SPID, 1, 10, {A_Look}, S_SPID_STND, 0, 0}, // S_SPID_STND2 + {SPR_SPID, 0, 3, {A_Metal}, S_SPID_RUN2, 0, 0}, // S_SPID_RUN1 + {SPR_SPID, 0, 3, {A_Chase}, S_SPID_RUN3, 0, 0}, // S_SPID_RUN2 + {SPR_SPID, 1, 3, {A_Chase}, S_SPID_RUN4, 0, 0}, // S_SPID_RUN3 + {SPR_SPID, 1, 3, {A_Chase}, S_SPID_RUN5, 0, 0}, // S_SPID_RUN4 + {SPR_SPID, 2, 3, {A_Metal}, S_SPID_RUN6, 0, 0}, // S_SPID_RUN5 + {SPR_SPID, 2, 3, {A_Chase}, S_SPID_RUN7, 0, 0}, // S_SPID_RUN6 + {SPR_SPID, 3, 3, {A_Chase}, S_SPID_RUN8, 0, 0}, // S_SPID_RUN7 + {SPR_SPID, 3, 3, {A_Chase}, S_SPID_RUN9, 0, 0}, // S_SPID_RUN8 + {SPR_SPID, 4, 3, {A_Metal}, S_SPID_RUN10, 0, 0}, // S_SPID_RUN9 + {SPR_SPID, 4, 3, {A_Chase}, S_SPID_RUN11, 0, 0}, // S_SPID_RUN10 + {SPR_SPID, 5, 3, {A_Chase}, S_SPID_RUN12, 0, 0}, // S_SPID_RUN11 + {SPR_SPID, 5, 3, {A_Chase}, S_SPID_RUN1, 0, 0}, // S_SPID_RUN12 + {SPR_SPID, 32768, 20, {A_FaceTarget}, S_SPID_ATK2, 0, 0}, // S_SPID_ATK1 + {SPR_SPID, 32774, 4, {A_SPosAttack}, S_SPID_ATK3, 0, 0}, // S_SPID_ATK2 + {SPR_SPID, 32775, 4, {A_SPosAttack}, S_SPID_ATK4, 0, 0}, // S_SPID_ATK3 + {SPR_SPID, 32775, 1, {A_SpidRefire}, S_SPID_ATK2, 0, 0}, // S_SPID_ATK4 + {SPR_SPID, 8, 3, {NULL}, S_SPID_PAIN2, 0, 0}, // S_SPID_PAIN + {SPR_SPID, 8, 3, {A_Pain}, S_SPID_RUN1, 0, 0}, // S_SPID_PAIN2 + {SPR_SPID, 9, 20, {A_Scream}, S_SPID_DIE2, 0, 0}, // S_SPID_DIE1 + {SPR_SPID, 10, 10, {A_Fall}, S_SPID_DIE3, 0, 0}, // S_SPID_DIE2 + {SPR_SPID, 11, 10, {NULL}, S_SPID_DIE4, 0, 0}, // S_SPID_DIE3 + {SPR_SPID, 12, 10, {NULL}, S_SPID_DIE5, 0, 0}, // S_SPID_DIE4 + {SPR_SPID, 13, 10, {NULL}, S_SPID_DIE6, 0, 0}, // S_SPID_DIE5 + {SPR_SPID, 14, 10, {NULL}, S_SPID_DIE7, 0, 0}, // S_SPID_DIE6 + {SPR_SPID, 15, 10, {NULL}, S_SPID_DIE8, 0, 0}, // S_SPID_DIE7 + {SPR_SPID, 16, 10, {NULL}, S_SPID_DIE9, 0, 0}, // S_SPID_DIE8 + {SPR_SPID, 17, 10, {NULL}, S_SPID_DIE10, 0, 0}, // S_SPID_DIE9 + {SPR_SPID, 18, 30, {NULL}, S_SPID_DIE11, 0, 0}, // S_SPID_DIE10 + {SPR_SPID, 18, -1, {A_BossDeath}, S_NULL, 0, 0}, // S_SPID_DIE11 + {SPR_BSPI, 0, 10, {A_Look}, S_BSPI_STND2, 0, 0}, // S_BSPI_STND + {SPR_BSPI, 1, 10, {A_Look}, S_BSPI_STND, 0, 0}, // S_BSPI_STND2 + {SPR_BSPI, 0, 20, {NULL}, S_BSPI_RUN1, 0, 0}, // S_BSPI_SIGHT + {SPR_BSPI, 0, 3, {A_BabyMetal}, S_BSPI_RUN2, 0, 0}, // S_BSPI_RUN1 + {SPR_BSPI, 0, 3, {A_Chase}, S_BSPI_RUN3, 0, 0}, // S_BSPI_RUN2 + {SPR_BSPI, 1, 3, {A_Chase}, S_BSPI_RUN4, 0, 0}, // S_BSPI_RUN3 + {SPR_BSPI, 1, 3, {A_Chase}, S_BSPI_RUN5, 0, 0}, // S_BSPI_RUN4 + {SPR_BSPI, 2, 3, {A_Chase}, S_BSPI_RUN6, 0, 0}, // S_BSPI_RUN5 + {SPR_BSPI, 2, 3, {A_Chase}, S_BSPI_RUN7, 0, 0}, // S_BSPI_RUN6 + {SPR_BSPI, 3, 3, {A_BabyMetal}, S_BSPI_RUN8, 0, 0}, // S_BSPI_RUN7 + {SPR_BSPI, 3, 3, {A_Chase}, S_BSPI_RUN9, 0, 0}, // S_BSPI_RUN8 + {SPR_BSPI, 4, 3, {A_Chase}, S_BSPI_RUN10, 0, 0}, // S_BSPI_RUN9 + {SPR_BSPI, 4, 3, {A_Chase}, S_BSPI_RUN11, 0, 0}, // S_BSPI_RUN10 + {SPR_BSPI, 5, 3, {A_Chase}, S_BSPI_RUN12, 0, 0}, // S_BSPI_RUN11 + {SPR_BSPI, 5, 3, {A_Chase}, S_BSPI_RUN1, 0, 0}, // S_BSPI_RUN12 + {SPR_BSPI, 32768, 20, {A_FaceTarget}, S_BSPI_ATK2, 0, 0}, // S_BSPI_ATK1 + {SPR_BSPI, 32774, 4, {A_BspiAttack}, S_BSPI_ATK3, 0, 0}, // S_BSPI_ATK2 + {SPR_BSPI, 32775, 4, {NULL}, S_BSPI_ATK4, 0, 0}, // S_BSPI_ATK3 + {SPR_BSPI, 32775, 1, {A_SpidRefire}, S_BSPI_ATK2, 0, 0}, // S_BSPI_ATK4 + {SPR_BSPI, 8, 3, {NULL}, S_BSPI_PAIN2, 0, 0}, // S_BSPI_PAIN + {SPR_BSPI, 8, 3, {A_Pain}, S_BSPI_RUN1, 0, 0}, // S_BSPI_PAIN2 + {SPR_BSPI, 9, 20, {A_Scream}, S_BSPI_DIE2, 0, 0}, // S_BSPI_DIE1 + {SPR_BSPI, 10, 7, {A_Fall}, S_BSPI_DIE3, 0, 0}, // S_BSPI_DIE2 + {SPR_BSPI, 11, 7, {NULL}, S_BSPI_DIE4, 0, 0}, // S_BSPI_DIE3 + {SPR_BSPI, 12, 7, {NULL}, S_BSPI_DIE5, 0, 0}, // S_BSPI_DIE4 + {SPR_BSPI, 13, 7, {NULL}, S_BSPI_DIE6, 0, 0}, // S_BSPI_DIE5 + {SPR_BSPI, 14, 7, {NULL}, S_BSPI_DIE7, 0, 0}, // S_BSPI_DIE6 + {SPR_BSPI, 15, -1, {A_BossDeath}, S_NULL, 0, 0}, // S_BSPI_DIE7 + {SPR_BSPI, 15, 5, {NULL}, S_BSPI_RAISE2, 0, 0}, // S_BSPI_RAISE1 + {SPR_BSPI, 14, 5, {NULL}, S_BSPI_RAISE3, 0, 0}, // S_BSPI_RAISE2 + {SPR_BSPI, 13, 5, {NULL}, S_BSPI_RAISE4, 0, 0}, // S_BSPI_RAISE3 + {SPR_BSPI, 12, 5, {NULL}, S_BSPI_RAISE5, 0, 0}, // S_BSPI_RAISE4 + {SPR_BSPI, 11, 5, {NULL}, S_BSPI_RAISE6, 0, 0}, // S_BSPI_RAISE5 + {SPR_BSPI, 10, 5, {NULL}, S_BSPI_RAISE7, 0, 0}, // S_BSPI_RAISE6 + {SPR_BSPI, 9, 5, {NULL}, S_BSPI_RUN1, 0, 0}, // S_BSPI_RAISE7 + {SPR_APLS, 32768, 5, {NULL}, S_ARACH_PLAZ2, 0, 0}, // S_ARACH_PLAZ + {SPR_APLS, 32769, 5, {NULL}, S_ARACH_PLAZ, 0, 0}, // S_ARACH_PLAZ2 + {SPR_APBX, 32768, 5, {NULL}, S_ARACH_PLEX2, 0, 0}, // S_ARACH_PLEX + {SPR_APBX, 32769, 5, {NULL}, S_ARACH_PLEX3, 0, 0}, // S_ARACH_PLEX2 + {SPR_APBX, 32770, 5, {NULL}, S_ARACH_PLEX4, 0, 0}, // S_ARACH_PLEX3 + {SPR_APBX, 32771, 5, {NULL}, S_ARACH_PLEX5, 0, 0}, // S_ARACH_PLEX4 + {SPR_APBX, 32772, 5, {NULL}, S_NULL, 0, 0}, // S_ARACH_PLEX5 + {SPR_CYBR, 0, 10, {A_Look}, S_CYBER_STND2, 0, 0}, // S_CYBER_STND + {SPR_CYBR, 1, 10, {A_Look}, S_CYBER_STND, 0, 0}, // S_CYBER_STND2 + {SPR_CYBR, 0, 3, {A_Hoof}, S_CYBER_RUN2, 0, 0}, // S_CYBER_RUN1 + {SPR_CYBR, 0, 3, {A_Chase}, S_CYBER_RUN3, 0, 0}, // S_CYBER_RUN2 + {SPR_CYBR, 1, 3, {A_Chase}, S_CYBER_RUN4, 0, 0}, // S_CYBER_RUN3 + {SPR_CYBR, 1, 3, {A_Chase}, S_CYBER_RUN5, 0, 0}, // S_CYBER_RUN4 + {SPR_CYBR, 2, 3, {A_Chase}, S_CYBER_RUN6, 0, 0}, // S_CYBER_RUN5 + {SPR_CYBR, 2, 3, {A_Chase}, S_CYBER_RUN7, 0, 0}, // S_CYBER_RUN6 + {SPR_CYBR, 3, 3, {A_Metal}, S_CYBER_RUN8, 0, 0}, // S_CYBER_RUN7 + {SPR_CYBR, 3, 3, {A_Chase}, S_CYBER_RUN1, 0, 0}, // S_CYBER_RUN8 + {SPR_CYBR, 4, 6, {A_FaceTarget}, S_CYBER_ATK2, 0, 0}, // S_CYBER_ATK1 + {SPR_CYBR, 5, 12, {A_CyberAttack}, S_CYBER_ATK3, 0, 0}, // S_CYBER_ATK2 + {SPR_CYBR, 4, 12, {A_FaceTarget}, S_CYBER_ATK4, 0, 0}, // S_CYBER_ATK3 + {SPR_CYBR, 5, 12, {A_CyberAttack}, S_CYBER_ATK5, 0, 0}, // S_CYBER_ATK4 + {SPR_CYBR, 4, 12, {A_FaceTarget}, S_CYBER_ATK6, 0, 0}, // S_CYBER_ATK5 + {SPR_CYBR, 5, 12, {A_CyberAttack}, S_CYBER_RUN1, 0, 0}, // S_CYBER_ATK6 + {SPR_CYBR, 6, 10, {A_Pain}, S_CYBER_RUN1, 0, 0}, // S_CYBER_PAIN + {SPR_CYBR, 7, 10, {NULL}, S_CYBER_DIE2, 0, 0}, // S_CYBER_DIE1 + {SPR_CYBR, 8, 10, {A_Scream}, S_CYBER_DIE3, 0, 0}, // S_CYBER_DIE2 + {SPR_CYBR, 9, 10, {NULL}, S_CYBER_DIE4, 0, 0}, // S_CYBER_DIE3 + {SPR_CYBR, 10, 10, {NULL}, S_CYBER_DIE5, 0, 0}, // S_CYBER_DIE4 + {SPR_CYBR, 11, 10, {NULL}, S_CYBER_DIE6, 0, 0}, // S_CYBER_DIE5 + {SPR_CYBR, 12, 10, {A_Fall}, S_CYBER_DIE7, 0, 0}, // S_CYBER_DIE6 + {SPR_CYBR, 13, 10, {NULL}, S_CYBER_DIE8, 0, 0}, // S_CYBER_DIE7 + {SPR_CYBR, 14, 10, {NULL}, S_CYBER_DIE9, 0, 0}, // S_CYBER_DIE8 + {SPR_CYBR, 15, 30, {NULL}, S_CYBER_DIE10, 0, 0}, // S_CYBER_DIE9 + {SPR_CYBR, 15, -1, {A_BossDeath}, S_NULL, 0, 0}, // S_CYBER_DIE10 + {SPR_PAIN, 0, 10, {A_Look}, S_PAIN_STND, 0, 0}, // S_PAIN_STND + {SPR_PAIN, 0, 3, {A_Chase}, S_PAIN_RUN2, 0, 0}, // S_PAIN_RUN1 + {SPR_PAIN, 0, 3, {A_Chase}, S_PAIN_RUN3, 0, 0}, // S_PAIN_RUN2 + {SPR_PAIN, 1, 3, {A_Chase}, S_PAIN_RUN4, 0, 0}, // S_PAIN_RUN3 + {SPR_PAIN, 1, 3, {A_Chase}, S_PAIN_RUN5, 0, 0}, // S_PAIN_RUN4 + {SPR_PAIN, 2, 3, {A_Chase}, S_PAIN_RUN6, 0, 0}, // S_PAIN_RUN5 + {SPR_PAIN, 2, 3, {A_Chase}, S_PAIN_RUN1, 0, 0}, // S_PAIN_RUN6 + {SPR_PAIN, 3, 5, {A_FaceTarget}, S_PAIN_ATK2, 0, 0}, // S_PAIN_ATK1 + {SPR_PAIN, 4, 5, {A_FaceTarget}, S_PAIN_ATK3, 0, 0}, // S_PAIN_ATK2 + {SPR_PAIN, 32773, 5, {A_FaceTarget}, S_PAIN_ATK4, 0, 0}, // S_PAIN_ATK3 + {SPR_PAIN, 32773, 0, {A_PainAttack}, S_PAIN_RUN1, 0, 0}, // S_PAIN_ATK4 + {SPR_PAIN, 6, 6, {NULL}, S_PAIN_PAIN2, 0, 0}, // S_PAIN_PAIN + {SPR_PAIN, 6, 6, {A_Pain}, S_PAIN_RUN1, 0, 0}, // S_PAIN_PAIN2 + {SPR_PAIN, 32775, 8, {NULL}, S_PAIN_DIE2, 0, 0}, // S_PAIN_DIE1 + {SPR_PAIN, 32776, 8, {A_Scream}, S_PAIN_DIE3, 0, 0}, // S_PAIN_DIE2 + {SPR_PAIN, 32777, 8, {NULL}, S_PAIN_DIE4, 0, 0}, // S_PAIN_DIE3 + {SPR_PAIN, 32778, 8, {NULL}, S_PAIN_DIE5, 0, 0}, // S_PAIN_DIE4 + {SPR_PAIN, 32779, 8, {A_PainDie}, S_PAIN_DIE6, 0, 0}, // S_PAIN_DIE5 + {SPR_PAIN, 32780, 8, {NULL}, S_NULL, 0, 0}, // S_PAIN_DIE6 + {SPR_PAIN, 12, 8, {NULL}, S_PAIN_RAISE2, 0, 0}, // S_PAIN_RAISE1 + {SPR_PAIN, 11, 8, {NULL}, S_PAIN_RAISE3, 0, 0}, // S_PAIN_RAISE2 + {SPR_PAIN, 10, 8, {NULL}, S_PAIN_RAISE4, 0, 0}, // S_PAIN_RAISE3 + {SPR_PAIN, 9, 8, {NULL}, S_PAIN_RAISE5, 0, 0}, // S_PAIN_RAISE4 + {SPR_PAIN, 8, 8, {NULL}, S_PAIN_RAISE6, 0, 0}, // S_PAIN_RAISE5 + {SPR_PAIN, 7, 8, {NULL}, S_PAIN_RUN1, 0, 0}, // S_PAIN_RAISE6 + {SPR_SSWV, 0, 10, {A_Look}, S_SSWV_STND2, 0, 0}, // S_SSWV_STND + {SPR_SSWV, 1, 10, {A_Look}, S_SSWV_STND, 0, 0}, // S_SSWV_STND2 + {SPR_SSWV, 0, 3, {A_Chase}, S_SSWV_RUN2, 0, 0}, // S_SSWV_RUN1 + {SPR_SSWV, 0, 3, {A_Chase}, S_SSWV_RUN3, 0, 0}, // S_SSWV_RUN2 + {SPR_SSWV, 1, 3, {A_Chase}, S_SSWV_RUN4, 0, 0}, // S_SSWV_RUN3 + {SPR_SSWV, 1, 3, {A_Chase}, S_SSWV_RUN5, 0, 0}, // S_SSWV_RUN4 + {SPR_SSWV, 2, 3, {A_Chase}, S_SSWV_RUN6, 0, 0}, // S_SSWV_RUN5 + {SPR_SSWV, 2, 3, {A_Chase}, S_SSWV_RUN7, 0, 0}, // S_SSWV_RUN6 + {SPR_SSWV, 3, 3, {A_Chase}, S_SSWV_RUN8, 0, 0}, // S_SSWV_RUN7 + {SPR_SSWV, 3, 3, {A_Chase}, S_SSWV_RUN1, 0, 0}, // S_SSWV_RUN8 + {SPR_SSWV, 4, 10, {A_FaceTarget}, S_SSWV_ATK2, 0, 0}, // S_SSWV_ATK1 + {SPR_SSWV, 5, 10, {A_FaceTarget}, S_SSWV_ATK3, 0, 0}, // S_SSWV_ATK2 + {SPR_SSWV, 32774, 4, {A_CPosAttack}, S_SSWV_ATK4, 0, 0}, // S_SSWV_ATK3 + {SPR_SSWV, 5, 6, {A_FaceTarget}, S_SSWV_ATK5, 0, 0}, // S_SSWV_ATK4 + {SPR_SSWV, 32774, 4, {A_CPosAttack}, S_SSWV_ATK6, 0, 0}, // S_SSWV_ATK5 + {SPR_SSWV, 5, 1, {A_CPosRefire}, S_SSWV_ATK2, 0, 0}, // S_SSWV_ATK6 + {SPR_SSWV, 7, 3, {NULL}, S_SSWV_PAIN2, 0, 0}, // S_SSWV_PAIN + {SPR_SSWV, 7, 3, {A_Pain}, S_SSWV_RUN1, 0, 0}, // S_SSWV_PAIN2 + {SPR_SSWV, 8, 5, {NULL}, S_SSWV_DIE2, 0, 0}, // S_SSWV_DIE1 + {SPR_SSWV, 9, 5, {A_Scream}, S_SSWV_DIE3, 0, 0}, // S_SSWV_DIE2 + {SPR_SSWV, 10, 5, {A_Fall}, S_SSWV_DIE4, 0, 0}, // S_SSWV_DIE3 + {SPR_SSWV, 11, 5, {NULL}, S_SSWV_DIE5, 0, 0}, // S_SSWV_DIE4 + {SPR_SSWV, 12, -1, {NULL}, S_NULL, 0, 0}, // S_SSWV_DIE5 + {SPR_SSWV, 13, 5, {NULL}, S_SSWV_XDIE2, 0, 0}, // S_SSWV_XDIE1 + {SPR_SSWV, 14, 5, {A_XScream}, S_SSWV_XDIE3, 0, 0}, // S_SSWV_XDIE2 + {SPR_SSWV, 15, 5, {A_Fall}, S_SSWV_XDIE4, 0, 0}, // S_SSWV_XDIE3 + {SPR_SSWV, 16, 5, {NULL}, S_SSWV_XDIE5, 0, 0}, // S_SSWV_XDIE4 + {SPR_SSWV, 17, 5, {NULL}, S_SSWV_XDIE6, 0, 0}, // S_SSWV_XDIE5 + {SPR_SSWV, 18, 5, {NULL}, S_SSWV_XDIE7, 0, 0}, // S_SSWV_XDIE6 + {SPR_SSWV, 19, 5, {NULL}, S_SSWV_XDIE8, 0, 0}, // S_SSWV_XDIE7 + {SPR_SSWV, 20, 5, {NULL}, S_SSWV_XDIE9, 0, 0}, // S_SSWV_XDIE8 + {SPR_SSWV, 21, -1, {NULL}, S_NULL, 0, 0}, // S_SSWV_XDIE9 + {SPR_SSWV, 12, 5, {NULL}, S_SSWV_RAISE2, 0, 0}, // S_SSWV_RAISE1 + {SPR_SSWV, 11, 5, {NULL}, S_SSWV_RAISE3, 0, 0}, // S_SSWV_RAISE2 + {SPR_SSWV, 10, 5, {NULL}, S_SSWV_RAISE4, 0, 0}, // S_SSWV_RAISE3 + {SPR_SSWV, 9, 5, {NULL}, S_SSWV_RAISE5, 0, 0}, // S_SSWV_RAISE4 + {SPR_SSWV, 8, 5, {NULL}, S_SSWV_RUN1, 0, 0}, // S_SSWV_RAISE5 + {SPR_KEEN, 0, -1, {NULL}, S_KEENSTND, 0, 0}, // S_KEENSTND + {SPR_KEEN, 0, 6, {NULL}, S_COMMKEEN2, 0, 0}, // S_COMMKEEN + {SPR_KEEN, 1, 6, {NULL}, S_COMMKEEN3, 0, 0}, // S_COMMKEEN2 + {SPR_KEEN, 2, 6, {A_Scream}, S_COMMKEEN4, 0, 0}, // S_COMMKEEN3 + {SPR_KEEN, 3, 6, {NULL}, S_COMMKEEN5, 0, 0}, // S_COMMKEEN4 + {SPR_KEEN, 4, 6, {NULL}, S_COMMKEEN6, 0, 0}, // S_COMMKEEN5 + {SPR_KEEN, 5, 6, {NULL}, S_COMMKEEN7, 0, 0}, // S_COMMKEEN6 + {SPR_KEEN, 6, 6, {NULL}, S_COMMKEEN8, 0, 0}, // S_COMMKEEN7 + {SPR_KEEN, 7, 6, {NULL}, S_COMMKEEN9, 0, 0}, // S_COMMKEEN8 + {SPR_KEEN, 8, 6, {NULL}, S_COMMKEEN10, 0, 0}, // S_COMMKEEN9 + {SPR_KEEN, 9, 6, {NULL}, S_COMMKEEN11, 0, 0}, // S_COMMKEEN10 + {SPR_KEEN, 10, 6, {A_KeenDie}, S_COMMKEEN12, 0, 0}, // S_COMMKEEN11 + {SPR_KEEN, 11, -1, {NULL}, S_NULL, 0, 0}, // S_COMMKEEN12 + {SPR_KEEN, 12, 4, {NULL}, S_KEENPAIN2, 0, 0}, // S_KEENPAIN + {SPR_KEEN, 12, 8, {A_Pain}, S_KEENSTND, 0, 0}, // S_KEENPAIN2 + {SPR_BBRN, 0, -1, {NULL}, S_NULL, 0, 0}, // S_BRAIN + {SPR_BBRN, 1, 36, {A_BrainPain}, S_BRAIN, 0, 0}, // S_BRAIN_PAIN + {SPR_BBRN, 0, 100, {A_BrainScream}, S_BRAIN_DIE2, 0, 0}, // S_BRAIN_DIE1 + {SPR_BBRN, 0, 10, {NULL}, S_BRAIN_DIE3, 0, 0}, // S_BRAIN_DIE2 + {SPR_BBRN, 0, 10, {NULL}, S_BRAIN_DIE4, 0, 0}, // S_BRAIN_DIE3 + {SPR_BBRN, 0, -1, {A_BrainDie}, S_NULL, 0, 0}, // S_BRAIN_DIE4 + {SPR_SSWV, 0, 10, {A_Look}, S_BRAINEYE, 0, 0}, // S_BRAINEYE + {SPR_SSWV, 0, 181, {A_BrainAwake}, S_BRAINEYE1, 0, 0}, // S_BRAINEYESEE + {SPR_SSWV, 0, 150, {A_BrainSpit}, S_BRAINEYE1, 0, 0}, // S_BRAINEYE1 + {SPR_BOSF, 32768, 3, {A_SpawnSound}, S_SPAWN2, 0, 0}, // S_SPAWN1 + {SPR_BOSF, 32769, 3, {A_SpawnFly}, S_SPAWN3, 0, 0}, // S_SPAWN2 + {SPR_BOSF, 32770, 3, {A_SpawnFly}, S_SPAWN4, 0, 0}, // S_SPAWN3 + {SPR_BOSF, 32771, 3, {A_SpawnFly}, S_SPAWN1, 0, 0}, // S_SPAWN4 + {SPR_FIRE, 32768, 4, {A_Fire}, S_SPAWNFIRE2, 0, 0}, // S_SPAWNFIRE1 + {SPR_FIRE, 32769, 4, {A_Fire}, S_SPAWNFIRE3, 0, 0}, // S_SPAWNFIRE2 + {SPR_FIRE, 32770, 4, {A_Fire}, S_SPAWNFIRE4, 0, 0}, // S_SPAWNFIRE3 + {SPR_FIRE, 32771, 4, {A_Fire}, S_SPAWNFIRE5, 0, 0}, // S_SPAWNFIRE4 + {SPR_FIRE, 32772, 4, {A_Fire}, S_SPAWNFIRE6, 0, 0}, // S_SPAWNFIRE5 + {SPR_FIRE, 32773, 4, {A_Fire}, S_SPAWNFIRE7, 0, 0}, // S_SPAWNFIRE6 + {SPR_FIRE, 32774, 4, {A_Fire}, S_SPAWNFIRE8, 0, 0}, // S_SPAWNFIRE7 + {SPR_FIRE, 32775, 4, {A_Fire}, S_NULL, 0, 0}, // S_SPAWNFIRE8 + {SPR_MISL, 32769, 10, {NULL}, S_BRAINEXPLODE2, 0, 0}, // S_BRAINEXPLODE1 + {SPR_MISL, 32770, 10, {NULL}, S_BRAINEXPLODE3, 0, 0}, // S_BRAINEXPLODE2 + {SPR_MISL, 32771, 10, {A_BrainExplode}, S_NULL, 0, 0}, // S_BRAINEXPLODE3 + {SPR_ARM1, 0, 6, {NULL}, S_ARM1A, 0, 0}, // S_ARM1 + {SPR_ARM1, 32769, 7, {NULL}, S_ARM1, 0, 0}, // S_ARM1A + {SPR_ARM2, 0, 6, {NULL}, S_ARM2A, 0, 0}, // S_ARM2 + {SPR_ARM2, 32769, 6, {NULL}, S_ARM2, 0, 0}, // S_ARM2A + {SPR_BAR1, 0, 6, {NULL}, S_BAR2, 0, 0}, // S_BAR1 + {SPR_BAR1, 1, 6, {NULL}, S_BAR1, 0, 0}, // S_BAR2 + {SPR_BEXP, 32768, 5, {NULL}, S_BEXP2, 0, 0}, // S_BEXP + {SPR_BEXP, 32769, 5, {A_Scream}, S_BEXP3, 0, 0}, // S_BEXP2 + {SPR_BEXP, 32770, 5, {NULL}, S_BEXP4, 0, 0}, // S_BEXP3 + {SPR_BEXP, 32771, 10, {A_Explode}, S_BEXP5, 0, 0}, // S_BEXP4 + {SPR_BEXP, 32772, 10, {NULL}, S_NULL, 0, 0}, // S_BEXP5 + {SPR_FCAN, 32768, 4, {NULL}, S_BBAR2, 0, 0}, // S_BBAR1 + {SPR_FCAN, 32769, 4, {NULL}, S_BBAR3, 0, 0}, // S_BBAR2 + {SPR_FCAN, 32770, 4, {NULL}, S_BBAR1, 0, 0}, // S_BBAR3 + {SPR_BON1, 0, 6, {NULL}, S_BON1A, 0, 0}, // S_BON1 + {SPR_BON1, 1, 6, {NULL}, S_BON1B, 0, 0}, // S_BON1A + {SPR_BON1, 2, 6, {NULL}, S_BON1C, 0, 0}, // S_BON1B + {SPR_BON1, 3, 6, {NULL}, S_BON1D, 0, 0}, // S_BON1C + {SPR_BON1, 2, 6, {NULL}, S_BON1E, 0, 0}, // S_BON1D + {SPR_BON1, 1, 6, {NULL}, S_BON1, 0, 0}, // S_BON1E + {SPR_BON2, 0, 6, {NULL}, S_BON2A, 0, 0}, // S_BON2 + {SPR_BON2, 1, 6, {NULL}, S_BON2B, 0, 0}, // S_BON2A + {SPR_BON2, 2, 6, {NULL}, S_BON2C, 0, 0}, // S_BON2B + {SPR_BON2, 3, 6, {NULL}, S_BON2D, 0, 0}, // S_BON2C + {SPR_BON2, 2, 6, {NULL}, S_BON2E, 0, 0}, // S_BON2D + {SPR_BON2, 1, 6, {NULL}, S_BON2, 0, 0}, // S_BON2E + {SPR_BKEY, 0, 10, {NULL}, S_BKEY2, 0, 0}, // S_BKEY + {SPR_BKEY, 32769, 10, {NULL}, S_BKEY, 0, 0}, // S_BKEY2 + {SPR_RKEY, 0, 10, {NULL}, S_RKEY2, 0, 0}, // S_RKEY + {SPR_RKEY, 32769, 10, {NULL}, S_RKEY, 0, 0}, // S_RKEY2 + {SPR_YKEY, 0, 10, {NULL}, S_YKEY2, 0, 0}, // S_YKEY + {SPR_YKEY, 32769, 10, {NULL}, S_YKEY, 0, 0}, // S_YKEY2 + {SPR_BSKU, 0, 10, {NULL}, S_BSKULL2, 0, 0}, // S_BSKULL + {SPR_BSKU, 32769, 10, {NULL}, S_BSKULL, 0, 0}, // S_BSKULL2 + {SPR_RSKU, 0, 10, {NULL}, S_RSKULL2, 0, 0}, // S_RSKULL + {SPR_RSKU, 32769, 10, {NULL}, S_RSKULL, 0, 0}, // S_RSKULL2 + {SPR_YSKU, 0, 10, {NULL}, S_YSKULL2, 0, 0}, // S_YSKULL + {SPR_YSKU, 32769, 10, {NULL}, S_YSKULL, 0, 0}, // S_YSKULL2 + {SPR_STIM, 0, -1, {NULL}, S_NULL, 0, 0}, // S_STIM + {SPR_MEDI, 0, -1, {NULL}, S_NULL, 0, 0}, // S_MEDI + {SPR_SOUL, 32768, 6, {NULL}, S_SOUL2, 0, 0}, // S_SOUL + {SPR_SOUL, 32769, 6, {NULL}, S_SOUL3, 0, 0}, // S_SOUL2 + {SPR_SOUL, 32770, 6, {NULL}, S_SOUL4, 0, 0}, // S_SOUL3 + {SPR_SOUL, 32771, 6, {NULL}, S_SOUL5, 0, 0}, // S_SOUL4 + {SPR_SOUL, 32770, 6, {NULL}, S_SOUL6, 0, 0}, // S_SOUL5 + {SPR_SOUL, 32769, 6, {NULL}, S_SOUL, 0, 0}, // S_SOUL6 + {SPR_PINV, 32768, 6, {NULL}, S_PINV2, 0, 0}, // S_PINV + {SPR_PINV, 32769, 6, {NULL}, S_PINV3, 0, 0}, // S_PINV2 + {SPR_PINV, 32770, 6, {NULL}, S_PINV4, 0, 0}, // S_PINV3 + {SPR_PINV, 32771, 6, {NULL}, S_PINV, 0, 0}, // S_PINV4 + {SPR_PSTR, 32768, -1, {NULL}, S_NULL, 0, 0}, // S_PSTR + {SPR_PINS, 32768, 6, {NULL}, S_PINS2, 0, 0}, // S_PINS + {SPR_PINS, 32769, 6, {NULL}, S_PINS3, 0, 0}, // S_PINS2 + {SPR_PINS, 32770, 6, {NULL}, S_PINS4, 0, 0}, // S_PINS3 + {SPR_PINS, 32771, 6, {NULL}, S_PINS, 0, 0}, // S_PINS4 + {SPR_MEGA, 32768, 6, {NULL}, S_MEGA2, 0, 0}, // S_MEGA + {SPR_MEGA, 32769, 6, {NULL}, S_MEGA3, 0, 0}, // S_MEGA2 + {SPR_MEGA, 32770, 6, {NULL}, S_MEGA4, 0, 0}, // S_MEGA3 + {SPR_MEGA, 32771, 6, {NULL}, S_MEGA, 0, 0}, // S_MEGA4 + {SPR_SUIT, 32768, -1, {NULL}, S_NULL, 0, 0}, // S_SUIT + {SPR_PMAP, 32768, 6, {NULL}, S_PMAP2, 0, 0}, // S_PMAP + {SPR_PMAP, 32769, 6, {NULL}, S_PMAP3, 0, 0}, // S_PMAP2 + {SPR_PMAP, 32770, 6, {NULL}, S_PMAP4, 0, 0}, // S_PMAP3 + {SPR_PMAP, 32771, 6, {NULL}, S_PMAP5, 0, 0}, // S_PMAP4 + {SPR_PMAP, 32770, 6, {NULL}, S_PMAP6, 0, 0}, // S_PMAP5 + {SPR_PMAP, 32769, 6, {NULL}, S_PMAP, 0, 0}, // S_PMAP6 + {SPR_PVIS, 32768, 6, {NULL}, S_PVIS2, 0, 0}, // S_PVIS + {SPR_PVIS, 1, 6, {NULL}, S_PVIS, 0, 0}, // S_PVIS2 + {SPR_CLIP, 0, -1, {NULL}, S_NULL, 0, 0}, // S_CLIP + {SPR_AMMO, 0, -1, {NULL}, S_NULL, 0, 0}, // S_AMMO + {SPR_ROCK, 0, -1, {NULL}, S_NULL, 0, 0}, // S_ROCK + {SPR_BROK, 0, -1, {NULL}, S_NULL, 0, 0}, // S_BROK + {SPR_CELL, 0, -1, {NULL}, S_NULL, 0, 0}, // S_CELL + {SPR_CELP, 0, -1, {NULL}, S_NULL, 0, 0}, // S_CELP + {SPR_SHEL, 0, -1, {NULL}, S_NULL, 0, 0}, // S_SHEL + {SPR_SBOX, 0, -1, {NULL}, S_NULL, 0, 0}, // S_SBOX + {SPR_BPAK, 0, -1, {NULL}, S_NULL, 0, 0}, // S_BPAK + {SPR_BFUG, 0, -1, {NULL}, S_NULL, 0, 0}, // S_BFUG + {SPR_MGUN, 0, -1, {NULL}, S_NULL, 0, 0}, // S_MGUN + {SPR_CSAW, 0, -1, {NULL}, S_NULL, 0, 0}, // S_CSAW + {SPR_LAUN, 0, -1, {NULL}, S_NULL, 0, 0}, // S_LAUN + {SPR_PLAS, 0, -1, {NULL}, S_NULL, 0, 0}, // S_PLAS + {SPR_SHOT, 0, -1, {NULL}, S_NULL, 0, 0}, // S_SHOT + {SPR_SGN2, 0, -1, {NULL}, S_NULL, 0, 0}, // S_SHOT2 + {SPR_COLU, 32768, -1, {NULL}, S_NULL, 0, 0}, // S_COLU + {SPR_SMT2, 0, -1, {NULL}, S_NULL, 0, 0}, // S_STALAG + {SPR_GOR1, 0, 10, {NULL}, S_BLOODYTWITCH2, 0, 0}, // S_BLOODYTWITCH + {SPR_GOR1, 1, 15, {NULL}, S_BLOODYTWITCH3, 0, 0}, // S_BLOODYTWITCH2 + {SPR_GOR1, 2, 8, {NULL}, S_BLOODYTWITCH4, 0, 0}, // S_BLOODYTWITCH3 + {SPR_GOR1, 1, 6, {NULL}, S_BLOODYTWITCH, 0, 0}, // S_BLOODYTWITCH4 + {SPR_PLAY, 13, -1, {NULL}, S_NULL, 0, 0}, // S_DEADTORSO + {SPR_PLAY, 18, -1, {NULL}, S_NULL, 0, 0}, // S_DEADBOTTOM + {SPR_POL2, 0, -1, {NULL}, S_NULL, 0, 0}, // S_HEADSONSTICK + {SPR_POL5, 0, -1, {NULL}, S_NULL, 0, 0}, // S_GIBS + {SPR_POL4, 0, -1, {NULL}, S_NULL, 0, 0}, // S_HEADONASTICK + {SPR_POL3, 32768, 6, {NULL}, S_HEADCANDLES2, 0, 0}, // S_HEADCANDLES + {SPR_POL3, 32769, 6, {NULL}, S_HEADCANDLES, 0, 0}, // S_HEADCANDLES2 + {SPR_POL1, 0, -1, {NULL}, S_NULL, 0, 0}, // S_DEADSTICK + {SPR_POL6, 0, 6, {NULL}, S_LIVESTICK2, 0, 0}, // S_LIVESTICK + {SPR_POL6, 1, 8, {NULL}, S_LIVESTICK, 0, 0}, // S_LIVESTICK2 + {SPR_GOR2, 0, -1, {NULL}, S_NULL, 0, 0}, // S_MEAT2 + {SPR_GOR3, 0, -1, {NULL}, S_NULL, 0, 0}, // S_MEAT3 + {SPR_GOR4, 0, -1, {NULL}, S_NULL, 0, 0}, // S_MEAT4 + {SPR_GOR5, 0, -1, {NULL}, S_NULL, 0, 0}, // S_MEAT5 + {SPR_SMIT, 0, -1, {NULL}, S_NULL, 0, 0}, // S_STALAGTITE + {SPR_COL1, 0, -1, {NULL}, S_NULL, 0, 0}, // S_TALLGRNCOL + {SPR_COL2, 0, -1, {NULL}, S_NULL, 0, 0}, // S_SHRTGRNCOL + {SPR_COL3, 0, -1, {NULL}, S_NULL, 0, 0}, // S_TALLREDCOL + {SPR_COL4, 0, -1, {NULL}, S_NULL, 0, 0}, // S_SHRTREDCOL + {SPR_CAND, 32768, -1, {NULL}, S_NULL, 0, 0}, // S_CANDLESTIK + {SPR_CBRA, 32768, -1, {NULL}, S_NULL, 0, 0}, // S_CANDELABRA + {SPR_COL6, 0, -1, {NULL}, S_NULL, 0, 0}, // S_SKULLCOL + {SPR_TRE1, 0, -1, {NULL}, S_NULL, 0, 0}, // S_TORCHTREE + {SPR_TRE2, 0, -1, {NULL}, S_NULL, 0, 0}, // S_BIGTREE + {SPR_ELEC, 0, -1, {NULL}, S_NULL, 0, 0}, // S_TECHPILLAR + {SPR_CEYE, 32768, 6, {NULL}, S_EVILEYE2, 0, 0}, // S_EVILEYE + {SPR_CEYE, 32769, 6, {NULL}, S_EVILEYE3, 0, 0}, // S_EVILEYE2 + {SPR_CEYE, 32770, 6, {NULL}, S_EVILEYE4, 0, 0}, // S_EVILEYE3 + {SPR_CEYE, 32769, 6, {NULL}, S_EVILEYE, 0, 0}, // S_EVILEYE4 + {SPR_FSKU, 32768, 6, {NULL}, S_FLOATSKULL2, 0, 0}, // S_FLOATSKULL + {SPR_FSKU, 32769, 6, {NULL}, S_FLOATSKULL3, 0, 0}, // S_FLOATSKULL2 + {SPR_FSKU, 32770, 6, {NULL}, S_FLOATSKULL, 0, 0}, // S_FLOATSKULL3 + {SPR_COL5, 0, 14, {NULL}, S_HEARTCOL2, 0, 0}, // S_HEARTCOL + {SPR_COL5, 1, 14, {NULL}, S_HEARTCOL, 0, 0}, // S_HEARTCOL2 + {SPR_TBLU, 32768, 4, {NULL}, S_BLUETORCH2, 0, 0}, // S_BLUETORCH + {SPR_TBLU, 32769, 4, {NULL}, S_BLUETORCH3, 0, 0}, // S_BLUETORCH2 + {SPR_TBLU, 32770, 4, {NULL}, S_BLUETORCH4, 0, 0}, // S_BLUETORCH3 + {SPR_TBLU, 32771, 4, {NULL}, S_BLUETORCH, 0, 0}, // S_BLUETORCH4 + {SPR_TGRN, 32768, 4, {NULL}, S_GREENTORCH2, 0, 0}, // S_GREENTORCH + {SPR_TGRN, 32769, 4, {NULL}, S_GREENTORCH3, 0, 0}, // S_GREENTORCH2 + {SPR_TGRN, 32770, 4, {NULL}, S_GREENTORCH4, 0, 0}, // S_GREENTORCH3 + {SPR_TGRN, 32771, 4, {NULL}, S_GREENTORCH, 0, 0}, // S_GREENTORCH4 + {SPR_TRED, 32768, 4, {NULL}, S_REDTORCH2, 0, 0}, // S_REDTORCH + {SPR_TRED, 32769, 4, {NULL}, S_REDTORCH3, 0, 0}, // S_REDTORCH2 + {SPR_TRED, 32770, 4, {NULL}, S_REDTORCH4, 0, 0}, // S_REDTORCH3 + {SPR_TRED, 32771, 4, {NULL}, S_REDTORCH, 0, 0}, // S_REDTORCH4 + {SPR_SMBT, 32768, 4, {NULL}, S_BTORCHSHRT2, 0, 0}, // S_BTORCHSHRT + {SPR_SMBT, 32769, 4, {NULL}, S_BTORCHSHRT3, 0, 0}, // S_BTORCHSHRT2 + {SPR_SMBT, 32770, 4, {NULL}, S_BTORCHSHRT4, 0, 0}, // S_BTORCHSHRT3 + {SPR_SMBT, 32771, 4, {NULL}, S_BTORCHSHRT, 0, 0}, // S_BTORCHSHRT4 + {SPR_SMGT, 32768, 4, {NULL}, S_GTORCHSHRT2, 0, 0}, // S_GTORCHSHRT + {SPR_SMGT, 32769, 4, {NULL}, S_GTORCHSHRT3, 0, 0}, // S_GTORCHSHRT2 + {SPR_SMGT, 32770, 4, {NULL}, S_GTORCHSHRT4, 0, 0}, // S_GTORCHSHRT3 + {SPR_SMGT, 32771, 4, {NULL}, S_GTORCHSHRT, 0, 0}, // S_GTORCHSHRT4 + {SPR_SMRT, 32768, 4, {NULL}, S_RTORCHSHRT2, 0, 0}, // S_RTORCHSHRT + {SPR_SMRT, 32769, 4, {NULL}, S_RTORCHSHRT3, 0, 0}, // S_RTORCHSHRT2 + {SPR_SMRT, 32770, 4, {NULL}, S_RTORCHSHRT4, 0, 0}, // S_RTORCHSHRT3 + {SPR_SMRT, 32771, 4, {NULL}, S_RTORCHSHRT, 0, 0}, // S_RTORCHSHRT4 + {SPR_HDB1, 0, -1, {NULL}, S_NULL, 0, 0}, // S_HANGNOGUTS + {SPR_HDB2, 0, -1, {NULL}, S_NULL, 0, 0}, // S_HANGBNOBRAIN + {SPR_HDB3, 0, -1, {NULL}, S_NULL, 0, 0}, // S_HANGTLOOKDN + {SPR_HDB4, 0, -1, {NULL}, S_NULL, 0, 0}, // S_HANGTSKULL + {SPR_HDB5, 0, -1, {NULL}, S_NULL, 0, 0}, // S_HANGTLOOKUP + {SPR_HDB6, 0, -1, {NULL}, S_NULL, 0, 0}, // S_HANGTNOBRAIN + {SPR_POB1, 0, -1, {NULL}, S_NULL, 0, 0}, // S_COLONGIBS + {SPR_POB2, 0, -1, {NULL}, S_NULL, 0, 0}, // S_SMALLPOOL + {SPR_BRS1, 0, -1, {NULL}, S_NULL, 0, 0}, // S_BRAINSTEM + {SPR_TLMP, 32768, 4, {NULL}, S_TECHLAMP2, 0, 0}, // S_TECHLAMP + {SPR_TLMP, 32769, 4, {NULL}, S_TECHLAMP3, 0, 0}, // S_TECHLAMP2 + {SPR_TLMP, 32770, 4, {NULL}, S_TECHLAMP4, 0, 0}, // S_TECHLAMP3 + {SPR_TLMP, 32771, 4, {NULL}, S_TECHLAMP, 0, 0}, // S_TECHLAMP4 + {SPR_TLP2, 32768, 4, {NULL}, S_TECH2LAMP2, 0, 0}, // S_TECH2LAMP + {SPR_TLP2, 32769, 4, {NULL}, S_TECH2LAMP3, 0, 0}, // S_TECH2LAMP2 + {SPR_TLP2, 32770, 4, {NULL}, S_TECH2LAMP4, 0, 0}, // S_TECH2LAMP3 + {SPR_TLP2, 32771, 4, {NULL}, S_TECH2LAMP, 0, 0} // S_TECH2LAMP4 +}; + +mobjinfo_t mobjinfo[NUMMOBJTYPES] = { + + { + // MT_PLAYER + -1, // doomednum + S_PLAY, // spawnstate + 100, // spawnhealth + S_PLAY_RUN1, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_PLAY_PAIN, // painstate + 255, // painchance + sfx_plpain, // painsound + S_NULL, // meleestate + S_PLAY_ATK1, // missilestate + S_PLAY_DIE1, // deathstate + S_PLAY_XDIE1, // xdeathstate + sfx_pldeth, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SHOOTABLE | MF_DROPOFF | MF_PICKUP | + MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { + // MT_POSSESSED + 3004, // doomednum + S_POSS_STND, // spawnstate + 20, // spawnhealth + S_POSS_RUN1, // seestate + sfx_posit1, // seesound + 8, // reactiontime + sfx_pistol, // attacksound + S_POSS_PAIN, // painstate + 200, // painchance + sfx_popain, // painsound + 0, // meleestate + S_POSS_ATK1, // missilestate + S_POSS_DIE1, // deathstate + S_POSS_XDIE1, // xdeathstate + sfx_podth1, // deathsound + 8, // speed + 20 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_POSS_RAISE1 // raisestate + }, + + { + // MT_SHOTGUY + 9, // doomednum + S_SPOS_STND, // spawnstate + 30, // spawnhealth + S_SPOS_RUN1, // seestate + sfx_posit2, // seesound + 8, // reactiontime + 0, // attacksound + S_SPOS_PAIN, // painstate + 170, // painchance + sfx_popain, // painsound + 0, // meleestate + S_SPOS_ATK1, // missilestate + S_SPOS_DIE1, // deathstate + S_SPOS_XDIE1, // xdeathstate + sfx_podth2, // deathsound + 8, // speed + 20 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_SPOS_RAISE1 // raisestate + }, + + { + // MT_VILE + 64, // doomednum + S_VILE_STND, // spawnstate + 700, // spawnhealth + S_VILE_RUN1, // seestate + sfx_vilsit, // seesound + 8, // reactiontime + 0, // attacksound + S_VILE_PAIN, // painstate + 10, // painchance + sfx_vipain, // painsound + 0, // meleestate + S_VILE_ATK1, // missilestate + S_VILE_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_vildth, // deathsound + 15, // speed + 20 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 500, // mass + 0, // damage + sfx_vilact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { + // MT_FIRE + -1, // doomednum + S_FIRE1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_UNDEAD + 66, // doomednum + S_SKEL_STND, // spawnstate + 300, // spawnhealth + S_SKEL_RUN1, // seestate + sfx_skesit, // seesound + 8, // reactiontime + 0, // attacksound + S_SKEL_PAIN, // painstate + 100, // painchance + sfx_popain, // painsound + S_SKEL_FIST1, // meleestate + S_SKEL_MISS1, // missilestate + S_SKEL_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_skedth, // deathsound + 10, // speed + 20 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 500, // mass + 0, // damage + sfx_skeact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_SKEL_RAISE1 // raisestate + }, + + { + // MT_TRACER + -1, // doomednum + S_TRACER, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_skeatk, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_TRACEEXP1, // deathstate + S_NULL, // xdeathstate + sfx_barexp, // deathsound + 10 * FRACUNIT, // speed + 11 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 10, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_SMOKE + -1, // doomednum + S_SMOKE1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_FATSO + 67, // doomednum + S_FATT_STND, // spawnstate + 600, // spawnhealth + S_FATT_RUN1, // seestate + sfx_mansit, // seesound + 8, // reactiontime + 0, // attacksound + S_FATT_PAIN, // painstate + 80, // painchance + sfx_mnpain, // painsound + 0, // meleestate + S_FATT_ATK1, // missilestate + S_FATT_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_mandth, // deathsound + 8, // speed + 48 * FRACUNIT, // radius + 64 * FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_FATT_RAISE1 // raisestate + }, + + { + // MT_FATSHOT + -1, // doomednum + S_FATSHOT1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_FATSHOTX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 20 * FRACUNIT, // speed + 6 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 8, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_CHAINGUY + 65, // doomednum + S_CPOS_STND, // spawnstate + 70, // spawnhealth + S_CPOS_RUN1, // seestate + sfx_posit2, // seesound + 8, // reactiontime + 0, // attacksound + S_CPOS_PAIN, // painstate + 170, // painchance + sfx_popain, // painsound + 0, // meleestate + S_CPOS_ATK1, // missilestate + S_CPOS_DIE1, // deathstate + S_CPOS_XDIE1, // xdeathstate + sfx_podth2, // deathsound + 8, // speed + 20 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_CPOS_RAISE1 // raisestate + }, + + { + // MT_TROOP + 3001, // doomednum + S_TROO_STND, // spawnstate + 60, // spawnhealth + S_TROO_RUN1, // seestate + sfx_bgsit1, // seesound + 8, // reactiontime + 0, // attacksound + S_TROO_PAIN, // painstate + 200, // painchance + sfx_popain, // painsound + S_TROO_ATK1, // meleestate + S_TROO_ATK1, // missilestate + S_TROO_DIE1, // deathstate + S_TROO_XDIE1, // xdeathstate + sfx_bgdth1, // deathsound + 8, // speed + 20 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_bgact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_TROO_RAISE1 // raisestate + }, + + { + // MT_SERGEANT + 3002, // doomednum + S_SARG_STND, // spawnstate + 150, // spawnhealth + S_SARG_RUN1, // seestate + sfx_sgtsit, // seesound + 8, // reactiontime + sfx_sgtatk, // attacksound + S_SARG_PAIN, // painstate + 180, // painchance + sfx_dmpain, // painsound + S_SARG_ATK1, // meleestate + 0, // missilestate + S_SARG_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_sgtdth, // deathsound + 10, // speed + 30 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_SARG_RAISE1 // raisestate + }, + + { + // MT_SHADOWS + 58, // doomednum + S_SARG_STND, // spawnstate + 150, // spawnhealth + S_SARG_RUN1, // seestate + sfx_sgtsit, // seesound + 8, // reactiontime + sfx_sgtatk, // attacksound + S_SARG_PAIN, // painstate + 180, // painchance + sfx_dmpain, // painsound + S_SARG_ATK1, // meleestate + 0, // missilestate + S_SARG_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_sgtdth, // deathsound + 10, // speed + 30 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_SHADOW | MF_COUNTKILL, // flags + S_SARG_RAISE1 // raisestate + }, + + { + // MT_HEAD + 3005, // doomednum + S_HEAD_STND, // spawnstate + 400, // spawnhealth + S_HEAD_RUN1, // seestate + sfx_cacsit, // seesound + 8, // reactiontime + 0, // attacksound + S_HEAD_PAIN, // painstate + 128, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_HEAD_ATK1, // missilestate + S_HEAD_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_cacdth, // deathsound + 8, // speed + 31 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_FLOAT | MF_NOGRAVITY | + MF_COUNTKILL, // flags + S_HEAD_RAISE1 // raisestate + }, + + { + // MT_BRUISER + 3003, // doomednum + S_BOSS_STND, // spawnstate + 1000, // spawnhealth + S_BOSS_RUN1, // seestate + sfx_brssit, // seesound + 8, // reactiontime + 0, // attacksound + S_BOSS_PAIN, // painstate + 50, // painchance + sfx_dmpain, // painsound + S_BOSS_ATK1, // meleestate + S_BOSS_ATK1, // missilestate + S_BOSS_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_brsdth, // deathsound + 8, // speed + 24 * FRACUNIT, // radius + 64 * FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_BOSS_RAISE1 // raisestate + }, + + { + // MT_BRUISERSHOT + -1, // doomednum + S_BRBALL1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BRBALLX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 15 * FRACUNIT, // speed + 6 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 8, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_KNIGHT + 69, // doomednum + S_BOS2_STND, // spawnstate + 500, // spawnhealth + S_BOS2_RUN1, // seestate + sfx_kntsit, // seesound + 8, // reactiontime + 0, // attacksound + S_BOS2_PAIN, // painstate + 50, // painchance + sfx_dmpain, // painsound + S_BOS2_ATK1, // meleestate + S_BOS2_ATK1, // missilestate + S_BOS2_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_kntdth, // deathsound + 8, // speed + 24 * FRACUNIT, // radius + 64 * FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_BOS2_RAISE1 // raisestate + }, + + { + // MT_SKULL + 3006, // doomednum + S_SKULL_STND, // spawnstate + 100, // spawnhealth + S_SKULL_RUN1, // seestate + 0, // seesound + 8, // reactiontime + sfx_sklatk, // attacksound + S_SKULL_PAIN, // painstate + 256, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_SKULL_ATK1, // missilestate + S_SKULL_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 8, // speed + 16 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 50, // mass + 3, // damage + sfx_dmact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_FLOAT | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_SPIDER + 7, // doomednum + S_SPID_STND, // spawnstate + 3000, // spawnhealth + S_SPID_RUN1, // seestate + sfx_spisit, // seesound + 8, // reactiontime + sfx_shotgn, // attacksound + S_SPID_PAIN, // painstate + 40, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_SPID_ATK1, // missilestate + S_SPID_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_spidth, // deathsound + 12, // speed + 128 * FRACUNIT, // radius + 100 * FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { + // MT_BABY + 68, // doomednum + S_BSPI_STND, // spawnstate + 500, // spawnhealth + S_BSPI_SIGHT, // seestate + sfx_bspsit, // seesound + 8, // reactiontime + 0, // attacksound + S_BSPI_PAIN, // painstate + 128, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_BSPI_ATK1, // missilestate + S_BSPI_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_bspdth, // deathsound + 12, // speed + 64 * FRACUNIT, // radius + 64 * FRACUNIT, // height + 600, // mass + 0, // damage + sfx_bspact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_BSPI_RAISE1 // raisestate + }, + + { + // MT_CYBORG + 16, // doomednum + S_CYBER_STND, // spawnstate + 4000, // spawnhealth + S_CYBER_RUN1, // seestate + sfx_cybsit, // seesound + 8, // reactiontime + 0, // attacksound + S_CYBER_PAIN, // painstate + 20, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_CYBER_ATK1, // missilestate + S_CYBER_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_cybdth, // deathsound + 16, // speed + 40 * FRACUNIT, // radius + 110 * FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { + // MT_PAIN + 71, // doomednum + S_PAIN_STND, // spawnstate + 400, // spawnhealth + S_PAIN_RUN1, // seestate + sfx_pesit, // seesound + 8, // reactiontime + 0, // attacksound + S_PAIN_PAIN, // painstate + 128, // painchance + sfx_pepain, // painsound + 0, // meleestate + S_PAIN_ATK1, // missilestate + S_PAIN_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_pedth, // deathsound + 8, // speed + 31 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_FLOAT | MF_NOGRAVITY | + MF_COUNTKILL, // flags + S_PAIN_RAISE1 // raisestate + }, + + { + // MT_WOLFSS + 84, // doomednum + S_SSWV_STND, // spawnstate + 50, // spawnhealth + S_SSWV_RUN1, // seestate + sfx_sssit, // seesound + 8, // reactiontime + 0, // attacksound + S_SSWV_PAIN, // painstate + 170, // painchance + sfx_popain, // painsound + 0, // meleestate + S_SSWV_ATK1, // missilestate + S_SSWV_DIE1, // deathstate + S_SSWV_XDIE1, // xdeathstate + sfx_ssdth, // deathsound + 8, // speed + 20 * FRACUNIT, // radius + 56 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + S_SSWV_RAISE1 // raisestate + }, + + { + // MT_KEEN + 72, // doomednum + S_KEENSTND, // spawnstate + 100, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_KEENPAIN, // painstate + 256, // painchance + sfx_keenpn, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_COMMKEEN, // deathstate + S_NULL, // xdeathstate + sfx_keendt, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 72 * FRACUNIT, // height + 10000000, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_SHOOTABLE | + MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { + // MT_BOSSBRAIN + 88, // doomednum + S_BRAIN, // spawnstate + 250, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_BRAIN_PAIN, // painstate + 255, // painchance + sfx_bospn, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BRAIN_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_bosdth, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 10000000, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SHOOTABLE, // flags + S_NULL // raisestate + }, + + { + // MT_BOSSSPIT + 89, // doomednum + S_BRAINEYE, // spawnstate + 1000, // spawnhealth + S_BRAINEYESEE, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 32 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOSECTOR, // flags + S_NULL // raisestate + }, + + { + // MT_BOSSTARGET + 87, // doomednum + S_NULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 32 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOSECTOR, // flags + S_NULL // raisestate + }, + + { + // MT_SPAWNSHOT + -1, // doomednum + S_SPAWN1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_bospit, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 10 * FRACUNIT, // speed + 6 * FRACUNIT, // radius + 32 * FRACUNIT, // height + 100, // mass + 3, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY | + MF_NOCLIP, // flags + S_NULL // raisestate + }, + + { + // MT_SPAWNFIRE + -1, // doomednum + S_SPAWNFIRE1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_BARREL + 2035, // doomednum + S_BAR1, // spawnstate + 20, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BEXP, // deathstate + S_NULL, // xdeathstate + sfx_barexp, // deathsound + 0, // speed + 10 * FRACUNIT, // radius + 42 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SHOOTABLE | MF_NOBLOOD, // flags + S_NULL // raisestate + }, + + { + // MT_TROOPSHOT + -1, // doomednum + S_TBALL1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_TBALLX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 10 * FRACUNIT, // speed + 6 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 3, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_HEADSHOT + -1, // doomednum + S_RBALL1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_RBALLX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 10 * FRACUNIT, // speed + 6 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_ROCKET + -1, // doomednum + S_ROCKET, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_rlaunc, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_EXPLODE1, // deathstate + S_NULL, // xdeathstate + sfx_barexp, // deathsound + 20 * FRACUNIT, // speed + 11 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 20, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_PLASMA + -1, // doomednum + S_PLASBALL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_plasma, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_PLASEXP, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 25 * FRACUNIT, // speed + 13 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_BFG + -1, // doomednum + S_BFGSHOT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + 0, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BFGLAND, // deathstate + S_NULL, // xdeathstate + sfx_rxplod, // deathsound + 25 * FRACUNIT, // speed + 13 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 100, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_ARACHPLAZ + -1, // doomednum + S_ARACH_PLAZ, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_plasma, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_ARACH_PLEX, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 25 * FRACUNIT, // speed + 13 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_PUFF + -1, // doomednum + S_PUFF1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_BLOOD + -1, // doomednum + S_BLOOD1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }, + + { + // MT_TFOG + -1, // doomednum + S_TFOG, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_IFOG + -1, // doomednum + S_IFOG, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_TELEPORTMAN + 14, // doomednum + S_NULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOSECTOR, // flags + S_NULL // raisestate + }, + + { + // MT_EXTRABFG + -1, // doomednum + S_BFGEXP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC0 + 2018, // doomednum + S_ARM1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC1 + 2019, // doomednum + S_ARM2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC2 + 2014, // doomednum + S_BON1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { + // MT_MISC3 + 2015, // doomednum + S_BON2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { + // MT_MISC4 + 5, // doomednum + S_BKEY, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { + // MT_MISC5 + 13, // doomednum + S_RKEY, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { + // MT_MISC6 + 6, // doomednum + S_YKEY, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { + // MT_MISC7 + 39, // doomednum + S_YSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { + // MT_MISC8 + 38, // doomednum + S_RSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { + // MT_MISC9 + 40, // doomednum + S_BSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { + // MT_MISC10 + 2011, // doomednum + S_STIM, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC11 + 2012, // doomednum + S_MEDI, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC12 + 2013, // doomednum + S_SOUL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { + // MT_INV + 2022, // doomednum + S_PINV, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { + // MT_MISC13 + 2023, // doomednum + S_PSTR, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { + // MT_INS + 2024, // doomednum + S_PINS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { + // MT_MISC14 + 2025, // doomednum + S_SUIT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC15 + 2026, // doomednum + S_PMAP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { + // MT_MISC16 + 2045, // doomednum + S_PVIS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { + // MT_MEGA + 83, // doomednum + S_MEGA, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL | MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { + // MT_CLIP + 2007, // doomednum + S_CLIP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC17 + 2048, // doomednum + S_AMMO, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC18 + 2010, // doomednum + S_ROCK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC19 + 2046, // doomednum + S_BROK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC20 + 2047, // doomednum + S_CELL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC21 + 17, // doomednum + S_CELP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC22 + 2008, // doomednum + S_SHEL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC23 + 2049, // doomednum + S_SBOX, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC24 + 8, // doomednum + S_BPAK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC25 + 2006, // doomednum + S_BFUG, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_CHAINGUN + 2002, // doomednum + S_MGUN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC26 + 2005, // doomednum + S_CSAW, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC27 + 2003, // doomednum + S_LAUN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC28 + 2004, // doomednum + S_PLAS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_SHOTGUN + 2001, // doomednum + S_SHOT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_SUPERSHOTGUN + 82, // doomednum + S_SHOT2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { + // MT_MISC29 + 85, // doomednum + S_TECHLAMP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC30 + 86, // doomednum + S_TECH2LAMP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC31 + 2028, // doomednum + S_COLU, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC32 + 30, // doomednum + S_TALLGRNCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC33 + 31, // doomednum + S_SHRTGRNCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC34 + 32, // doomednum + S_TALLREDCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC35 + 33, // doomednum + S_SHRTREDCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC36 + 37, // doomednum + S_SKULLCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC37 + 36, // doomednum + S_HEARTCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC38 + 41, // doomednum + S_EVILEYE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC39 + 42, // doomednum + S_FLOATSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC40 + 43, // doomednum + S_TORCHTREE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC41 + 44, // doomednum + S_BLUETORCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC42 + 45, // doomednum + S_GREENTORCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC43 + 46, // doomednum + S_REDTORCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC44 + 55, // doomednum + S_BTORCHSHRT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC45 + 56, // doomednum + S_GTORCHSHRT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC46 + 57, // doomednum + S_RTORCHSHRT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC47 + 47, // doomednum + S_STALAGTITE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC48 + 48, // doomednum + S_TECHPILLAR, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC49 + 34, // doomednum + S_CANDLESTIK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { + // MT_MISC50 + 35, // doomednum + S_CANDELABRA, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC51 + 49, // doomednum + S_BLOODYTWITCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 68 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC52 + 50, // doomednum + S_MEAT2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 84 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC53 + 51, // doomednum + S_MEAT3, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 84 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC54 + 52, // doomednum + S_MEAT4, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 68 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC55 + 53, // doomednum + S_MEAT5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 52 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC56 + 59, // doomednum + S_MEAT2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 84 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC57 + 60, // doomednum + S_MEAT4, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 68 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC58 + 61, // doomednum + S_MEAT3, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 52 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC59 + 62, // doomednum + S_MEAT5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 52 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC60 + 63, // doomednum + S_BLOODYTWITCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 68 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC61 + 22, // doomednum + S_HEAD_DIE6, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { + // MT_MISC62 + 15, // doomednum + S_PLAY_DIE7, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { + // MT_MISC63 + 18, // doomednum + S_POSS_DIE5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { + // MT_MISC64 + 21, // doomednum + S_SARG_DIE6, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { + // MT_MISC65 + 23, // doomednum + S_SKULL_DIE6, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { + // MT_MISC66 + 20, // doomednum + S_TROO_DIE5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { + // MT_MISC67 + 19, // doomednum + S_SPOS_DIE5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { + // MT_MISC68 + 10, // doomednum + S_PLAY_XDIE9, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { + // MT_MISC69 + 12, // doomednum + S_PLAY_XDIE9, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { + // MT_MISC70 + 28, // doomednum + S_HEADSONSTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC71 + 24, // doomednum + S_GIBS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { + // MT_MISC72 + 27, // doomednum + S_HEADONASTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC73 + 29, // doomednum + S_HEADCANDLES, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC74 + 25, // doomednum + S_DEADSTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC75 + 26, // doomednum + S_LIVESTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC76 + 54, // doomednum + S_BIGTREE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC77 + 70, // doomednum + S_BBAR1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { + // MT_MISC78 + 73, // doomednum + S_HANGNOGUTS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 88 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC79 + 74, // doomednum + S_HANGBNOBRAIN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 88 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC80 + 75, // doomednum + S_HANGTLOOKDN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 64 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC81 + 76, // doomednum + S_HANGTSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 64 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC82 + 77, // doomednum + S_HANGTLOOKUP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 64 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC83 + 78, // doomednum + S_HANGTNOBRAIN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 64 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { + // MT_MISC84 + 79, // doomednum + S_COLONGIBS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }, + + { + // MT_MISC85 + 80, // doomednum + S_SMALLPOOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }, + + { + // MT_MISC86 + 81, // doomednum + S_BRAINSTEM, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }}; diff --git a/client/src/info.h b/client/src/info.h new file mode 100644 index 0000000..a971424 --- /dev/null +++ b/client/src/info.h @@ -0,0 +1,1326 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Thing frame/state LUT, +// generated by multigen utilitiy. +// This one is the original DOOM version, preserved. +// + +#ifndef __INFO__ +#define __INFO__ + +// Needed for action function pointer handling. +#include "d_think.h" + +typedef enum { + SPR_TROO, + SPR_SHTG, + SPR_PUNG, + SPR_PISG, + SPR_PISF, + SPR_SHTF, + SPR_SHT2, + SPR_CHGG, + SPR_CHGF, + SPR_MISG, + SPR_MISF, + SPR_SAWG, + SPR_PLSG, + SPR_PLSF, + SPR_BFGG, + SPR_BFGF, + SPR_BLUD, + SPR_PUFF, + SPR_BAL1, + SPR_BAL2, + SPR_PLSS, + SPR_PLSE, + SPR_MISL, + SPR_BFS1, + SPR_BFE1, + SPR_BFE2, + SPR_TFOG, + SPR_IFOG, + SPR_PLAY, + SPR_POSS, + SPR_SPOS, + SPR_VILE, + SPR_FIRE, + SPR_FATB, + SPR_FBXP, + SPR_SKEL, + SPR_MANF, + SPR_FATT, + SPR_CPOS, + SPR_SARG, + SPR_HEAD, + SPR_BAL7, + SPR_BOSS, + SPR_BOS2, + SPR_SKUL, + SPR_SPID, + SPR_BSPI, + SPR_APLS, + SPR_APBX, + SPR_CYBR, + SPR_PAIN, + SPR_SSWV, + SPR_KEEN, + SPR_BBRN, + SPR_BOSF, + SPR_ARM1, + SPR_ARM2, + SPR_BAR1, + SPR_BEXP, + SPR_FCAN, + SPR_BON1, + SPR_BON2, + SPR_BKEY, + SPR_RKEY, + SPR_YKEY, + SPR_BSKU, + SPR_RSKU, + SPR_YSKU, + SPR_STIM, + SPR_MEDI, + SPR_SOUL, + SPR_PINV, + SPR_PSTR, + SPR_PINS, + SPR_MEGA, + SPR_SUIT, + SPR_PMAP, + SPR_PVIS, + SPR_CLIP, + SPR_AMMO, + SPR_ROCK, + SPR_BROK, + SPR_CELL, + SPR_CELP, + SPR_SHEL, + SPR_SBOX, + SPR_BPAK, + SPR_BFUG, + SPR_MGUN, + SPR_CSAW, + SPR_LAUN, + SPR_PLAS, + SPR_SHOT, + SPR_SGN2, + SPR_COLU, + SPR_SMT2, + SPR_GOR1, + SPR_POL2, + SPR_POL5, + SPR_POL4, + SPR_POL3, + SPR_POL1, + SPR_POL6, + SPR_GOR2, + SPR_GOR3, + SPR_GOR4, + SPR_GOR5, + SPR_SMIT, + SPR_COL1, + SPR_COL2, + SPR_COL3, + SPR_COL4, + SPR_CAND, + SPR_CBRA, + SPR_COL6, + SPR_TRE1, + SPR_TRE2, + SPR_ELEC, + SPR_CEYE, + SPR_FSKU, + SPR_COL5, + SPR_TBLU, + SPR_TGRN, + SPR_TRED, + SPR_SMBT, + SPR_SMGT, + SPR_SMRT, + SPR_HDB1, + SPR_HDB2, + SPR_HDB3, + SPR_HDB4, + SPR_HDB5, + SPR_HDB6, + SPR_POB1, + SPR_POB2, + SPR_BRS1, + SPR_TLMP, + SPR_TLP2, + NUMSPRITES + +} spritenum_t; + +typedef enum { + S_NULL, + S_LIGHTDONE, + S_PUNCH, + S_PUNCHDOWN, + S_PUNCHUP, + S_PUNCH1, + S_PUNCH2, + S_PUNCH3, + S_PUNCH4, + S_PUNCH5, + S_PISTOL, + S_PISTOLDOWN, + S_PISTOLUP, + S_PISTOL1, + S_PISTOL2, + S_PISTOL3, + S_PISTOL4, + S_PISTOLFLASH, + S_SGUN, + S_SGUNDOWN, + S_SGUNUP, + S_SGUN1, + S_SGUN2, + S_SGUN3, + S_SGUN4, + S_SGUN5, + S_SGUN6, + S_SGUN7, + S_SGUN8, + S_SGUN9, + S_SGUNFLASH1, + S_SGUNFLASH2, + S_DSGUN, + S_DSGUNDOWN, + S_DSGUNUP, + S_DSGUN1, + S_DSGUN2, + S_DSGUN3, + S_DSGUN4, + S_DSGUN5, + S_DSGUN6, + S_DSGUN7, + S_DSGUN8, + S_DSGUN9, + S_DSGUN10, + S_DSNR1, + S_DSNR2, + S_DSGUNFLASH1, + S_DSGUNFLASH2, + S_CHAIN, + S_CHAINDOWN, + S_CHAINUP, + S_CHAIN1, + S_CHAIN2, + S_CHAIN3, + S_CHAINFLASH1, + S_CHAINFLASH2, + S_MISSILE, + S_MISSILEDOWN, + S_MISSILEUP, + S_MISSILE1, + S_MISSILE2, + S_MISSILE3, + S_MISSILEFLASH1, + S_MISSILEFLASH2, + S_MISSILEFLASH3, + S_MISSILEFLASH4, + S_SAW, + S_SAWB, + S_SAWDOWN, + S_SAWUP, + S_SAW1, + S_SAW2, + S_SAW3, + S_PLASMA, + S_PLASMADOWN, + S_PLASMAUP, + S_PLASMA1, + S_PLASMA2, + S_PLASMAFLASH1, + S_PLASMAFLASH2, + S_BFG, + S_BFGDOWN, + S_BFGUP, + S_BFG1, + S_BFG2, + S_BFG3, + S_BFG4, + S_BFGFLASH1, + S_BFGFLASH2, + S_BLOOD1, + S_BLOOD2, + S_BLOOD3, + S_PUFF1, + S_PUFF2, + S_PUFF3, + S_PUFF4, + S_TBALL1, + S_TBALL2, + S_TBALLX1, + S_TBALLX2, + S_TBALLX3, + S_RBALL1, + S_RBALL2, + S_RBALLX1, + S_RBALLX2, + S_RBALLX3, + S_PLASBALL, + S_PLASBALL2, + S_PLASEXP, + S_PLASEXP2, + S_PLASEXP3, + S_PLASEXP4, + S_PLASEXP5, + S_ROCKET, + S_BFGSHOT, + S_BFGSHOT2, + S_BFGLAND, + S_BFGLAND2, + S_BFGLAND3, + S_BFGLAND4, + S_BFGLAND5, + S_BFGLAND6, + S_BFGEXP, + S_BFGEXP2, + S_BFGEXP3, + S_BFGEXP4, + S_EXPLODE1, + S_EXPLODE2, + S_EXPLODE3, + S_TFOG, + S_TFOG01, + S_TFOG02, + S_TFOG2, + S_TFOG3, + S_TFOG4, + S_TFOG5, + S_TFOG6, + S_TFOG7, + S_TFOG8, + S_TFOG9, + S_TFOG10, + S_IFOG, + S_IFOG01, + S_IFOG02, + S_IFOG2, + S_IFOG3, + S_IFOG4, + S_IFOG5, + S_PLAY, + S_PLAY_RUN1, + S_PLAY_RUN2, + S_PLAY_RUN3, + S_PLAY_RUN4, + S_PLAY_ATK1, + S_PLAY_ATK2, + S_PLAY_PAIN, + S_PLAY_PAIN2, + S_PLAY_DIE1, + S_PLAY_DIE2, + S_PLAY_DIE3, + S_PLAY_DIE4, + S_PLAY_DIE5, + S_PLAY_DIE6, + S_PLAY_DIE7, + S_PLAY_XDIE1, + S_PLAY_XDIE2, + S_PLAY_XDIE3, + S_PLAY_XDIE4, + S_PLAY_XDIE5, + S_PLAY_XDIE6, + S_PLAY_XDIE7, + S_PLAY_XDIE8, + S_PLAY_XDIE9, + S_POSS_STND, + S_POSS_STND2, + S_POSS_RUN1, + S_POSS_RUN2, + S_POSS_RUN3, + S_POSS_RUN4, + S_POSS_RUN5, + S_POSS_RUN6, + S_POSS_RUN7, + S_POSS_RUN8, + S_POSS_ATK1, + S_POSS_ATK2, + S_POSS_ATK3, + S_POSS_PAIN, + S_POSS_PAIN2, + S_POSS_DIE1, + S_POSS_DIE2, + S_POSS_DIE3, + S_POSS_DIE4, + S_POSS_DIE5, + S_POSS_XDIE1, + S_POSS_XDIE2, + S_POSS_XDIE3, + S_POSS_XDIE4, + S_POSS_XDIE5, + S_POSS_XDIE6, + S_POSS_XDIE7, + S_POSS_XDIE8, + S_POSS_XDIE9, + S_POSS_RAISE1, + S_POSS_RAISE2, + S_POSS_RAISE3, + S_POSS_RAISE4, + S_SPOS_STND, + S_SPOS_STND2, + S_SPOS_RUN1, + S_SPOS_RUN2, + S_SPOS_RUN3, + S_SPOS_RUN4, + S_SPOS_RUN5, + S_SPOS_RUN6, + S_SPOS_RUN7, + S_SPOS_RUN8, + S_SPOS_ATK1, + S_SPOS_ATK2, + S_SPOS_ATK3, + S_SPOS_PAIN, + S_SPOS_PAIN2, + S_SPOS_DIE1, + S_SPOS_DIE2, + S_SPOS_DIE3, + S_SPOS_DIE4, + S_SPOS_DIE5, + S_SPOS_XDIE1, + S_SPOS_XDIE2, + S_SPOS_XDIE3, + S_SPOS_XDIE4, + S_SPOS_XDIE5, + S_SPOS_XDIE6, + S_SPOS_XDIE7, + S_SPOS_XDIE8, + S_SPOS_XDIE9, + S_SPOS_RAISE1, + S_SPOS_RAISE2, + S_SPOS_RAISE3, + S_SPOS_RAISE4, + S_SPOS_RAISE5, + S_VILE_STND, + S_VILE_STND2, + S_VILE_RUN1, + S_VILE_RUN2, + S_VILE_RUN3, + S_VILE_RUN4, + S_VILE_RUN5, + S_VILE_RUN6, + S_VILE_RUN7, + S_VILE_RUN8, + S_VILE_RUN9, + S_VILE_RUN10, + S_VILE_RUN11, + S_VILE_RUN12, + S_VILE_ATK1, + S_VILE_ATK2, + S_VILE_ATK3, + S_VILE_ATK4, + S_VILE_ATK5, + S_VILE_ATK6, + S_VILE_ATK7, + S_VILE_ATK8, + S_VILE_ATK9, + S_VILE_ATK10, + S_VILE_ATK11, + S_VILE_HEAL1, + S_VILE_HEAL2, + S_VILE_HEAL3, + S_VILE_PAIN, + S_VILE_PAIN2, + S_VILE_DIE1, + S_VILE_DIE2, + S_VILE_DIE3, + S_VILE_DIE4, + S_VILE_DIE5, + S_VILE_DIE6, + S_VILE_DIE7, + S_VILE_DIE8, + S_VILE_DIE9, + S_VILE_DIE10, + S_FIRE1, + S_FIRE2, + S_FIRE3, + S_FIRE4, + S_FIRE5, + S_FIRE6, + S_FIRE7, + S_FIRE8, + S_FIRE9, + S_FIRE10, + S_FIRE11, + S_FIRE12, + S_FIRE13, + S_FIRE14, + S_FIRE15, + S_FIRE16, + S_FIRE17, + S_FIRE18, + S_FIRE19, + S_FIRE20, + S_FIRE21, + S_FIRE22, + S_FIRE23, + S_FIRE24, + S_FIRE25, + S_FIRE26, + S_FIRE27, + S_FIRE28, + S_FIRE29, + S_FIRE30, + S_SMOKE1, + S_SMOKE2, + S_SMOKE3, + S_SMOKE4, + S_SMOKE5, + S_TRACER, + S_TRACER2, + S_TRACEEXP1, + S_TRACEEXP2, + S_TRACEEXP3, + S_SKEL_STND, + S_SKEL_STND2, + S_SKEL_RUN1, + S_SKEL_RUN2, + S_SKEL_RUN3, + S_SKEL_RUN4, + S_SKEL_RUN5, + S_SKEL_RUN6, + S_SKEL_RUN7, + S_SKEL_RUN8, + S_SKEL_RUN9, + S_SKEL_RUN10, + S_SKEL_RUN11, + S_SKEL_RUN12, + S_SKEL_FIST1, + S_SKEL_FIST2, + S_SKEL_FIST3, + S_SKEL_FIST4, + S_SKEL_MISS1, + S_SKEL_MISS2, + S_SKEL_MISS3, + S_SKEL_MISS4, + S_SKEL_PAIN, + S_SKEL_PAIN2, + S_SKEL_DIE1, + S_SKEL_DIE2, + S_SKEL_DIE3, + S_SKEL_DIE4, + S_SKEL_DIE5, + S_SKEL_DIE6, + S_SKEL_RAISE1, + S_SKEL_RAISE2, + S_SKEL_RAISE3, + S_SKEL_RAISE4, + S_SKEL_RAISE5, + S_SKEL_RAISE6, + S_FATSHOT1, + S_FATSHOT2, + S_FATSHOTX1, + S_FATSHOTX2, + S_FATSHOTX3, + S_FATT_STND, + S_FATT_STND2, + S_FATT_RUN1, + S_FATT_RUN2, + S_FATT_RUN3, + S_FATT_RUN4, + S_FATT_RUN5, + S_FATT_RUN6, + S_FATT_RUN7, + S_FATT_RUN8, + S_FATT_RUN9, + S_FATT_RUN10, + S_FATT_RUN11, + S_FATT_RUN12, + S_FATT_ATK1, + S_FATT_ATK2, + S_FATT_ATK3, + S_FATT_ATK4, + S_FATT_ATK5, + S_FATT_ATK6, + S_FATT_ATK7, + S_FATT_ATK8, + S_FATT_ATK9, + S_FATT_ATK10, + S_FATT_PAIN, + S_FATT_PAIN2, + S_FATT_DIE1, + S_FATT_DIE2, + S_FATT_DIE3, + S_FATT_DIE4, + S_FATT_DIE5, + S_FATT_DIE6, + S_FATT_DIE7, + S_FATT_DIE8, + S_FATT_DIE9, + S_FATT_DIE10, + S_FATT_RAISE1, + S_FATT_RAISE2, + S_FATT_RAISE3, + S_FATT_RAISE4, + S_FATT_RAISE5, + S_FATT_RAISE6, + S_FATT_RAISE7, + S_FATT_RAISE8, + S_CPOS_STND, + S_CPOS_STND2, + S_CPOS_RUN1, + S_CPOS_RUN2, + S_CPOS_RUN3, + S_CPOS_RUN4, + S_CPOS_RUN5, + S_CPOS_RUN6, + S_CPOS_RUN7, + S_CPOS_RUN8, + S_CPOS_ATK1, + S_CPOS_ATK2, + S_CPOS_ATK3, + S_CPOS_ATK4, + S_CPOS_PAIN, + S_CPOS_PAIN2, + S_CPOS_DIE1, + S_CPOS_DIE2, + S_CPOS_DIE3, + S_CPOS_DIE4, + S_CPOS_DIE5, + S_CPOS_DIE6, + S_CPOS_DIE7, + S_CPOS_XDIE1, + S_CPOS_XDIE2, + S_CPOS_XDIE3, + S_CPOS_XDIE4, + S_CPOS_XDIE5, + S_CPOS_XDIE6, + S_CPOS_RAISE1, + S_CPOS_RAISE2, + S_CPOS_RAISE3, + S_CPOS_RAISE4, + S_CPOS_RAISE5, + S_CPOS_RAISE6, + S_CPOS_RAISE7, + S_TROO_STND, + S_TROO_STND2, + S_TROO_RUN1, + S_TROO_RUN2, + S_TROO_RUN3, + S_TROO_RUN4, + S_TROO_RUN5, + S_TROO_RUN6, + S_TROO_RUN7, + S_TROO_RUN8, + S_TROO_ATK1, + S_TROO_ATK2, + S_TROO_ATK3, + S_TROO_PAIN, + S_TROO_PAIN2, + S_TROO_DIE1, + S_TROO_DIE2, + S_TROO_DIE3, + S_TROO_DIE4, + S_TROO_DIE5, + S_TROO_XDIE1, + S_TROO_XDIE2, + S_TROO_XDIE3, + S_TROO_XDIE4, + S_TROO_XDIE5, + S_TROO_XDIE6, + S_TROO_XDIE7, + S_TROO_XDIE8, + S_TROO_RAISE1, + S_TROO_RAISE2, + S_TROO_RAISE3, + S_TROO_RAISE4, + S_TROO_RAISE5, + S_SARG_STND, + S_SARG_STND2, + S_SARG_RUN1, + S_SARG_RUN2, + S_SARG_RUN3, + S_SARG_RUN4, + S_SARG_RUN5, + S_SARG_RUN6, + S_SARG_RUN7, + S_SARG_RUN8, + S_SARG_ATK1, + S_SARG_ATK2, + S_SARG_ATK3, + S_SARG_PAIN, + S_SARG_PAIN2, + S_SARG_DIE1, + S_SARG_DIE2, + S_SARG_DIE3, + S_SARG_DIE4, + S_SARG_DIE5, + S_SARG_DIE6, + S_SARG_RAISE1, + S_SARG_RAISE2, + S_SARG_RAISE3, + S_SARG_RAISE4, + S_SARG_RAISE5, + S_SARG_RAISE6, + S_HEAD_STND, + S_HEAD_RUN1, + S_HEAD_ATK1, + S_HEAD_ATK2, + S_HEAD_ATK3, + S_HEAD_PAIN, + S_HEAD_PAIN2, + S_HEAD_PAIN3, + S_HEAD_DIE1, + S_HEAD_DIE2, + S_HEAD_DIE3, + S_HEAD_DIE4, + S_HEAD_DIE5, + S_HEAD_DIE6, + S_HEAD_RAISE1, + S_HEAD_RAISE2, + S_HEAD_RAISE3, + S_HEAD_RAISE4, + S_HEAD_RAISE5, + S_HEAD_RAISE6, + S_BRBALL1, + S_BRBALL2, + S_BRBALLX1, + S_BRBALLX2, + S_BRBALLX3, + S_BOSS_STND, + S_BOSS_STND2, + S_BOSS_RUN1, + S_BOSS_RUN2, + S_BOSS_RUN3, + S_BOSS_RUN4, + S_BOSS_RUN5, + S_BOSS_RUN6, + S_BOSS_RUN7, + S_BOSS_RUN8, + S_BOSS_ATK1, + S_BOSS_ATK2, + S_BOSS_ATK3, + S_BOSS_PAIN, + S_BOSS_PAIN2, + S_BOSS_DIE1, + S_BOSS_DIE2, + S_BOSS_DIE3, + S_BOSS_DIE4, + S_BOSS_DIE5, + S_BOSS_DIE6, + S_BOSS_DIE7, + S_BOSS_RAISE1, + S_BOSS_RAISE2, + S_BOSS_RAISE3, + S_BOSS_RAISE4, + S_BOSS_RAISE5, + S_BOSS_RAISE6, + S_BOSS_RAISE7, + S_BOS2_STND, + S_BOS2_STND2, + S_BOS2_RUN1, + S_BOS2_RUN2, + S_BOS2_RUN3, + S_BOS2_RUN4, + S_BOS2_RUN5, + S_BOS2_RUN6, + S_BOS2_RUN7, + S_BOS2_RUN8, + S_BOS2_ATK1, + S_BOS2_ATK2, + S_BOS2_ATK3, + S_BOS2_PAIN, + S_BOS2_PAIN2, + S_BOS2_DIE1, + S_BOS2_DIE2, + S_BOS2_DIE3, + S_BOS2_DIE4, + S_BOS2_DIE5, + S_BOS2_DIE6, + S_BOS2_DIE7, + S_BOS2_RAISE1, + S_BOS2_RAISE2, + S_BOS2_RAISE3, + S_BOS2_RAISE4, + S_BOS2_RAISE5, + S_BOS2_RAISE6, + S_BOS2_RAISE7, + S_SKULL_STND, + S_SKULL_STND2, + S_SKULL_RUN1, + S_SKULL_RUN2, + S_SKULL_ATK1, + S_SKULL_ATK2, + S_SKULL_ATK3, + S_SKULL_ATK4, + S_SKULL_PAIN, + S_SKULL_PAIN2, + S_SKULL_DIE1, + S_SKULL_DIE2, + S_SKULL_DIE3, + S_SKULL_DIE4, + S_SKULL_DIE5, + S_SKULL_DIE6, + S_SPID_STND, + S_SPID_STND2, + S_SPID_RUN1, + S_SPID_RUN2, + S_SPID_RUN3, + S_SPID_RUN4, + S_SPID_RUN5, + S_SPID_RUN6, + S_SPID_RUN7, + S_SPID_RUN8, + S_SPID_RUN9, + S_SPID_RUN10, + S_SPID_RUN11, + S_SPID_RUN12, + S_SPID_ATK1, + S_SPID_ATK2, + S_SPID_ATK3, + S_SPID_ATK4, + S_SPID_PAIN, + S_SPID_PAIN2, + S_SPID_DIE1, + S_SPID_DIE2, + S_SPID_DIE3, + S_SPID_DIE4, + S_SPID_DIE5, + S_SPID_DIE6, + S_SPID_DIE7, + S_SPID_DIE8, + S_SPID_DIE9, + S_SPID_DIE10, + S_SPID_DIE11, + S_BSPI_STND, + S_BSPI_STND2, + S_BSPI_SIGHT, + S_BSPI_RUN1, + S_BSPI_RUN2, + S_BSPI_RUN3, + S_BSPI_RUN4, + S_BSPI_RUN5, + S_BSPI_RUN6, + S_BSPI_RUN7, + S_BSPI_RUN8, + S_BSPI_RUN9, + S_BSPI_RUN10, + S_BSPI_RUN11, + S_BSPI_RUN12, + S_BSPI_ATK1, + S_BSPI_ATK2, + S_BSPI_ATK3, + S_BSPI_ATK4, + S_BSPI_PAIN, + S_BSPI_PAIN2, + S_BSPI_DIE1, + S_BSPI_DIE2, + S_BSPI_DIE3, + S_BSPI_DIE4, + S_BSPI_DIE5, + S_BSPI_DIE6, + S_BSPI_DIE7, + S_BSPI_RAISE1, + S_BSPI_RAISE2, + S_BSPI_RAISE3, + S_BSPI_RAISE4, + S_BSPI_RAISE5, + S_BSPI_RAISE6, + S_BSPI_RAISE7, + S_ARACH_PLAZ, + S_ARACH_PLAZ2, + S_ARACH_PLEX, + S_ARACH_PLEX2, + S_ARACH_PLEX3, + S_ARACH_PLEX4, + S_ARACH_PLEX5, + S_CYBER_STND, + S_CYBER_STND2, + S_CYBER_RUN1, + S_CYBER_RUN2, + S_CYBER_RUN3, + S_CYBER_RUN4, + S_CYBER_RUN5, + S_CYBER_RUN6, + S_CYBER_RUN7, + S_CYBER_RUN8, + S_CYBER_ATK1, + S_CYBER_ATK2, + S_CYBER_ATK3, + S_CYBER_ATK4, + S_CYBER_ATK5, + S_CYBER_ATK6, + S_CYBER_PAIN, + S_CYBER_DIE1, + S_CYBER_DIE2, + S_CYBER_DIE3, + S_CYBER_DIE4, + S_CYBER_DIE5, + S_CYBER_DIE6, + S_CYBER_DIE7, + S_CYBER_DIE8, + S_CYBER_DIE9, + S_CYBER_DIE10, + S_PAIN_STND, + S_PAIN_RUN1, + S_PAIN_RUN2, + S_PAIN_RUN3, + S_PAIN_RUN4, + S_PAIN_RUN5, + S_PAIN_RUN6, + S_PAIN_ATK1, + S_PAIN_ATK2, + S_PAIN_ATK3, + S_PAIN_ATK4, + S_PAIN_PAIN, + S_PAIN_PAIN2, + S_PAIN_DIE1, + S_PAIN_DIE2, + S_PAIN_DIE3, + S_PAIN_DIE4, + S_PAIN_DIE5, + S_PAIN_DIE6, + S_PAIN_RAISE1, + S_PAIN_RAISE2, + S_PAIN_RAISE3, + S_PAIN_RAISE4, + S_PAIN_RAISE5, + S_PAIN_RAISE6, + S_SSWV_STND, + S_SSWV_STND2, + S_SSWV_RUN1, + S_SSWV_RUN2, + S_SSWV_RUN3, + S_SSWV_RUN4, + S_SSWV_RUN5, + S_SSWV_RUN6, + S_SSWV_RUN7, + S_SSWV_RUN8, + S_SSWV_ATK1, + S_SSWV_ATK2, + S_SSWV_ATK3, + S_SSWV_ATK4, + S_SSWV_ATK5, + S_SSWV_ATK6, + S_SSWV_PAIN, + S_SSWV_PAIN2, + S_SSWV_DIE1, + S_SSWV_DIE2, + S_SSWV_DIE3, + S_SSWV_DIE4, + S_SSWV_DIE5, + S_SSWV_XDIE1, + S_SSWV_XDIE2, + S_SSWV_XDIE3, + S_SSWV_XDIE4, + S_SSWV_XDIE5, + S_SSWV_XDIE6, + S_SSWV_XDIE7, + S_SSWV_XDIE8, + S_SSWV_XDIE9, + S_SSWV_RAISE1, + S_SSWV_RAISE2, + S_SSWV_RAISE3, + S_SSWV_RAISE4, + S_SSWV_RAISE5, + S_KEENSTND, + S_COMMKEEN, + S_COMMKEEN2, + S_COMMKEEN3, + S_COMMKEEN4, + S_COMMKEEN5, + S_COMMKEEN6, + S_COMMKEEN7, + S_COMMKEEN8, + S_COMMKEEN9, + S_COMMKEEN10, + S_COMMKEEN11, + S_COMMKEEN12, + S_KEENPAIN, + S_KEENPAIN2, + S_BRAIN, + S_BRAIN_PAIN, + S_BRAIN_DIE1, + S_BRAIN_DIE2, + S_BRAIN_DIE3, + S_BRAIN_DIE4, + S_BRAINEYE, + S_BRAINEYESEE, + S_BRAINEYE1, + S_SPAWN1, + S_SPAWN2, + S_SPAWN3, + S_SPAWN4, + S_SPAWNFIRE1, + S_SPAWNFIRE2, + S_SPAWNFIRE3, + S_SPAWNFIRE4, + S_SPAWNFIRE5, + S_SPAWNFIRE6, + S_SPAWNFIRE7, + S_SPAWNFIRE8, + S_BRAINEXPLODE1, + S_BRAINEXPLODE2, + S_BRAINEXPLODE3, + S_ARM1, + S_ARM1A, + S_ARM2, + S_ARM2A, + S_BAR1, + S_BAR2, + S_BEXP, + S_BEXP2, + S_BEXP3, + S_BEXP4, + S_BEXP5, + S_BBAR1, + S_BBAR2, + S_BBAR3, + S_BON1, + S_BON1A, + S_BON1B, + S_BON1C, + S_BON1D, + S_BON1E, + S_BON2, + S_BON2A, + S_BON2B, + S_BON2C, + S_BON2D, + S_BON2E, + S_BKEY, + S_BKEY2, + S_RKEY, + S_RKEY2, + S_YKEY, + S_YKEY2, + S_BSKULL, + S_BSKULL2, + S_RSKULL, + S_RSKULL2, + S_YSKULL, + S_YSKULL2, + S_STIM, + S_MEDI, + S_SOUL, + S_SOUL2, + S_SOUL3, + S_SOUL4, + S_SOUL5, + S_SOUL6, + S_PINV, + S_PINV2, + S_PINV3, + S_PINV4, + S_PSTR, + S_PINS, + S_PINS2, + S_PINS3, + S_PINS4, + S_MEGA, + S_MEGA2, + S_MEGA3, + S_MEGA4, + S_SUIT, + S_PMAP, + S_PMAP2, + S_PMAP3, + S_PMAP4, + S_PMAP5, + S_PMAP6, + S_PVIS, + S_PVIS2, + S_CLIP, + S_AMMO, + S_ROCK, + S_BROK, + S_CELL, + S_CELP, + S_SHEL, + S_SBOX, + S_BPAK, + S_BFUG, + S_MGUN, + S_CSAW, + S_LAUN, + S_PLAS, + S_SHOT, + S_SHOT2, + S_COLU, + S_STALAG, + S_BLOODYTWITCH, + S_BLOODYTWITCH2, + S_BLOODYTWITCH3, + S_BLOODYTWITCH4, + S_DEADTORSO, + S_DEADBOTTOM, + S_HEADSONSTICK, + S_GIBS, + S_HEADONASTICK, + S_HEADCANDLES, + S_HEADCANDLES2, + S_DEADSTICK, + S_LIVESTICK, + S_LIVESTICK2, + S_MEAT2, + S_MEAT3, + S_MEAT4, + S_MEAT5, + S_STALAGTITE, + S_TALLGRNCOL, + S_SHRTGRNCOL, + S_TALLREDCOL, + S_SHRTREDCOL, + S_CANDLESTIK, + S_CANDELABRA, + S_SKULLCOL, + S_TORCHTREE, + S_BIGTREE, + S_TECHPILLAR, + S_EVILEYE, + S_EVILEYE2, + S_EVILEYE3, + S_EVILEYE4, + S_FLOATSKULL, + S_FLOATSKULL2, + S_FLOATSKULL3, + S_HEARTCOL, + S_HEARTCOL2, + S_BLUETORCH, + S_BLUETORCH2, + S_BLUETORCH3, + S_BLUETORCH4, + S_GREENTORCH, + S_GREENTORCH2, + S_GREENTORCH3, + S_GREENTORCH4, + S_REDTORCH, + S_REDTORCH2, + S_REDTORCH3, + S_REDTORCH4, + S_BTORCHSHRT, + S_BTORCHSHRT2, + S_BTORCHSHRT3, + S_BTORCHSHRT4, + S_GTORCHSHRT, + S_GTORCHSHRT2, + S_GTORCHSHRT3, + S_GTORCHSHRT4, + S_RTORCHSHRT, + S_RTORCHSHRT2, + S_RTORCHSHRT3, + S_RTORCHSHRT4, + S_HANGNOGUTS, + S_HANGBNOBRAIN, + S_HANGTLOOKDN, + S_HANGTSKULL, + S_HANGTLOOKUP, + S_HANGTNOBRAIN, + S_COLONGIBS, + S_SMALLPOOL, + S_BRAINSTEM, + S_TECHLAMP, + S_TECHLAMP2, + S_TECHLAMP3, + S_TECHLAMP4, + S_TECH2LAMP, + S_TECH2LAMP2, + S_TECH2LAMP3, + S_TECH2LAMP4, + NUMSTATES +} statenum_t; + +typedef struct { + spritenum_t sprite; + int frame; + int tics; + // void (*action) (); + actionf_t action; + statenum_t nextstate; + int misc1; + int misc2; +} state_t; + +extern state_t states[NUMSTATES]; +extern char *sprnames[]; + +typedef enum { + MT_PLAYER, + MT_POSSESSED, + MT_SHOTGUY, + MT_VILE, + MT_FIRE, + MT_UNDEAD, + MT_TRACER, + MT_SMOKE, + MT_FATSO, + MT_FATSHOT, + MT_CHAINGUY, + MT_TROOP, + MT_SERGEANT, + MT_SHADOWS, + MT_HEAD, + MT_BRUISER, + MT_BRUISERSHOT, + MT_KNIGHT, + MT_SKULL, + MT_SPIDER, + MT_BABY, + MT_CYBORG, + MT_PAIN, + MT_WOLFSS, + MT_KEEN, + MT_BOSSBRAIN, + MT_BOSSSPIT, + MT_BOSSTARGET, + MT_SPAWNSHOT, + MT_SPAWNFIRE, + MT_BARREL, + MT_TROOPSHOT, + MT_HEADSHOT, + MT_ROCKET, + MT_PLASMA, + MT_BFG, + MT_ARACHPLAZ, + MT_PUFF, + MT_BLOOD, + MT_TFOG, + MT_IFOG, + MT_TELEPORTMAN, + MT_EXTRABFG, + MT_MISC0, + MT_MISC1, + MT_MISC2, + MT_MISC3, + MT_MISC4, + MT_MISC5, + MT_MISC6, + MT_MISC7, + MT_MISC8, + MT_MISC9, + MT_MISC10, + MT_MISC11, + MT_MISC12, + MT_INV, + MT_MISC13, + MT_INS, + MT_MISC14, + MT_MISC15, + MT_MISC16, + MT_MEGA, + MT_CLIP, + MT_MISC17, + MT_MISC18, + MT_MISC19, + MT_MISC20, + MT_MISC21, + MT_MISC22, + MT_MISC23, + MT_MISC24, + MT_MISC25, + MT_CHAINGUN, + MT_MISC26, + MT_MISC27, + MT_MISC28, + MT_SHOTGUN, + MT_SUPERSHOTGUN, + MT_MISC29, + MT_MISC30, + MT_MISC31, + MT_MISC32, + MT_MISC33, + MT_MISC34, + MT_MISC35, + MT_MISC36, + MT_MISC37, + MT_MISC38, + MT_MISC39, + MT_MISC40, + MT_MISC41, + MT_MISC42, + MT_MISC43, + MT_MISC44, + MT_MISC45, + MT_MISC46, + MT_MISC47, + MT_MISC48, + MT_MISC49, + MT_MISC50, + MT_MISC51, + MT_MISC52, + MT_MISC53, + MT_MISC54, + MT_MISC55, + MT_MISC56, + MT_MISC57, + MT_MISC58, + MT_MISC59, + MT_MISC60, + MT_MISC61, + MT_MISC62, + MT_MISC63, + MT_MISC64, + MT_MISC65, + MT_MISC66, + MT_MISC67, + MT_MISC68, + MT_MISC69, + MT_MISC70, + MT_MISC71, + MT_MISC72, + MT_MISC73, + MT_MISC74, + MT_MISC75, + MT_MISC76, + MT_MISC77, + MT_MISC78, + MT_MISC79, + MT_MISC80, + MT_MISC81, + MT_MISC82, + MT_MISC83, + MT_MISC84, + MT_MISC85, + MT_MISC86, + NUMMOBJTYPES + +} mobjtype_t; + +typedef struct { + int doomednum; + int spawnstate; + int spawnhealth; + int seestate; + int seesound; + int reactiontime; + int attacksound; + int painstate; + int painchance; + int painsound; + int meleestate; + int missilestate; + int deathstate; + int xdeathstate; + int deathsound; + int speed; + int radius; + int height; + int mass; + int damage; + int activesound; + int flags; + int raisestate; + +} mobjinfo_t; + +extern mobjinfo_t mobjinfo[NUMMOBJTYPES]; + +#endif diff --git a/client/src/ipu/Makefile b/client/src/ipu/Makefile new file mode 100644 index 0000000..0a43a55 --- /dev/null +++ b/client/src/ipu/Makefile @@ -0,0 +1,16 @@ + +GARPHOBJS = AM_map.gp # m_fixed.gp + +all: ipu_rt.gp + +ipu_rt.gp: $(GARPHOBJS) ipu_vertices.gp + popc $(GARPHOBJS) ipu_vertices.gp -o $@ + +ipu_vertices.gp: ipu_vertices.cpp + popc -Os $^ -o $@ + +%.gp: %.c + popc -Os $^ -o $@ + +clean: + rm $(GARPHOBJS) ipu_vertices.gp \ No newline at end of file diff --git a/client/src/ipu/aes_prng.h b/client/src/ipu/aes_prng.h new file mode 100644 index 0000000..f255db1 --- /dev/null +++ b/client/src/ipu/aes_prng.h @@ -0,0 +1,32 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Pseudo-random number generator for secure demos. +// + +#ifndef __AES_PRNG_H__ +#define __AES_PRNG_H__ + +#include "doomtype.h" + +// Nonce value used as random seed for secure demos. + +typedef byte prng_seed_t[16]; + +void PRNG_Start(prng_seed_t seed); +void PRNG_Stop(void); +unsigned int PRNG_Random(void); + +#endif /* #ifndef __AES_PRNG_H__ */ + diff --git a/client/src/ipu/am_map.c b/client/src/ipu/am_map.c new file mode 100644 index 0000000..9bda67a --- /dev/null +++ b/client/src/ipu/am_map.c @@ -0,0 +1,1129 @@ + +// State. +#include "doomstat.h" +#include "m_controls.h" +#include "m_fixed.h" +#include "p_local.h" +#include "r_state.h" +#include "st_stuff.h" +#include "tables.h" +#include "v_video.h" + +#include "ipu_print.h" +#include "ipu_interface.h" + + +// For use if I do walls with outsides/insides +#define REDS (256 - 5 * 16) +#define REDRANGE 16 +#define BLUES (256 - 4 * 16 + 8) +#define BLUERANGE 8 +#define GREENS (7 * 16) +#define GREENRANGE 16 +#define GRAYS (6 * 16) +#define GRAYSRANGE 16 +#define BROWNS (4 * 16) +#define BROWNRANGE 16 +#define YELLOWS (256 - 32 + 7) +#define YELLOWRANGE 1 +#define BLACK 0 +#define WHITE (256 - 47) + +// Automap colors +#define BACKGROUND BLACK +#define YOURCOLORS WHITE +#define YOURRANGE 0 +#define WALLCOLORS REDS +#define WALLRANGE REDRANGE +#define TSWALLCOLORS GRAYS +#define TSWALLRANGE GRAYSRANGE +#define FDWALLCOLORS BROWNS +#define FDWALLRANGE BROWNRANGE +#define CDWALLCOLORS YELLOWS +#define CDWALLRANGE YELLOWRANGE +#define THINGCOLORS GREENS +#define THINGRANGE GREENRANGE +#define SECRETWALLCOLORS WALLCOLORS +#define SECRETWALLRANGE WALLRANGE +#define GRIDCOLORS (GRAYS + GRAYSRANGE / 2) +#define GRIDRANGE 0 +#define XHAIRCOLORS GRAYS + +// drawing stuff + +#define AM_NUMMARKPOINTS 10 + +// 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)) + +// translates between frame-buffer and map distances +#define FTOM(x) FixedMul(((x) << FRACBITS), scale_ftom) +#define MTOF(x) (FixedMul((x), scale_mtof) >> FRACBITS) +// 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))) + +// the following is crap +#define LINE_NEVERSEE ML_DONTDRAW + +typedef struct { int x, y; } fpoint_t; + +typedef struct { fpoint_t a, b; } fline_t; + +typedef struct { fixed_t x, y; } mpoint_t; + +typedef struct { mpoint_t a, b; } mline_t; + +typedef struct { fixed_t slp, islp; } islope_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 R ((8 * PLAYERRADIUS) / 7) +mline_t cheat_player_arrow[] = { + {{-R + R / 8, 0}, {R, 0}}, // ----- + {{R, 0}, {R - R / 2, R / 6}}, // -----> + {{R, 0}, {R - R / 2, -R / 6}}, + {{-R + R / 8, 0}, {-R - R / 8, R / 6}}, // >-----> + {{-R + R / 8, 0}, {-R - R / 8, -R / 6}}, + {{-R + 3 * R / 8, 0}, {-R + R / 8, R / 6}}, // >>-----> + {{-R + 3 * R / 8, 0}, {-R + R / 8, -R / 6}}, + {{-R / 2, 0}, {-R / 2, -R / 6}}, // >>-d---> + {{-R / 2, -R / 6}, {-R / 2 + R / 6, -R / 6}}, + {{-R / 2 + R / 6, -R / 6}, {-R / 2 + R / 6, R / 4}}, + {{-R / 6, 0}, {-R / 6, -R / 6}}, // >>-dd--> + {{-R / 6, -R / 6}, {0, -R / 6}}, + {{0, -R / 6}, {0, R / 4}}, + {{R / 6, R / 4}, {R / 6, -R / 7}}, // >>-ddt-> + {{R / 6, -R / 7}, {R / 6 + R / 32, -R / 7 - R / 32}}, + {{R / 6 + R / 32, -R / 7 - R / 32}, {R / 6 + R / 10, -R / 7}}}; +#undef R + +static const fline_t ipu_watermark_lines[] = { + {{0, 0}, {0, 5}}, // I + {{3, 0}, {3, 5}}, // P + {{6, 0}, {6, 3}}, + {{3, 0}, {6, 0}}, + {{3, 3}, {6, 3}}, + {{9, 0}, {9, 5}}, // U + {{12, 0}, {12, 5}}, + {{9, 5}, {12, 5}}, + {{17, 5}, {17, 0}}, // M + {{17, 0}, {19, 3}}, + {{21, 0}, {19, 3}}, + {{21, 0}, {21, 5}}, + {{24, 0}, {24, 5}}, // A + {{27, 0}, {27, 5}}, + {{24, 0}, {27, 0}}, + {{24, 3}, {27, 3}}, + {{30, 0}, {30, 5}}, // P + {{33, 0}, {33, 3}}, + {{30, 0}, {33, 0}}, + {{30, 3}, {33, 3}}, +}; + +#define R (FRACUNIT) +mline_t triangle_guy[] = { + {{(fixed_t)(-.867 * R), (fixed_t)(-.5 * R)}, + {(fixed_t)(.867 * R), (fixed_t)(-.5 * R)}}, + {{(fixed_t)(.867 * R), (fixed_t)(-.5 * R)}, {(fixed_t)(0), (fixed_t)(R)}}, + {{(fixed_t)(0), (fixed_t)(R)}, {(fixed_t)(-.867 * R), (fixed_t)(-.5 * R)}}}; +#undef R + +#define R (FRACUNIT) +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 + +static int cheating = 0; +static int grid = 0; + +static int leveljuststarted = 1; // kluge until AM_LevelInit() is called + +boolean automapactive = false; +static int finit_width = SCREENWIDTH; +static int finit_height = SCREENHEIGHT - ST_HEIGHT; + +// 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 int lightlev; // used for funky strobing effect +static pixel_t *fb; // pseudo-frame buffer +static int amclock; + +static mpoint_t m_paninc; // how far the window pans each tic (map coords) +static fixed_t + mtof_zoommul; // how far the window zooms in each tic (map coords) +static fixed_t ftom_zoommul; // how far the window zooms in each tic (fb coords) + +static fixed_t m_x, m_y; // LL x,y where the window is on the map (map coords) +static fixed_t m_x2, m_y2; // UR x,y where the window is 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; + +// JOSEF: Map only uses player position, so don't need reference to full +// player object (which will be on a different IPU tile). +// May need mechanism for sending plr->message to rendering tile... LATER +// static player_t *plr; // the player represented by an arrow +IPUPlayerPos_t am_playerpos; + +patch_t *marknums[10]; // numbers used for marking by the automap +unsigned char markbuf[IPUAMMARKBUFSIZE]; // JOSEF: static mark patch storage, avoiding disk +static mpoint_t markpoints[AM_NUMMARKPOINTS]; // where the points are +static int markpointnum = 0; // next point to be assigned + +static int followplayer = 1; // specifies whether to follow the player around + +cheatseq_t cheat_amap = CHEAT("iddt", 0); + +static boolean stopped = true; + +// +// +// +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; +} + +// +// +// +void AM_saveScaleAndLoc(void) { + old_m_x = m_x; + old_m_y = m_y; + old_m_w = m_w; + old_m_h = m_h; +} + +// +// +// +void AM_restoreScaleAndLoc(void) { + + m_w = old_m_w; + m_h = old_m_h; + if (!followplayer) { + m_x = old_m_x; + m_y = old_m_y; + } else { + m_x = am_playerpos.x - m_w / 2; + m_y = am_playerpos.y - m_h / 2; + } + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + + // Change the scaling multipliers + scale_mtof = FixedDiv(f_w << FRACBITS, m_w); + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); +} + +// +// adds a marker at the current location +// +void AM_addMark(void) { + markpoints[markpointnum].x = m_x + m_w / 2; + markpoints[markpointnum].y = m_y + m_h / 2; + markpointnum = (markpointnum + 1) % AM_NUMMARKPOINTS; +} + +// +// Determines bounding box of all vertices, +// sets global variables controlling zoom range. +// +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 < numvertexes; i++) { + if (vertexes[i].x < min_x) + min_x = vertexes[i].x; + else if (vertexes[i].x > 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 - min_x; + max_h = max_y - min_y; + + min_w = 2 * PLAYERRADIUS; // const? never changed? + min_h = 2 * PLAYERRADIUS; + + a = FixedDiv(f_w << FRACBITS, max_w); + b = FixedDiv(f_h << FRACBITS, max_h); + + min_scale_mtof = a < b ? a : b; + max_scale_mtof = FixedDiv(f_h << FRACBITS, 2 * PLAYERRADIUS); +} + +// +// +// +void AM_changeWindowLoc(void) { + if (m_paninc.x || m_paninc.y) { + followplayer = 0; + f_oldloc.x = INT_MAX; + } + + m_x += m_paninc.x; + m_y += m_paninc.y; + + if (m_x + m_w / 2 > 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; +} + +// +// +// +void AM_initVariables(void) { + int pnum; + // static event_t st_notify = {ev_keyup, AM_MSGENTERED, 0, 0}; // LATER + + automapactive = true; + // fb = I_VideoBuffer; // JOSEF, done in AM_Drawer + + f_oldloc.x = INT_MAX; + amclock = 0; + lightlev = 0; + + 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 + /* JOSEF: Don't support multiplayer + if (playeringame[consoleplayer]) { + plr = &players[consoleplayer]; + } else { + plr = &players[0]; + + for (pnum = 0; pnum < MAXPLAYERS; pnum++) { + if (playeringame[pnum]) { + plr = &players[pnum]; + break; + } + } + } + */ + + + m_x = am_playerpos.x - m_w / 2; + m_y = am_playerpos.y - m_h / 2; + 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); // JOSEF +} + + +void AM_clearMarks(void) { + int i; + + for (i = 0; i < AM_NUMMARKPOINTS; i++) + markpoints[i].x = -1; // means empty + markpointnum = 0; +} + +// +// should be called at the start of every level +// right now, i figure it out myself +// +void AM_LevelInit(void) { + leveljuststarted = 0; + + f_x = f_y = 0; + f_w = finit_width; + f_h = finit_height; + + AM_clearMarks(); + + 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); +} + + +// +// +// +void AM_Stop(void) { + // static event_t st_notify = {0, ev_keyup, AM_MSGEXITED, 0}; // LATER + + // AM_unloadPics(); // JOSEF: pics loaded once on startup + automapactive = false; + // ST_Responder(&st_notify); // LATER + stopped = true; +} + +// +// +// +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(); // JOSEF: pics loaded once on startup + ipuprint("Starting automap"); +} + +// +// set the window scale to the maximum size +// +void AM_minOutWindowScale(void) { + scale_mtof = min_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + AM_activateNewScale(); +} + +// +// set the window scale to the minimum size +// +void AM_maxOutWindowScale(void) { + scale_mtof = max_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + AM_activateNewScale(); +} + +// +// Handle events (user inputs) in automap mode +// +boolean AM_Responder(event_t *ev) { + + int rc; + static int bigstate = 0; + static char buffer[20]; + int key; + rc = false; + + /* JOSEF: No Joystick support + if (ev->type == ev_joystick && joybautomap >= 0 && + (ev->data1 & (1 << joybautomap)) != 0) { + joywait = I_GetTime() + 5; + + if (!automapactive) { + AM_Start(); + viewactive = false; + } else { + bigstate = 0; + viewactive = true; + AM_Stop(); + } + + return true; + } + */ + + if (!automapactive) { + if (ev->type == ev_keydown && ev->data1 == key_map_toggle) { + AM_Start(); + viewactive = false; + rc = true; + } + } + else if (ev->type == ev_keydown) { + rc = true; + key = ev->data1; + + + if (key == key_map_east) // pan right + { + if (!followplayer) + m_paninc.x = FTOM(F_PANINC); + else + rc = false; + } else if (key == key_map_west) // pan left + { + if (!followplayer) + m_paninc.x = -FTOM(F_PANINC); + else + rc = false; + } else if (key == key_map_north) // pan up + { + if (!followplayer) + m_paninc.y = FTOM(F_PANINC); + else + rc = false; + } else if (key == key_map_south) // pan down + { + if (!followplayer) + m_paninc.y = -FTOM(F_PANINC); + else + rc = false; + } else if (key == key_map_zoomout) // zoom out + { + mtof_zoommul = M_ZOOMOUT; + ftom_zoommul = M_ZOOMIN; + } else if (key == key_map_zoomin) // zoom in + { + mtof_zoommul = M_ZOOMIN; + ftom_zoommul = M_ZOOMOUT; + } else if (key == key_map_toggle) { + bigstate = 0; + viewactive = true; + AM_Stop(); + } else if (key == key_map_maxzoom) { + bigstate = !bigstate; + if (bigstate) { + AM_saveScaleAndLoc(); + AM_minOutWindowScale(); + } else + AM_restoreScaleAndLoc(); + } else if (key == key_map_follow) { + followplayer = !followplayer; + f_oldloc.x = INT_MAX; + /* LATER + if (followplayer) + plr->message = (AMSTR_FOLLOWON); + else + plr->message = (AMSTR_FOLLOWOFF); + */ + } else if (key == key_map_grid) { + grid = !grid; + /* LATER + if (grid) + plr->message = (AMSTR_GRIDON); + else + plr->message = (AMSTR_GRIDOFF); + */ + } + else if (key == key_map_mark) { + /* LATER + M_snprintf(buffer, sizeof(buffer), "%s %d", (AMSTR_MARKEDSPOT), + markpointnum); + plr->message = buffer; + */ + AM_addMark(); + } else if (key == key_map_clearmark) { + AM_clearMarks(); + // plr->message = (AMSTR_MARKSCLEARED); // LATER + } else { + rc = false; + } + + /* JOSEF: cheating unsupported + if ((!deathmatch || gameversion <= exe_doom_1_8) && + cht_CheckCheat(&cheat_amap, ev->data2)) { + rc = false; + cheating = (cheating + 1) % 3; + } + */ + } else if (ev->type == ev_keyup) { + rc = false; + key = ev->data1; + + if (key == key_map_east) { + if (!followplayer) + m_paninc.x = 0; + } else if (key == key_map_west) { + if (!followplayer) + m_paninc.x = 0; + } else if (key == key_map_north) { + if (!followplayer) + m_paninc.y = 0; + } else if (key == key_map_south) { + if (!followplayer) + m_paninc.y = 0; + } else if (key == key_map_zoomout || key == key_map_zoomin) { + mtof_zoommul = FRACUNIT; + ftom_zoommul = FRACUNIT; + } + } + + return rc; +} + +// +// Zooming +// +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(); +} + +// +// +// +void AM_doFollowPlayer(void) { + + if (f_oldloc.x != am_playerpos.x || f_oldloc.y != am_playerpos.y) { + m_x = FTOM(MTOF(am_playerpos.x)) - m_w / 2; + m_y = FTOM(MTOF(am_playerpos.y)) - m_h / 2; + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + f_oldloc.x = am_playerpos.x; + f_oldloc.y = am_playerpos.y; + + // m_x = FTOM(MTOF(plr->mo->x - m_w/2)); + // m_y = FTOM(MTOF(plr->mo->y - m_h/2)); + // m_x = plr->mo->x - m_w/2; + // m_y = plr->mo->y - m_h/2; + } +} + +// +// Updates on Game Tick +// +void AM_Ticker(void) { + if (!automapactive) + return; + + amclock++; + + if (followplayer) + 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(); + + // Update light level + // AM_updateLightLev(); // NOT JOSEF +} + +// +// Clear automap frame buffer. +// +void AM_clearFB(int color) { memset(fb, color, f_w * f_h * sizeof(*fb)); } + +// +// 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. +// +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; + } else { + tmp.x = 0; + tmp.y = 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 + +// +// Classic Bresenham w/ whatever optimizations needed for speed +// +void AM_drawFline(fline_t *fl, int color) { + register int x; + register int y; + register int dx; + register int dy; + register int sx; + register int sy; + register int ax; + register int ay; + register int d; + +// static int fuck = 0; + +// // For debugging only +// if (fl->a.x < 0 || fl->a.x >= f_w || fl->a.y < 0 || fl->a.y >= f_h || +// fl->b.x < 0 || fl->b.x >= f_w || fl->b.y < 0 || fl->b.y >= f_h) { +// fprintf(stderr, "fuck %d \r", fuck++); +// return; +// } + +#define PUTDOT(xx, yy, cc) fb[(yy)*f_w + (xx)] = (cc) + + dx = fl->b.x - fl->a.x; + ax = 2 * (dx < 0 ? -dx : dx); + sx = dx < 0 ? -1 : 1; + + dy = fl->b.y - fl->a.y; + ay = 2 * (dy < 0 ? -dy : dy); + sy = dy < 0 ? -1 : 1; + + x = fl->a.x; + y = fl->a.y; + + if (ax > ay) { + d = ay - ax / 2; + while (1) { + PUTDOT(x, y, color); + if (x == fl->b.x) + return; + if (d >= 0) { + y += sy; + d -= ax; + } + x += sx; + d += ay; + } + } else { + d = ax - ay / 2; + while (1) { + PUTDOT(x, y, color); + if (y == fl->b.y) + return; + if (d >= 0) { + x += sx; + d -= ay; + } + y += sy; + d += ax; + } + } +} + +// +// Clip lines, draw visible part sof lines. +// +void AM_drawMline(mline_t *ml, int color) { + static fline_t fl; + + if (AM_clipMline(ml, &fl)) + AM_drawFline(&fl, color); // draws it on frame buffer using fb coords +} + +// +// Draws flat (floor/ceiling tile) aligned grid lines. +// +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 << FRACBITS)) + start += (MAPBLOCKUNITS << FRACBITS) - + ((start - bmaporgx) % (MAPBLOCKUNITS << FRACBITS)); + end = m_x + m_w; + + // draw vertical gridlines + ml.a.y = m_y; + ml.b.y = m_y + m_h; + for (x = start; x < end; x += (MAPBLOCKUNITS << FRACBITS)) { + ml.a.x = x; + ml.b.x = x; + AM_drawMline(&ml, color); + } + + // Figure out start of horizontal gridlines + start = m_y; + if ((start - bmaporgy) % (MAPBLOCKUNITS << FRACBITS)) + start += (MAPBLOCKUNITS << FRACBITS) - + ((start - bmaporgy) % (MAPBLOCKUNITS << FRACBITS)); + end = m_y + m_h; + + // draw horizontal gridlines + ml.a.x = m_x; + ml.b.x = m_x + m_w; + for (y = start; y < end; y += (MAPBLOCKUNITS << FRACBITS)) { + ml.a.y = y; + ml.b.y = y; + AM_drawMline(&ml, color); + } +} + +// +// Determines visible lines, draws them. +// This is LineDef based, not LineSeg based. +// +void AM_drawWalls(void) { + int i; + static mline_t l; + + for (i = 0; i < numlines; i++) { + l.a.x = lines[i].v1->x; + l.a.y = lines[i].v1->y; + l.b.x = lines[i].v2->x; + l.b.y = lines[i].v2->y; + if (/*cheating JOSEF ||*/ lines[i].flags & ML_MAPPED) { + if ((lines[i].flags & LINE_NEVERSEE) && !cheating) + continue; + if (!lines[i].backsector) { + AM_drawMline(&l, WALLCOLORS + lightlev); + } else { + if (lines[i].special == 39) { // teleporters + AM_drawMline(&l, WALLCOLORS + WALLRANGE / 2); + } else if (lines[i].flags & ML_SECRET) // secret door + { + if (cheating) + AM_drawMline(&l, SECRETWALLCOLORS + lightlev); + else + AM_drawMline(&l, WALLCOLORS + lightlev); + } else if (lines[i].backsector->floorheight != + lines[i].frontsector->floorheight) { + AM_drawMline(&l, FDWALLCOLORS + lightlev); // floor level change + } else if (lines[i].backsector->ceilingheight != + lines[i].frontsector->ceilingheight) { + AM_drawMline(&l, CDWALLCOLORS + lightlev); // ceiling level change + } else if (cheating) { + AM_drawMline(&l, TSWALLCOLORS + lightlev); + } + } + } + /* LATER + else if (plr->powers[pw_allmap]) { + if (!(lines[i].flags & LINE_NEVERSEE)) + AM_drawMline(&l, GRAYS + 3); + } + */ + } +} + + +// +// Rotation in 2D. +// Used to rotate player arrow line character. +// +void AM_rotate(fixed_t *x, fixed_t *y, angle_t a) { + fixed_t tmpx; + + tmpx = FixedMul(*x, finecosine[a >> ANGLETOFINESHIFT]) - + FixedMul(*y, finesine[a >> ANGLETOFINESHIFT]); + + *y = FixedMul(*x, finesine[a >> ANGLETOFINESHIFT]) + + FixedMul(*y, finecosine[a >> ANGLETOFINESHIFT]); + + *x = tmpx; +} + +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; + + for (i = 0; i < lineguylines; i++) { + l.a.x = lineguy[i].a.x; + l.a.y = lineguy[i].a.y; + + if (scale) { + l.a.x = FixedMul(scale, l.a.x); + l.a.y = FixedMul(scale, l.a.y); + } + + if (angle) + AM_rotate(&l.a.x, &l.a.y, angle); + + l.a.x += x; + l.a.y += y; + + l.b.x = lineguy[i].b.x; + l.b.y = lineguy[i].b.y; + + if (scale) { + l.b.x = FixedMul(scale, l.b.x); + l.b.y = FixedMul(scale, l.b.y); + } + + if (angle) + AM_rotate(&l.b.x, &l.b.y, angle); + + l.b.x += x; + l.b.y += y; + + AM_drawMline(&l, color); + } +} + +void AM_drawPlayers(void) { + int i; + player_t *p; + static int their_colors[] = {GREENS, GRAYS, BROWNS, REDS}; + int their_color = -1; + int color; + + if (!netgame) { + if (cheating) + AM_drawLineCharacter(cheat_player_arrow, arrlen(cheat_player_arrow), 0, + am_playerpos.angle, WHITE, am_playerpos.x, am_playerpos.y); + else + AM_drawLineCharacter(player_arrow, arrlen(player_arrow), 0, + am_playerpos.angle, WHITE, am_playerpos.x, am_playerpos.y); + return; + } + /* JOSEF: multiplayer not supported + for (i = 0; i < MAXPLAYERS; i++) { + their_color++; + p = &players[i]; + + if ((deathmatch && !singledemo) && p != plr) + continue; + + if (!playeringame[i]) + continue; + + if (p->powers[pw_invisibility]) + color = 246; // *close* to black + else + color = their_colors[their_color]; + + AM_drawLineCharacter(player_arrow, arrlen(player_arrow), 0, p->mo->angle, + color, p->mo->x, p->mo->y); + } + */ +} + +void AM_drawMarks(void) { + int i, fx, fy, w, h; + + for (i = 0; i < AM_NUMMARKPOINTS; i++) { + if (markpoints[i].x != -1) { + // w = SHORT(marknums[i]->width); + // h = SHORT(marknums[i]->height); + w = 5; // because something's wrong with the wad, i guess + h = 6; // because something's wrong with the wad, i guess + fx = CXMTOF(markpoints[i].x); + fy = CYMTOF(markpoints[i].y); + if (fx >= f_x && fx <= f_w - w && fy >= f_y && fy <= f_h - h) { + V_DrawPatch(fx, fy, marknums[i]); + } + } + } +} + +void AM_drawCrosshair(int color) { + fb[(f_w * (f_h + 1)) / 2] = color; // single point for now +} + +void AM_drawIPUWatermark(int color) { + const int wmx = 283, wmy = 160; + for (int i = 0; i < arrlen(ipu_watermark_lines); ++i) { + fline_t ln = ipu_watermark_lines[i]; + ln.a.x += wmx; ln.b.x += wmx; + ln.a.y += wmy; ln.b.y += wmy; + AM_drawFline(&ln, color); + } +} + +void AM_Drawer(pixel_t* fb_tensor) { + fb = fb_tensor; // JOSEF + V_UseBuffer(fb); + + if (!automapactive) + return; + + AM_clearFB(BACKGROUND); + if (grid) + AM_drawGrid(GRIDCOLORS); + AM_drawWalls(); + AM_drawPlayers(); + // if (cheating == 2) // JOSEF, unsupported + // AM_drawThings(THINGCOLORS, THINGRANGE); + AM_drawCrosshair(XHAIRCOLORS); + AM_drawMarks(); + + AM_drawIPUWatermark(REDS + 2); +} \ No newline at end of file diff --git a/client/src/ipu/am_map.h b/client/src/ipu/am_map.h new file mode 100644 index 0000000..665bd5b --- /dev/null +++ b/client/src/ipu/am_map.h @@ -0,0 +1,47 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// AutoMap module. +// + +#ifndef __AMMAP_H__ +#define __AMMAP_H__ + +#include "d_event.h" +#include "doomtype.h" +#include "m_cheat.h" + +// 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); + +extern cheatseq_t cheat_amap; + +#endif diff --git a/client/src/ipu/d_englsh.h b/client/src/ipu/d_englsh.h new file mode 100644 index 0000000..ddecc14 --- /dev/null +++ b/client/src/ipu/d_englsh.h @@ -0,0 +1,631 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Printed strings for translation. +// English language support (default). +// + +#ifndef __D_ENGLSH__ +#define __D_ENGLSH__ + +// +// Printed strings for translation +// + +// +// D_Main.C +// +#define D_DEVSTR "Development mode ON.\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 DOSY "(press y to quit to dos.)" + +#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" + +// +// 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 "Green: " +#define HUSTR_PLRINDIGO "Indigo: " +#define HUSTR_PLRBROWN "Brown: " +#define HUSTR_PLRRED "Red: " + +#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" + +// +// 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 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" + +// 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." + +#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." + +#endif diff --git a/client/src/ipu/d_event.h b/client/src/ipu/d_event.h new file mode 100644 index 0000000..c29b54b --- /dev/null +++ b/client/src/ipu/d_event.h @@ -0,0 +1,140 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// + + +#ifndef __D_EVENT__ +#define __D_EVENT__ +// +// Event handling. +// + +// Input event types. +typedef enum +{ + // Key press/release events. + // data1: Key code (from doomkeys.h) of the key that was + // pressed or released. This is the key as it appears + // on a US keyboard layout, and does not change with + // layout. + // For ev_keydown only: + // data2: ASCII representation of the key that was pressed that + // changes with the keyboard layout; eg. if 'Z' is + // pressed on a German keyboard, data1='y',data2='z'. + // Not affected by modifier keys. + // data3: ASCII input, fully modified according to keyboard + // layout and any modifier keys that are held down. + // Only set if I_StartTextInput() has been called. + ev_keydown, + ev_keyup, + + // Mouse movement event. + // data1: Bitfield of buttons currently held down. + // (bit 0 = left; bit 1 = right; bit 2 = middle). + // data2: X axis mouse movement (turn). + // data3: Y axis mouse movement (forward/backward). + ev_mouse, + + // Joystick state. + // data1: Bitfield of buttons currently pressed. + // data2: X axis mouse movement (turn). + // data3: Y axis mouse movement (forward/backward). + // data4: Third axis mouse movement (strafe). + ev_joystick, + + // Quit event. Triggered when the user clicks the "close" button + // to terminate the application. + ev_quit +} evtype_t; + +// Event structure. +typedef struct +{ + evtype_t type; + + // Event-specific data; see the descriptions given above. + int data1, data2, data3, data4; +} event_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 3 bits hold weapon num. + BT_CHANGE = 4, + // The 3bit weapon mask and shift, convenience. + BT_WEAPONMASK = (8+16+32), + BT_WEAPONSHIFT = 3, + + // Pause the game. + BTS_PAUSE = 1, + // Save the game at each console. + BTS_SAVEGAME = 2, + + // Savegame slot numbers + // occupy the second byte of buttons. + BTS_SAVEMASK = (4+8+16), + BTS_SAVESHIFT = 2, + +} buttoncode_t; + +// villsa [STRIFE] Strife specific buttons +// TODO - not finished +typedef enum +{ + // Player view look up + BT2_LOOKUP = 1, + // Player view look down + BT2_LOOKDOWN = 2, + // Center player's view + BT2_CENTERVIEW = 4, + // Use inventory item + BT2_INVUSE = 8, + // Drop inventory item + BT2_INVDROP = 16, + // Jump up and down + BT2_JUMP = 32, + // Use medkit + BT2_HEALTH = 128, + +} buttoncode2_t; + + + + +// Called by IO functions when input is detected. +void D_PostEvent (event_t *ev); + +// Read an event from the event queue + +event_t *D_PopEvent(void); + + +#endif + diff --git a/client/src/ipu/d_items.h b/client/src/ipu/d_items.h new file mode 100644 index 0000000..d4a1f60 --- /dev/null +++ b/client/src/ipu/d_items.h @@ -0,0 +1,37 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Items: key cards, artifacts, weapon, ammunition. +// + +#ifndef __D_ITEMS__ +#define __D_ITEMS__ + +#include "doomdef.h" + +// 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/client/src/ipu/d_iwad.h b/client/src/ipu/d_iwad.h new file mode 100644 index 0000000..91a804f --- /dev/null +++ b/client/src/ipu/d_iwad.h @@ -0,0 +1,52 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Find IWAD and initialize according to IWAD type. +// + + +#ifndef __D_IWAD__ +#define __D_IWAD__ + +#include "d_mode.h" + +#define IWAD_MASK_DOOM ((1 << doom) \ + | (1 << doom2) \ + | (1 << pack_tnt) \ + | (1 << pack_plut) \ + | (1 << pack_chex) \ + | (1 << pack_hacx)) +#define IWAD_MASK_HERETIC (1 << heretic) +#define IWAD_MASK_HEXEN (1 << hexen) +#define IWAD_MASK_STRIFE (1 << strife) + +typedef struct +{ + char *name; + GameMission_t mission; + GameMode_t mode; + char *description; +} iwad_t; + +char *D_FindWADByName(char *filename); +char *D_TryFindWADByName(char *filename); +char *D_FindIWAD(int mask, GameMission_t *mission); +const iwad_t **D_FindAllIWADs(int mask); +char *D_SaveGameIWADName(GameMission_t gamemission); +char *D_SuggestIWADName(GameMission_t mission, GameMode_t mode); +char *D_SuggestGameName(GameMission_t mission, GameMode_t mode); +void D_CheckCorrectIWAD(GameMission_t mission); + +#endif + diff --git a/client/src/ipu/d_loop.h b/client/src/ipu/d_loop.h new file mode 100644 index 0000000..b85fc42 --- /dev/null +++ b/client/src/ipu/d_loop.h @@ -0,0 +1,92 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Main loop stuff. +// + +#ifndef __D_LOOP__ +#define __D_LOOP__ + +#include "d_ticcmd.h" +#include "doomtype.h" +#include "net_defs.h" + +// Callback function invoked while waiting for the netgame to start. +// The callback is invoked when new players are ready. The callback +// should return true, or return false to abort startup. + +typedef boolean (*netgame_startup_callback_t)(int ready_players, + int num_players); + +/* JOSEF +typedef struct +{ + // Read events from the event queue, and process them. + + void (*ProcessEvents)(); + + // Given the current input state, fill in the fields of the specified + // ticcmd_t structure with data for a new tic. + + void (*BuildTiccmd)(ticcmd_t *cmd, int maketic); + + // Advance the game forward one tic, using the specified player input. + + void (*RunTic)(ticcmd_t *cmds, boolean *ingame); + + // Run the menu (runs independently of the game). + + void (*RunMenu)(); +} loop_interface_t; + +// Register callback functions for the main loop code to use. +void D_RegisterLoopCallbacks(loop_interface_t *i); +*/ + +// Create any new ticcmds and broadcast to other players. +void NetUpdate (void); + +// Broadcasts special packets to other players +// to notify of game exit +void D_QuitNetGame (void); + +//? how many ticks to run? +void TryRunTics (void); + +// Called at start of game loop to initialize timers +void D_StartGameLoop(void); + +// Initialize networking code and connect to server. + +boolean D_InitNetGame(net_connect_data_t *connect_data); + +// Start game with specified settings. The structure will be updated +// with the actual settings for the game. + +void D_StartNetGame(net_gamesettings_t *settings, + netgame_startup_callback_t callback); + +extern boolean singletics; +extern int gametic, ticdup; + +// Check if it is permitted to record a demo with a non-vanilla feature. +boolean D_NonVanillaRecord(boolean conditional, char *feature); + +// Check if it is permitted to play back a demo with a non-vanilla feature. +boolean D_NonVanillaPlayback(boolean conditional, int lumpnum, + char *feature); + +#endif + diff --git a/client/src/ipu/d_main.h b/client/src/ipu/d_main.h new file mode 100644 index 0000000..0c0491b --- /dev/null +++ b/client/src/ipu/d_main.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + +#ifndef __D_MAIN__ +#define __D_MAIN__ + +#include "doomdef.h" + +// Read events from all input devices + +void D_ProcessEvents(void); + +// +// BASE LEVEL +// +void D_PageTicker(void); +void D_PageDrawer(void); +void D_AdvanceDemo(void); +void D_DoAdvanceDemo(void); +void D_StartTitle(void); + +extern void D_DoomMain(void); + +// +// GLOBAL VARIABLES +// + +extern gameaction_t gameaction; + +#endif diff --git a/client/src/ipu/d_mode.h b/client/src/ipu/d_mode.h new file mode 100644 index 0000000..0be4937 --- /dev/null +++ b/client/src/ipu/d_mode.h @@ -0,0 +1,108 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Functions and definitions relating to the game type and operational +// mode. +// + +#ifndef __D_MODE__ +#define __D_MODE__ + +#include "doomtype.h" + +// The "mission" controls what game we are playing. + +typedef enum +{ + doom, // Doom 1 + doom2, // Doom 2 + pack_tnt, // Final Doom: TNT: Evilution + pack_plut, // Final Doom: The Plutonia Experiment + pack_chex, // Chex Quest (modded doom) + pack_hacx, // Hacx (modded doom2) + heretic, // Heretic + hexen, // Hexen + strife, // Strife + + none +} GameMission_t; + +// The "mode" allows more accurate specification of the game mode we are +// in: eg. shareware vs. registered. So doom1.wad and doom.wad are the +// same mission, but a different mode. + +typedef enum +{ + shareware, // Doom/Heretic shareware + registered, // Doom/Heretic registered + commercial, // Doom II/Hexen + retail, // Ultimate Doom + indetermined // Unknown. +} GameMode_t; + +// What version are we emulating? + +typedef enum +{ + exe_doom_1_2, // Doom 1.2: shareware and registered + exe_doom_1_666, // Doom 1.666: for shareware, registered and commercial + exe_doom_1_7, // Doom 1.7/1.7a: " + exe_doom_1_8, // Doom 1.8: " + exe_doom_1_9, // Doom 1.9: " + exe_hacx, // Hacx + exe_ultimate, // Ultimate Doom (retail) + exe_final, // Final Doom + exe_final2, // Final Doom (alternate exe) + exe_chex, // Chex Quest executable (based on Final Doom) + + exe_heretic_1_3, // Heretic 1.3 + + exe_hexen_1_1, // Hexen 1.1 + exe_strife_1_2, // Strife v1.2 + exe_strife_1_31 // Strife v1.31 +} GameVersion_t; + +// What IWAD variant are we using? + +typedef enum +{ + vanilla, // Vanilla Doom + freedoom, // FreeDoom: Phase 1 + 2 + freedm, // FreeDM + bfgedition, // Doom Classic (Doom 3: BFG Edition) +} GameVariant_t; + +// Skill level. + +typedef enum +{ + sk_noitems = -1, // the "-skill 0" hack + sk_baby = 0, + sk_easy, + sk_medium, + sk_hard, + sk_nightmare +} skill_t; + +boolean D_ValidGameMode(GameMission_t mission, GameMode_t mode); +boolean D_ValidGameVersion(GameMission_t mission, GameVersion_t version); +boolean D_ValidEpisodeMap(GameMission_t mission, GameMode_t mode, + int episode, int map); +int D_GetNumEpisodes(GameMission_t mission, GameMode_t mode); +boolean D_IsEpisodeMap(GameMission_t mission); +char *D_GameMissionString(GameMission_t mission); + +#endif /* #ifndef __D_MODE__ */ + diff --git a/client/src/ipu/d_player.h b/client/src/ipu/d_player.h new file mode 100644 index 0000000..7163abc --- /dev/null +++ b/client/src/ipu/d_player.h @@ -0,0 +1,195 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// + +#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" + +#include "net_defs.h" + +// +// 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; + + // 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; + + int 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. + 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]; + +} wbstartstruct_t; + +#endif diff --git a/client/src/ipu/d_textur.h b/client/src/ipu/d_textur.h new file mode 100644 index 0000000..706b356 --- /dev/null +++ b/client/src/ipu/d_textur.h @@ -0,0 +1,35 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Typedefs related to to textures etc., +// isolated here to make it easier separating modules. +// + +#ifndef __D_TEXTUR__ +#define __D_TEXTUR__ + +#include "doomtype.h" + +// +// Flats? +// +// a pic is an unmasked block of pixels +typedef struct { + byte width; + byte height; + byte data; +} pic_t; + +#endif diff --git a/client/src/ipu/d_think.h b/client/src/ipu/d_think.h new file mode 100644 index 0000000..ab61382 --- /dev/null +++ b/client/src/ipu/d_think.h @@ -0,0 +1,54 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// MapObj data. Map Objects or mobjs are actors, entities, +// thinker, take-your-pick... anything that moves, acts, or +// suffers state changes of more or less violent nature. +// + +#ifndef __D_THINK__ +#define __D_THINK__ + +// +// Experimental stuff. +// To compile this as "ANSI C with classes" +// we will need to handle the various +// action functions cleanly. +// +typedef void (*actionf_v)(); +typedef void (*actionf_p1)(void *); +typedef void (*actionf_p2)(void *, void *); + +typedef union { + actionf_v acv; + actionf_p1 acp1; + actionf_p2 acp2; + +} actionf_t; + +// Historically, "think_t" is yet another +// function pointer to a routine to handle +// an actor. +typedef actionf_t think_t; + +// Doubly linked list of actors. +typedef struct thinker_s { + struct thinker_s *prev; + struct thinker_s *next; + think_t function; + +} thinker_t; + +#endif diff --git a/client/src/ipu/d_ticcmd.h b/client/src/ipu/d_ticcmd.h new file mode 100644 index 0000000..daf0da3 --- /dev/null +++ b/client/src/ipu/d_ticcmd.h @@ -0,0 +1,56 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + + +#ifndef __D_TICCMD__ +#define __D_TICCMD__ + +#include "doomtype.h" + + +// The data sampled per tick (single player) +// and transmitted to other peers (multiplayer). +// Mainly movements/button commands per game tick, +// plus a checksum for internal state consistency. + +typedef struct +{ + signed char forwardmove; // *2048 for move + signed char sidemove; // *2048 for move + short angleturn; // <<16 for angle delta + byte chatchar; + byte buttons; + // villsa [STRIFE] according to the asm, + // consistancy is a short, not a byte + byte consistancy; // checks for net game + + // villsa - Strife specific: + + byte buttons2; + int inventory; + + // Heretic/Hexen specific: + + byte lookfly; // look/fly up/down/centering + byte arti; // artitype_t to use +} ticcmd_t; + + + +#endif diff --git a/client/src/ipu/doomdata.h b/client/src/ipu/doomdata.h new file mode 100644 index 0000000..2606459 --- /dev/null +++ b/client/src/ipu/doomdata.h @@ -0,0 +1,184 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// all external data is defined here +// most of the data is loaded into different structures at run time +// some internal structures shared by many modules are here +// + +#ifndef __DOOMDATA__ +#define __DOOMDATA__ + +// The most basic types we use, portability. +#include "doomtype.h" + +// Some global defines, that configure the game. +#include "doomdef.h" + +// +// Map level types. +// The following data structures define the persistent format +// used in the lumps of the WAD files. +// + +// Lump order in a map WAD: each map needs a couple of lumps +// to provide a complete scene geometry description. +enum { + ML_LABEL, // A separator, name, ExMx or MAPxx + ML_THINGS, // Monsters, items.. + ML_LINEDEFS, // LineDefs, from editing + ML_SIDEDEFS, // SideDefs, from editing + ML_VERTEXES, // Vertices, edited and BSP splits generated + ML_SEGS, // LineSegs, from LineDefs split by BSP + ML_SSECTORS, // SubSectors, list of LineSegs + ML_NODES, // BSP nodes + ML_SECTORS, // Sectors, from editing + ML_REJECT, // LUT, sector-sector visibility + ML_BLOCKMAP // LUT, motion clipping, walls/grid element +}; + +// A single Vertex. +typedef PACKED_STRUCT({ + short x; + short y; +}) mapvertex_t; + +// A SideDef, defining the visual appearance of a wall, +// by setting textures and offsets. +typedef PACKED_STRUCT({ + short textureoffset; + short rowoffset; + char toptexture[8]; + char bottomtexture[8]; + char midtexture[8]; + // Front sector, towards viewer. + short sector; +}) mapsidedef_t; + +// A LineDef, as used for editing, and as input +// to the BSP builder. +typedef PACKED_STRUCT({ + short v1; + short v2; + short flags; + short special; + short tag; + // sidenum[1] will be -1 if one sided + short sidenum[2]; +}) maplinedef_t; + +// +// LineDef attributes. +// + +// Solid, is an obstacle. +#define ML_BLOCKING 1 + +// Blocks monsters only. +#define ML_BLOCKMONSTERS 2 + +// Backside will not be present at all +// if not two sided. +#define ML_TWOSIDED 4 + +// If a texture is pegged, the texture will have +// the end exposed to air held constant at the +// top or bottom of the texture (stairs or pulled +// down things) and will move with a height change +// of one of the neighbor sectors. +// Unpegged textures allways have the first row of +// the texture at the top pixel of the line for both +// top and bottom textures (use next to windows). + +// upper texture unpegged +#define ML_DONTPEGTOP 8 + +// lower texture unpegged +#define ML_DONTPEGBOTTOM 16 + +// In AutoMap: don't map as two sided: IT'S A SECRET! +#define ML_SECRET 32 + +// Sound rendering: don't let sound cross two of these. +#define ML_SOUNDBLOCK 64 + +// Don't draw on the automap at all. +#define ML_DONTDRAW 128 + +// Set if already seen, thus drawn in automap. +#define ML_MAPPED 256 + +// Sector definition, from editing. +typedef PACKED_STRUCT({ + short floorheight; + short ceilingheight; + char floorpic[8]; + char ceilingpic[8]; + short lightlevel; + short special; + short tag; +}) mapsector_t; + +// SubSector, as generated by BSP. +typedef PACKED_STRUCT({ + short numsegs; + // Index of first one, segs are stored sequentially. + short firstseg; +}) mapsubsector_t; + +// LineSeg, generated by splitting LineDefs +// using partition lines selected by BSP builder. +typedef PACKED_STRUCT({ + short v1; + short v2; + short angle; + short linedef; + short side; + short offset; +}) mapseg_t; + +// BSP node structure. + +// Indicate a leaf. +#define NF_SUBSECTOR 0x8000 + +typedef PACKED_STRUCT({ + // Partition line from (x,y) to x+dx,y+dy) + short x; + short y; + short dx; + short dy; + + // Bounding box for each child, + // clip against view frustum. + short bbox[2][4]; + + // If NF_SUBSECTOR its a subsector, + // else it's a node of another subtree. + unsigned short children[2]; + +}) mapnode_t; + +// Thing definition, position, orientation and type, +// plus skill/visibility flags and attributes. +typedef PACKED_STRUCT({ + short x; + short y; + short angle; + short type; + short options; +}) mapthing_t; + +#endif // __DOOMDATA__ diff --git a/client/src/ipu/doomdef.h b/client/src/ipu/doomdef.h new file mode 100644 index 0000000..0aac349 --- /dev/null +++ b/client/src/ipu/doomdef.h @@ -0,0 +1,143 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Internally used data structures for virtually everything, +// lots of other stuff. +// + +#ifndef __DOOMDEF__ +#define __DOOMDEF__ + +#include "i_timer.h" + +// +// Global parameters/defines. +// +// DOOM version +#define DOOM_VERSION 109 + +// Version code for cph's longtics hack ("v1.91") +#define DOOM_191_VERSION 111 + +// The maximum number of players, multiplayer/networking. +#define MAXPLAYERS 4 + +// The current state of the game: whether we are +// playing, gazing at the intermission screen, +// the game final animation, or a demo. +typedef enum { + GS_LEVEL, + GS_INTERMISSION, + GS_DEMOSCREEN, +} gamestate_t; + +typedef enum { + ga_nothing, + ga_loadlevel, + ga_newgame, + ga_loadgame, + ga_savegame, + ga_playdemo, + ga_completed, + ga_victory, + ga_worlddone, + ga_screenshot +} gameaction_t; + +// +// Difficulty/skill settings/filters. +// + +// Skill flags. +#define MTF_EASY 1 +#define MTF_NORMAL 2 +#define MTF_HARD 4 + +// Deaf monsters/do not react to sound. +#define MTF_AMBUSH 8 + +// +// Key cards. +// +typedef enum { + it_bluecard, + it_yellowcard, + it_redcard, + it_blueskull, + it_yellowskull, + it_redskull, + + NUMCARDS + +} card_t; + +// The defined weapons, +// including a marker indicating +// user has not changed weapon. +typedef enum { + wp_fist, + wp_pistol, + wp_shotgun, + wp_chaingun, + wp_missile, + wp_plasma, + wp_bfg, + wp_chainsaw, + wp_supershotgun, + + NUMWEAPONS, + + // No pending weapon change. + wp_nochange + +} weapontype_t; + +// Ammunition types defined. +typedef enum { + am_clip, // Pistol / chaingun ammo. + am_shell, // Shotgun / double barreled shotgun. + am_cell, // Plasma rifle, BFG. + am_misl, // Missile launcher. + NUMAMMO, + am_noammo // Unlimited for chainsaw / fist. + +} ammotype_t; + +// Power up artifacts. +typedef enum { + pw_invulnerability, + pw_strength, + pw_invisibility, + pw_ironfeet, + pw_allmap, + pw_infrared, + NUMPOWERS + +} powertype_t; + +// +// Power up durations, +// how many seconds till expiration, +// assuming TICRATE is 35 ticks/second. +// +typedef enum { + INVULNTICS = (30 * TICRATE), + INVISTICS = (60 * TICRATE), + INFRATICS = (120 * TICRATE), + IRONTICS = (60 * TICRATE) + +} powerduration_t; + +#endif // __DOOMDEF__ diff --git a/client/src/ipu/doomkeys.h b/client/src/ipu/doomkeys.h new file mode 100644 index 0000000..35310a6 --- /dev/null +++ b/client/src/ipu/doomkeys.h @@ -0,0 +1,153 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Key definitions +// + +#ifndef __DOOMKEYS__ +#define __DOOMKEYS__ + +// +// DOOM keyboard definition. +// This is the stuff configured by Setup.Exe. +// Most key data are simple ascii (uppercased). +// +#define KEY_RIGHTARROW 0xae +#define KEY_LEFTARROW 0xac +#define KEY_UPARROW 0xad +#define KEY_DOWNARROW 0xaf +#define KEY_ESCAPE 27 +#define KEY_ENTER 13 +#define KEY_TAB 9 +#define KEY_F1 (0x80+0x3b) +#define KEY_F2 (0x80+0x3c) +#define KEY_F3 (0x80+0x3d) +#define KEY_F4 (0x80+0x3e) +#define KEY_F5 (0x80+0x3f) +#define KEY_F6 (0x80+0x40) +#define KEY_F7 (0x80+0x41) +#define KEY_F8 (0x80+0x42) +#define KEY_F9 (0x80+0x43) +#define KEY_F10 (0x80+0x44) +#define KEY_F11 (0x80+0x57) +#define KEY_F12 (0x80+0x58) + +#define KEY_BACKSPACE 0x7f +#define KEY_PAUSE 0xff + +#define KEY_EQUALS 0x3d +#define KEY_MINUS 0x2d + +#define KEY_RSHIFT (0x80+0x36) +#define KEY_RCTRL (0x80+0x1d) +#define KEY_RALT (0x80+0x38) + +#define KEY_LALT KEY_RALT + +// new keys: + +#define KEY_CAPSLOCK (0x80+0x3a) +#define KEY_NUMLOCK (0x80+0x45) +#define KEY_SCRLCK (0x80+0x46) +#define KEY_PRTSCR (0x80+0x59) + +#define KEY_HOME (0x80+0x47) +#define KEY_END (0x80+0x4f) +#define KEY_PGUP (0x80+0x49) +#define KEY_PGDN (0x80+0x51) +#define KEY_INS (0x80+0x52) +#define KEY_DEL (0x80+0x53) + +#define KEYP_0 0 +#define KEYP_1 KEY_END +#define KEYP_2 KEY_DOWNARROW +#define KEYP_3 KEY_PGDN +#define KEYP_4 KEY_LEFTARROW +#define KEYP_5 '5' +#define KEYP_6 KEY_RIGHTARROW +#define KEYP_7 KEY_HOME +#define KEYP_8 KEY_UPARROW +#define KEYP_9 KEY_PGUP + +#define KEYP_DIVIDE '/' +#define KEYP_PLUS '+' +#define KEYP_MINUS '-' +#define KEYP_MULTIPLY '*' +#define KEYP_PERIOD 0 +#define KEYP_EQUALS KEY_EQUALS +#define KEYP_ENTER KEY_ENTER + +#define SCANCODE_TO_KEYS_ARRAY { \ + 0, 0, 0, 0, 'a', /* 0-9 */ \ + 'b', 'c', 'd', 'e', 'f', \ + 'g', 'h', 'i', 'j', 'k', /* 10-19 */ \ + 'l', 'm', 'n', 'o', 'p', \ + 'q', 'r', 's', 't', 'u', /* 20-29 */ \ + 'v', 'w', 'x', 'y', 'z', \ + '1', '2', '3', '4', '5', /* 30-39 */ \ + '6', '7', '8', '9', '0', \ + KEY_ENTER, KEY_ESCAPE, KEY_BACKSPACE, KEY_TAB, ' ', /* 40-49 */ \ + KEY_MINUS, KEY_EQUALS, '[', ']', '\\', \ + 0, ';', '\'', '`', ',', /* 50-59 */ \ + '.', '/', KEY_CAPSLOCK, KEY_F1, KEY_F2, \ + KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, /* 60-69 */ \ + KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, \ + KEY_PRTSCR, KEY_SCRLCK, KEY_PAUSE, KEY_INS, KEY_HOME, /* 70-79 */ \ + KEY_PGUP, KEY_DEL, KEY_END, KEY_PGDN, KEY_RIGHTARROW, \ + KEY_LEFTARROW, KEY_DOWNARROW, KEY_UPARROW, /* 80-89 */ \ + KEY_NUMLOCK, KEYP_DIVIDE, \ + KEYP_MULTIPLY, KEYP_MINUS, KEYP_PLUS, KEYP_ENTER, KEYP_1, \ + KEYP_2, KEYP_3, KEYP_4, KEYP_5, KEYP_6, /* 90-99 */ \ + KEYP_7, KEYP_8, KEYP_9, KEYP_0, KEYP_PERIOD, \ + 0, 0, 0, KEYP_EQUALS, /* 100-103 */ \ +} + +// Default names for keys, to use in English or as fallback. +#define KEY_NAMES_ARRAY { \ + { KEY_BACKSPACE, "BACKSP" }, { KEY_TAB, "TAB" }, \ + { KEY_INS, "INS" }, { KEY_DEL, "DEL" }, \ + { KEY_PGUP, "PGUP" }, { KEY_PGDN, "PGDN" }, \ + { KEY_ENTER, "ENTER" }, { KEY_ESCAPE, "ESC" }, \ + { KEY_F1, "F1" }, { KEY_F2, "F2" }, \ + { KEY_F3, "F3" }, { KEY_F4, "F4" }, \ + { KEY_F5, "F5" }, { KEY_F6, "F6" }, \ + { KEY_F7, "F7" }, { KEY_F8, "F8" }, \ + { KEY_F9, "F9" }, { KEY_F10, "F10" }, \ + { KEY_F11, "F11" }, { KEY_F12, "F12" }, \ + { KEY_HOME, "HOME" }, { KEY_END, "END" }, \ + { KEY_MINUS, "-" }, { KEY_EQUALS, "=" }, \ + { KEY_NUMLOCK, "NUMLCK" }, { KEY_SCRLCK, "SCRLCK" }, \ + { KEY_PAUSE, "PAUSE" }, { KEY_PRTSCR, "PRTSC" }, \ + { KEY_UPARROW, "UP" }, { KEY_DOWNARROW, "DOWN" }, \ + { KEY_LEFTARROW, "LEFT" }, { KEY_RIGHTARROW, "RIGHT" }, \ + { KEY_RALT, "ALT" }, { KEY_LALT, "ALT" }, \ + { KEY_RSHIFT, "SHIFT" }, { KEY_CAPSLOCK, "CAPS" }, \ + { KEY_RCTRL, "CTRL" }, { ' ', "SPACE" }, \ + { 'a', "A" }, { 'b', "B" }, { 'c', "C" }, { 'd', "D" }, \ + { 'e', "E" }, { 'f', "F" }, { 'g', "G" }, { 'h', "H" }, \ + { 'i', "I" }, { 'j', "J" }, { 'k', "K" }, { 'l', "L" }, \ + { 'm', "M" }, { 'n', "N" }, { 'o', "O" }, { 'p', "P" }, \ + { 'q', "Q" }, { 'r', "R" }, { 's', "S" }, { 't', "T" }, \ + { 'u', "U" }, { 'v', "V" }, { 'w', "W" }, { 'x', "X" }, \ + { 'y', "Y" }, { 'z', "Z" }, { '0', "0" }, { '1', "1" }, \ + { '2', "2" }, { '3', "3" }, { '4', "4" }, { '5', "5" }, \ + { '6', "6" }, { '7', "7" }, { '8', "8" }, { '9', "9" }, \ + { '[', "[" }, { ']', "]" }, { ';', ";" }, { '`', "`" }, \ + { ',', "," }, { '.', "." }, { '/', "/" }, { '\\', "\\" }, \ + { '\'', "\'" }, \ +} + +#endif // __DOOMKEYS__ + diff --git a/client/src/ipu/doomstat.h b/client/src/ipu/doomstat.h new file mode 100644 index 0000000..b520599 --- /dev/null +++ b/client/src/ipu/doomstat.h @@ -0,0 +1,240 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// All the global variables that store the internal state. +// Theoretically speaking, the internal state of the engine +// should be found by looking at the variables collected +// here, and every relevant module will have to include +// this header file. +// In practice, things are a bit messy. +// + +#ifndef __D_STATE__ +#define __D_STATE__ + +// Game mode/mission +#include "d_mode.h" +// We need the playr data structure as well. +#include "d_player.h" +#include "d_ticcmd.h" +// We need globally shared data structures, +// for defining the global state variables. +#include "doomdata.h" +#include "doomdef.h" +#include "doomtype.h" + +// ------------------------ +// Command line parameters. +// +extern boolean nomonsters; // checkparm of -nomonsters +extern boolean respawnparm; // checkparm of -respawn +extern boolean fastparm; // checkparm of -fast + +extern boolean devparm; // DEBUG: launched with -devparm + +// ----------------------------------------------------- +// Game Mode - identify IWAD as shareware, retail etc. +// +extern GameMode_t gamemode; +extern GameMission_t gamemission; +extern GameVersion_t gameversion; +extern GameVariant_t gamevariant; +extern char *gamedescription; + +// Convenience macro. +// 'gamemission' can be equal to pack_chex or pack_hacx, but these are +// just modified versions of doom and doom2, and should be interpreted +// as the same most of the time. + +#define logical_gamemission \ + (gamemission == pack_chex ? doom \ + : gamemission == pack_hacx ? doom2 : gamemission) + +// Set if homebrew PWAD stuff has been added. +extern boolean modifiedgame; + +// ------------------------------------------- +// Selected skill type, map etc. +// + +// Defaults for menu, methinks. +extern skill_t startskill; +extern int startepisode; +extern int startmap; + +// Savegame slot to load on startup. This is the value provided to +// the -loadgame option. If this has not been provided, this is -1. + +extern int startloadgame; + +extern boolean autostart; + +// Selected by user. +extern skill_t gameskill; +extern int gameepisode; +extern int gamemap; + + +// If non-zero, exit the level after this number of minutes +extern int timelimit; + +// Nightmare mode flag, single player. +extern boolean respawnmonsters; + +// Netgame? Only true if >1 player. +extern boolean netgame; + +// 0=Cooperative; 1=Deathmatch; 2=Altdeath +extern int deathmatch; + +// ------------------------- +// Internal parameters for sound rendering. +// These have been taken from the DOS version, +// but are not (yet) supported with Linux +// (e.g. no sound volume adjustment with menu. + +// From m_menu.c: +// Sound FX volume has default, 0 - 15 +// Music volume has default, 0 - 15 +// These are multiplied by 8. +extern int sfxVolume; +extern int musicVolume; + +// Current music/sfx card - index useless +// w/o a reference LUT in a sound module. +// Ideally, this would use indices found +// in: /usr/include/linux/soundcard.h +extern int snd_MusicDevice; +extern int snd_SfxDevice; +// Config file? Same disclaimer as above. +extern int snd_DesiredMusicDevice; +extern int snd_DesiredSfxDevice; + +// ------------------------- +// Status flags for refresh. +// + +// Depending on view size - no status bar? +// Note that there is no way to disable the +// status bar explicitely. +extern boolean statusbaractive; + +extern boolean automapactive; // In AutoMap mode? +extern boolean menuactive; // Menu overlayed? +extern boolean paused; // Game Pause? + +extern boolean viewactive; + +extern boolean nodrawers; + +extern boolean testcontrols; +extern int testcontrols_mousespeed; + +// This one is related to the 3-screen display mode. +// ANG90 = left side, ANG270 = right +extern int viewangleoffset; + +// Player taking events, and displaying. +extern int consoleplayer; +extern int displayplayer; + +// ------------------------------------- +// Scores, rating. +// Statistics on a given map, for intermission. +// +extern int totalkills; +extern int totalitems; +extern int totalsecret; + +// Timer, for scores. +extern int levelstarttic; // gametic at level start +extern int leveltime; // tics in game play for par + +// -------------------------------------- +// DEMO playback/recording related stuff. +// No demo, there is a human player in charge? +// Disable save/end game? +extern boolean usergame; + +//? +extern boolean demoplayback; +extern boolean demorecording; + +// Round angleturn in ticcmds to the nearest 256. This is used when +// recording Vanilla demos in netgames. + +extern boolean lowres_turn; + +// Quit after playing a demo from cmdline. +extern boolean singledemo; + +//? +extern gamestate_t gamestate; + +//----------------------------- +// Internal parameters, fixed. +// These are set by the engine, and not changed +// according to user inputs. Partly load from +// WAD, partly set at startup time. + +// Bookkeeping on players - state. +extern player_t players[MAXPLAYERS]; + +// Alive? Disconnected? +extern boolean playeringame[MAXPLAYERS]; + +// Player spawn spots for deathmatch. +#define MAX_DM_STARTS 10 +extern mapthing_t deathmatchstarts[MAX_DM_STARTS]; +extern mapthing_t *deathmatch_p; + +// Player spawn spots. +extern mapthing_t playerstarts[MAXPLAYERS]; + +// Intermission stats. +// Parameters for world map / intermission. +extern wbstartstruct_t wminfo; + +//----------------------------------------- +// Internal parameters, used for engine. +// + +// File handling stuff. +extern char *savegamedir; +extern char basedefault[1024]; + +// if true, load all graphics at level load +extern boolean precache; + +// wipegamestate can be set to -1 +// to force a wipe on the next draw +extern gamestate_t wipegamestate; + +extern int mouseSensitivity; + +extern int bodyqueslot; + +// Needed to store the number of the dummy sky flat. +// Used for rendering, +// as well as tracking projectiles etc. +extern int skyflatnum; + +// Netgame stuff (buffers and pointers, i.e. indices). + +extern int rndindex; + +extern ticcmd_t *netcmds; + +#endif diff --git a/client/src/ipu/doomtype.h b/client/src/ipu/doomtype.h new file mode 100644 index 0000000..0b5256f --- /dev/null +++ b/client/src/ipu/doomtype.h @@ -0,0 +1,91 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Simple basic typedefs, isolated here to make it easier +// separating modules. +// + + +#ifndef __DOOMTYPE__ +#define __DOOMTYPE__ + +#include // JOSEF, was strings.h + + + +// +// The packed attribute forces structures to be packed into the minimum +// space necessary. If this is not done, the compiler may align structure +// fields differently to optimize memory access, inflating the overall +// structure size. It is important to use the packed attribute on certain +// structures where alignment is important, particularly data read/written +// to disk. +// + +#ifdef __GNUC__ + +#define PACKEDATTR __attribute__((packed)) + +#else +#define PACKEDATTR __attribute__((packed)) +#endif + +#define PACKEDPREFIX + +#define PACKED_STRUCT(...) PACKEDPREFIX struct __VA_ARGS__ PACKEDATTR + +// C99 integer types; with gcc we just use this. Other compilers +// should add conditional statements that define the C99 types. + +// What is really wanted here is stdint.h; however, some old versions +// of Solaris don't have stdint.h and only have inttypes.h (the +// pre-standardisation version). inttypes.h is also in the C99 +// standard and defined to include stdint.h, so include this. + +// #include // JOSEF +#include + +#if defined(__cplusplus) || defined(__bool_true_false_are_defined) + +// Use builtin bool type with C++. + +typedef bool boolean; + +#else + +typedef enum +{ + false, + true +} boolean; + +#endif + +typedef uint8_t byte; +typedef uint8_t pixel_t; +typedef int16_t dpixel_t; + +#include + + +#define DIR_SEPARATOR '/' +#define DIR_SEPARATOR_S "/" +#define PATH_SEPARATOR ':' + + +#define arrlen(array) (sizeof(array) / sizeof(*array)) + +#endif + diff --git a/client/src/ipu/dstrings.h b/client/src/ipu/dstrings.h new file mode 100644 index 0000000..7d2e388 --- /dev/null +++ b/client/src/ipu/dstrings.h @@ -0,0 +1,33 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: +// DOOM strings, by language. +// + +#ifndef __DSTRINGS__ +#define __DSTRINGS__ + +// Misc. other strings. +#define SAVEGAMENAME "doomsav" + +// QuitDOOM messages +// 8 per each game type +#define NUM_QUITMESSAGES 8 + +extern char *doom1_endmsg[]; +extern char *doom2_endmsg[]; + +#endif diff --git a/client/src/ipu/f_wipe.h b/client/src/ipu/f_wipe.h new file mode 100644 index 0000000..daaabfd --- /dev/null +++ b/client/src/ipu/f_wipe.h @@ -0,0 +1,42 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Mission start screen wipe/melt, special effects. +// + +#ifndef __F_WIPE_H__ +#define __F_WIPE_H__ + +// +// SCREEN WIPE PACKAGE +// + +enum { + // simple gradual pixel change for 8-bit only + wipe_ColorXForm, + + // weird screen melt + wipe_Melt, + + wipe_NUMWIPES +}; + +int wipe_StartScreen(int x, int y, int width, int height); + +int wipe_EndScreen(int x, int y, int width, int height); + +int wipe_ScreenWipe(int wipeno, int x, int y, int width, int height, int ticks); + +#endif diff --git a/client/src/ipu/g_game.c b/client/src/ipu/g_game.c new file mode 100644 index 0000000..1042127 --- /dev/null +++ b/client/src/ipu/g_game.c @@ -0,0 +1,393 @@ + + +#include "am_map.h" +#include "doomdef.h" +#include "g_game.h" +#include "doomstat.h" + +#include "ipu_print.h" + + +#define SAVEGAMESIZE 0x2c000 + + +// Gamestate the last time G_Ticker was called. + +gamestate_t oldgamestate; + +gameaction_t gameaction; +gamestate_t gamestate; +skill_t gameskill; +boolean respawnmonsters; +int gameepisode; +int gamemap; + +// If non-zero, exit the level after this number of minutes. + +int timelimit; + +boolean paused; +boolean sendpause; // send a pause event next tic +boolean sendsave; // send a save event next tic +boolean usergame; // ok to save / end game + +boolean timingdemo; // if true, exit with report on completion +boolean nodrawers; // for comparative timing purposes +int starttime; // for comparative timing purposes + +boolean viewactive; + +int deathmatch; // only if started as net death +boolean netgame = 0; // only true if packets are broadcast // JOSEF: singleplayer only +boolean playeringame[MAXPLAYERS]; +player_t players[MAXPLAYERS]; + +boolean turbodetected[MAXPLAYERS]; + +int consoleplayer; // player taking events and displaying +int displayplayer; // view being displayed +int levelstarttic; // gametic at level start +int totalkills, totalitems, totalsecret; // for intermission + +char *demoname; +boolean demorecording; +boolean longtics; // cph's doom 1.91 longtics hack +boolean lowres_turn; // low resolution turning for longtics +boolean demoplayback; +boolean netdemo; +byte *demobuffer; +byte *demo_p; +byte *demoend; +boolean singledemo; // quit after playing a demo from cmdline + +boolean precache = true; // if true, load all graphics at start + +boolean testcontrols = false; // Invoked by setup to test controls +int testcontrols_mousespeed; + +// from here... LATER + + +// +// G_DoLoadLevel +// +void G_DoLoadLevel(void) { + int i; + + // Set the sky map. + // First thing, we have a dummy sky texture name, + // a flat. The data is in the WAD only because + // we look for an actual index, instead of simply + // setting one. + + /* LATER + skyflatnum = R_FlatNumForName((SKYFLATNAME)); + + // The "Sky never changes in Doom II" bug was fixed in + // the id Anthology version of doom2.exe for Final Doom. + if ((gamemode == commercial) && + (gameversion == exe_final2 || gameversion == exe_chex)) { + char *skytexturename; + + if (gamemap < 12) { + skytexturename = "SKY1"; + } else if (gamemap < 21) { + skytexturename = "SKY2"; + } else { + skytexturename = "SKY3"; + } + + skytexture = R_TextureNumForName(skytexturename); + } + + levelstarttic = gametic; // for time calculation + + if (wipegamestate == GS_LEVEL) + wipegamestate = -1; // force a wipe + + gamestate = GS_LEVEL; + + for (i = 0; i < MAXPLAYERS; i++) { + turbodetected[i] = false; + if (playeringame[i] && players[i].playerstate == PST_DEAD) + players[i].playerstate = PST_REBORN; + memset(players[i].frags, 0, sizeof(players[i].frags)); + } + + P_SetupLevel(gameepisode, gamemap, 0, gameskill); + displayplayer = consoleplayer; // view the guy you are playing + gameaction = ga_nothing; + Z_CheckHeap(); + + // clear cmd building stuff + + memset(gamekeydown, 0, sizeof(gamekeydown)); + joyxmove = joyymove = joystrafemove = 0; + mousex = mousey = 0; + sendpause = sendsave = paused = false; + memset(mousearray, 0, sizeof(mousearray)); + memset(joyarray, 0, sizeof(joyarray)); + + if (testcontrols) { + players[consoleplayer].message = "Press escape to quit."; + } +*/ +} + +// +// G_Ticker +// Make ticcmd_ts for the players. +// +void G_Ticker(void) { + int i; + int buf; + ticcmd_t *cmd; + + reset_ipuprint(); + + /* LATER + // do player reborns if needed + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i] && players[i].playerstate == PST_REBORN) + G_DoReborn(i); + + // do things to change the game state + while (gameaction != ga_nothing) { + switch (gameaction) { + case ga_loadlevel: + G_DoLoadLevel(); + break; + case ga_newgame: + G_DoNewGame(); + break; + case ga_loadgame: + G_DoLoadGame(); + break; + case ga_savegame: + G_DoSaveGame(); + break; + case ga_playdemo: + G_DoPlayDemo(); + break; + case ga_completed: + G_DoCompleted(); + break; + case ga_victory: + break; + case ga_worlddone: + G_DoWorldDone(); + break; + case ga_screenshot: + V_ScreenShot("DOOM%02i.%s"); + players[consoleplayer].message = ("screen shot"); + gameaction = ga_nothing; + break; + case ga_nothing: + break; + } + } + + // get commands, check consistancy, + // and build new consistancy check + buf = (gametic / ticdup) % BACKUPTICS; + + for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + cmd = &players[i].cmd; + + memcpy(cmd, &netcmds[i], sizeof(ticcmd_t)); + + if (demoplayback) + G_ReadDemoTiccmd(cmd); + if (demorecording) + G_WriteDemoTiccmd(cmd); + + // check for turbo cheats + + // check ~ 4 seconds whether to display the turbo message. + // store if the turbo threshold was exceeded in any tics + // over the past 4 seconds. offset the checking period + // for each player so messages are not displayed at the + // same time. + + if (cmd->forwardmove > TURBOTHRESHOLD) { + turbodetected[i] = true; + } + + if ((gametic & 31) == 0 && ((gametic >> 5) % MAXPLAYERS) == i && + turbodetected[i]) { + static char turbomessage[80]; + extern char *player_names[4]; + M_snprintf(turbomessage, sizeof(turbomessage), "%s is turbo!", + player_names[i]); + players[consoleplayer].message = turbomessage; + turbodetected[i] = false; + } + + if (netgame && !netdemo && !(gametic % ticdup)) { + if (gametic > BACKUPTICS && consistancy[i][buf] != cmd->consistancy) { + I_Error("consistency failure (%i should be %i)", cmd->consistancy, + consistancy[i][buf]); + } + if (players[i].mo) + consistancy[i][buf] = players[i].mo->x; + else + consistancy[i][buf] = rndindex; + } + } + } + + // check for special buttons + for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + if (players[i].cmd.buttons & BT_SPECIAL) { + switch (players[i].cmd.buttons & BT_SPECIALMASK) { + case BTS_PAUSE: + paused ^= 1; + if (paused) + S_PauseSound(); + else + S_ResumeSound(); + break; + + case BTS_SAVEGAME: + if (!savedescription[0]) { + M_StringCopy(savedescription, "NET GAME", sizeof(savedescription)); + } + + savegameslot = + (players[i].cmd.buttons & BTS_SAVEMASK) >> BTS_SAVESHIFT; + gameaction = ga_savegame; + break; + } + } + } + } + + // Have we just finished displaying an intermission screen? + + if (oldgamestate == GS_INTERMISSION && gamestate != GS_INTERMISSION) { + WI_End(); + } + + oldgamestate = gamestate; + */ + + // do main actions + switch (gamestate) { + case GS_LEVEL: + // P_Ticker(); // LATER + // ST_Ticker(); // LATER + AM_Ticker(); + // HU_Ticker(); // LATER + break; + + case GS_INTERMISSION: + // WI_Ticker(); // LATER + break; + + case GS_DEMOSCREEN: + // D_PageTicker(); // LATER + break; + } + + +} + + +// +// G_Responder +// Get info needed to make ticcmd_ts for the players. +// +boolean G_Responder(event_t *ev) { + + /* LATER + // allow spy mode changes even during the demo + if (gamestate == GS_LEVEL && ev->type == ev_keydown && ev->data1 == key_spy && + (singledemo || !deathmatch)) { + // spy mode + do { + displayplayer++; + if (displayplayer == MAXPLAYERS) + displayplayer = 0; + } while (!playeringame[displayplayer] && displayplayer != consoleplayer); + return true; + } + + // any other key pops up menu if in demos + if (gameaction == ga_nothing && !singledemo && + (demoplayback || gamestate == GS_DEMOSCREEN)) { + if (ev->type == ev_keydown || (ev->type == ev_mouse && ev->data1) || + (ev->type == ev_joystick && ev->data1)) { + M_StartControlPanel(); + return true; + } + return false; + } + */ + + if (gamestate == GS_LEVEL) { + /* LATER + if (HU_Responder(ev)) + return true; // chat ate the event + if (ST_Responder(ev)) + return true; // status window ate it + */ + if (AM_Responder(ev)) + return true; // automap ate it + } + + /* LATER + if (testcontrols && ev->type == ev_mouse) { + // If we are invoked by setup to test the controls, save the + // mouse speed so that we can display it on-screen. + // Perform a low pass filter on this so that the thermometer + // appears to move smoothly. + + testcontrols_mousespeed = abs(ev->data2); + } + + // If the next/previous weapon keys are pressed, set the next_weapon + // variable to change weapons when the next ticcmd is generated. + + if (ev->type == ev_keydown && ev->data1 == key_prevweapon) { + next_weapon = -1; + } else if (ev->type == ev_keydown && ev->data1 == key_nextweapon) { + next_weapon = 1; + } + + switch (ev->type) { + case ev_keydown: + if (ev->data1 == key_pause) { + sendpause = true; + } else if (ev->data1 < NUMKEYS) { + gamekeydown[ev->data1] = true; + } + + return true; // eat key down events + + case ev_keyup: + if (ev->data1 < NUMKEYS) + gamekeydown[ev->data1] = false; + return false; // always let key up events filter down + + case ev_mouse: + SetMouseButtons(ev->data1); + mousex = ev->data2 * (mouseSensitivity + 5) / 10; + mousey = ev->data3 * (mouseSensitivity + 5) / 10; + return true; // eat events + + case ev_joystick: + SetJoyButtons(ev->data1); + joyxmove = ev->data2; + joyymove = ev->data3; + joystrafemove = ev->data4; + return true; // eat events + + default: + break; + } + + */ + return false; +} \ No newline at end of file diff --git a/client/src/ipu/g_game.h b/client/src/ipu/g_game.h new file mode 100644 index 0000000..528779d --- /dev/null +++ b/client/src/ipu/g_game.h @@ -0,0 +1,83 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Duh. +// + +#ifndef __G_GAME__ +#define __G_GAME__ + +#include "d_event.h" +#include "d_mode.h" +#include "d_ticcmd.h" +#include "doomtype.h" + + +// JOSEF TMP +void G_DoLoadLevel(void); + +// +// GAME +// +void G_DeathMatchSpawnPlayer(int playernum); + +void G_InitNew(skill_t skill, int episode, int map); + +// Can be called by the startup code or M_Responder. +// A normal game starts at map 1, +// but a warp test can start elsewhere +void G_DeferedInitNew(skill_t skill, int episode, int map); + +void G_DeferedPlayDemo(char *demo); + +// Can be called by the startup code or M_Responder, +// calls P_SetupLevel or W_EnterWorld. +void G_LoadGame(char *name); + +void G_DoLoadGame(void); + +// Called by M_Responder. +void G_SaveGame(int slot, char *description); + +// Only called by startup code. +void G_RecordDemo(char *name); + +void G_BeginRecording(void); + +void G_PlayDemo(char *name); +void G_TimeDemo(char *name); +boolean G_CheckDemoStatus(void); + +void G_ExitLevel(void); +void G_SecretExitLevel(void); + +void G_WorldDone(void); + +// Read current data from inputs and build a player movement command. + +void G_BuildTiccmd(ticcmd_t *cmd, int maketic); + +void G_Ticker(void); +boolean G_Responder(event_t *ev); + +void G_ScreenShot(void); + +void G_DrawMouseSpeedBox(void); +int G_VanillaVersionCode(void); + + +extern int vanilla_savegame_limit; +extern int vanilla_demo_limit; +#endif diff --git a/client/src/ipu/g_game_codelets.cpp b/client/src/ipu/g_game_codelets.cpp new file mode 100644 index 0000000..0f51805 --- /dev/null +++ b/client/src/ipu/g_game_codelets.cpp @@ -0,0 +1,45 @@ + +#include + +#include "ipu_transfer.h" + +extern "C" { + void G_DoLoadLevel(void); + void G_Ticker(void); + boolean G_Responder(event_t *ev); +}; + + +class G_DoLoadLevel_Vertex : public poplar::Vertex { + public: + poplar::Input> miscValues; + bool compute() { + IPU_G_LoadLevel_UnpackMiscValues((G_LoadLevel_MiscValues_t*)&miscValues[0]); + G_DoLoadLevel(); + return true; + } +}; + + +class G_Ticker_Vertex : public poplar::Vertex { + public: + poplar::Input> miscValues; + bool compute() { + IPU_G_Ticker_UnpackMiscValues((G_Ticker_MiscValues_t*)&miscValues[0]); + G_Ticker(); + return true; + } +}; + +class G_Responder_Vertex : public poplar::Vertex { + public: + poplar::Input> miscValues; + bool compute() { + // Respond to each of the buffered events in turn + G_Responder_MiscValues_t* ev_buf = (G_Responder_MiscValues_t*)&miscValues[0]; + for (int i = 0; i < ev_buf->num_ev; ++i) { + G_Responder(&ev_buf->events[i]); + } + return true; + } +}; \ No newline at end of file diff --git a/client/src/ipu/gusconf.h b/client/src/ipu/gusconf.h new file mode 100644 index 0000000..4efbe99 --- /dev/null +++ b/client/src/ipu/gusconf.h @@ -0,0 +1,29 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// GUS emulation code. +// + +#ifndef __GUSCONF_H__ +#define __GUSCONF_H__ + +#include "doomtype.h" + +extern char *gus_patch_path; +extern int gus_ram_kb; + +boolean GUS_WriteConfig(char *path); + +#endif /* #ifndef __GUSCONF_H__ */ + diff --git a/client/src/ipu/hu_lib.h b/client/src/ipu/hu_lib.h new file mode 100644 index 0000000..085cadb --- /dev/null +++ b/client/src/ipu/hu_lib.h @@ -0,0 +1,152 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: none +// + +#ifndef __HULIB__ +#define __HULIB__ + +#include "doomkeys.h" +#include "doomtype.h" +// We are referring to patches. +#include "v_patch.h" + +// font stuff +#define HU_CHARERASE KEY_BACKSPACE + +#define HU_MAXLINES 4 +#define HU_MAXLINELENGTH 80 + +// +// Typedefs of widgets +// + +// Text Line widget +// (parent of Scrolling Text and Input Text widgets) +typedef struct { + // left-justified position of scrolling text window + int x; + int y; + + patch_t **f; // font + int sc; // start character + char l[HU_MAXLINELENGTH + 1]; // line of text + int len; // current line length + + // whether this line needs to be udpated + int needsupdate; + +} hu_textline_t; + +// Scrolling Text window widget +// (child of Text Line widget) +typedef struct { + hu_textline_t l[HU_MAXLINES]; // text lines to draw + int h; // height in lines + int cl; // current line number + + // pointer to boolean stating whether to update window + boolean *on; + boolean laston; // last value of *->on. + +} hu_stext_t; + +// Input Text Line widget +// (child of Text Line widget) +typedef struct { + hu_textline_t l; // text line to input on + + // left margin past which I am not to delete characters + int lm; + + // pointer to boolean stating whether to update window + boolean *on; + boolean laston; // last value of *->on; + +} hu_itext_t; + +// +// Widget creation, access, and update routines +// + +// initializes heads-up widget library +void HUlib_init(void); + +// +// textline code +// + +// clear a line of text +void HUlib_clearTextLine(hu_textline_t *t); + +void HUlib_initTextLine(hu_textline_t *t, int x, int y, patch_t **f, int sc); + +// returns success +boolean HUlib_addCharToTextLine(hu_textline_t *t, char ch); + +// returns success +boolean HUlib_delCharFromTextLine(hu_textline_t *t); + +// draws tline +void HUlib_drawTextLine(hu_textline_t *l, boolean drawcursor); + +// erases text line +void HUlib_eraseTextLine(hu_textline_t *l); + +// +// Scrolling Text window widget routines +// + +// ? +void HUlib_initSText(hu_stext_t *s, int x, int y, int h, patch_t **font, + int startchar, boolean *on); + +// add a new line +void HUlib_addLineToSText(hu_stext_t *s); + +// ? +void HUlib_addMessageToSText(hu_stext_t *s, char *prefix, char *msg); + +// draws stext +void HUlib_drawSText(hu_stext_t *s); + +// erases all stext lines +void HUlib_eraseSText(hu_stext_t *s); + +// Input Text Line widget routines +void HUlib_initIText(hu_itext_t *it, int x, int y, patch_t **font, + int startchar, boolean *on); + +// enforces left margin +void HUlib_delCharFromIText(hu_itext_t *it); + +// enforces left margin +void HUlib_eraseLineFromIText(hu_itext_t *it); + +// resets line and left margin +void HUlib_resetIText(hu_itext_t *it); + +// left of left-margin +void HUlib_addPrefixToIText(hu_itext_t *it, char *str); + +// whether eaten +boolean HUlib_keyInIText(hu_itext_t *it, unsigned char ch); + +void HUlib_drawIText(hu_itext_t *it); + +// erases all itext lines +void HUlib_eraseIText(hu_itext_t *it); + +#endif diff --git a/client/src/ipu/hu_stuff.h b/client/src/ipu/hu_stuff.h new file mode 100644 index 0000000..3ebac49 --- /dev/null +++ b/client/src/ipu/hu_stuff.h @@ -0,0 +1,59 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: Head up display +// + +#ifndef __HU_STUFF_H__ +#define __HU_STUFF_H__ + +#include "d_event.h" +#include "doomtype.h" +#include "i_timer.h" + +// +// Globally visible constants. +// +#define HU_FONTSTART '!' // the first font characters +#define HU_FONTEND '_' // the last font characters + +// Calculate # of glyphs in font. +#define HU_FONTSIZE (HU_FONTEND - HU_FONTSTART + 1) + +#define HU_BROADCAST 5 + +#define HU_MSGX 0 +#define HU_MSGY 0 +#define HU_MSGWIDTH 64 // in characters +#define HU_MSGHEIGHT 1 // in lines + +#define HU_MSGTIMEOUT (4 * TICRATE) + +// +// HEADS UP TEXT +// + +void HU_Init(void); +void HU_Start(void); + +boolean HU_Responder(event_t *ev); + +void HU_Ticker(void); +void HU_Drawer(void); +char HU_dequeueChatChar(void); +void HU_Erase(void); + +extern char *chat_macros[10]; + +#endif diff --git a/client/src/ipu/i_cdmus.h b/client/src/ipu/i_cdmus.h new file mode 100644 index 0000000..31db2a6 --- /dev/null +++ b/client/src/ipu/i_cdmus.h @@ -0,0 +1,41 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// +// 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. +// + +// i_cdmus.h + +#ifndef __ICDMUS__ +#define __ICDMUS__ + +#define CDERR_NOTINSTALLED 10 // MSCDEX not installed +#define CDERR_NOAUDIOSUPPORT 11 // CD-ROM Doesn't support audio +#define CDERR_NOAUDIOTRACKS 12 // Current CD has no audio tracks +#define CDERR_BADDRIVE 20 // Bad drive number +#define CDERR_BADTRACK 21 // Bad track number +#define CDERR_IOCTLBUFFMEM 22 // Not enough low memory for IOCTL +#define CDERR_DEVREQBASE 100 // DevReq errors + +extern int cd_Error; + +int I_CDMusInit(void); +void I_CDMusPrintStartup(void); +int I_CDMusPlay(int track); +int I_CDMusStop(void); +int I_CDMusResume(void); +int I_CDMusSetVolume(int volume); +int I_CDMusFirstTrack(void); +int I_CDMusLastTrack(void); +int I_CDMusTrackLength(int track); + +#endif diff --git a/client/src/ipu/i_input.h b/client/src/ipu/i_input.h new file mode 100644 index 0000000..5da6e5f --- /dev/null +++ b/client/src/ipu/i_input.h @@ -0,0 +1,40 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System-specific keyboard/mouse input. +// + + +#ifndef __I_INPUT__ +#define __I_INPUT__ + +#define MAX_MOUSE_BUTTONS 8 + +extern float mouse_acceleration; +extern int mouse_threshold; + +void I_BindInputVariables(void); +void I_ReadMouse(void); + +// I_StartTextInput begins text input, activating the on-screen keyboard +// (if one is used). The caller indicates that any entered text will be +// displayed in the rectangle given by the provided set of coordinates. +void I_StartTextInput(int x1, int y1, int x2, int y2); + +// I_StopTextInput finishes text input, deactivating the on-screen keyboard +// (if one is used). +void I_StopTextInput(void); + +#endif diff --git a/client/src/ipu/i_joystick.h b/client/src/ipu/i_joystick.h new file mode 100644 index 0000000..1c9744f --- /dev/null +++ b/client/src/ipu/i_joystick.h @@ -0,0 +1,70 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System-specific joystick interface. +// + + +#ifndef __I_JOYSTICK__ +#define __I_JOYSTICK__ + +// Number of "virtual" joystick buttons defined in configuration files. +// This needs to be at least as large as the number of different key +// bindings supported by the higher-level game code (joyb* variables). +#define NUM_VIRTUAL_BUTTONS 11 + +// If this bit is set in a configuration file axis value, the axis is +// not actually a joystick axis, but instead is a "button axis". This +// means that instead of reading an SDL joystick axis, we read the +// state of two buttons to get the axis value. This is needed for eg. +// the PS3 SIXAXIS controller, where the D-pad buttons register as +// buttons, not as two axes. +#define BUTTON_AXIS 0x10000 + +// Query whether a given axis value describes a button axis. +#define IS_BUTTON_AXIS(axis) ((axis) >= 0 && ((axis) & BUTTON_AXIS) != 0) + +// Get the individual buttons from a button axis value. +#define BUTTON_AXIS_NEG(axis) ((axis) & 0xff) +#define BUTTON_AXIS_POS(axis) (((axis) >> 8) & 0xff) + +// Create a button axis value from two button values. +#define CREATE_BUTTON_AXIS(neg, pos) (BUTTON_AXIS | (neg) | ((pos) << 8)) + +// If this bit is set in an axis value, the axis is not actually a +// joystick axis, but is a "hat" axis. This means that we read (one of) +// the hats on the joystick. +#define HAT_AXIS 0x20000 + +#define IS_HAT_AXIS(axis) ((axis) >= 0 && ((axis) & HAT_AXIS) != 0) + +// Get the hat number from a hat axis value. +#define HAT_AXIS_HAT(axis) ((axis) & 0xff) +// Which axis of the hat? (horizonal or vertical) +#define HAT_AXIS_DIRECTION(axis) (((axis) >> 8) & 0xff) + +#define CREATE_HAT_AXIS(hat, direction) \ + (HAT_AXIS | (hat) | ((direction) << 8)) + +#define HAT_AXIS_HORIZONTAL 1 +#define HAT_AXIS_VERTICAL 2 + +void I_InitJoystick(void); +void I_ShutdownJoystick(void); +void I_UpdateJoystick(void); + +void I_BindJoystickVariables(void); + +#endif /* #ifndef __I_JOYSTICK__ */ + diff --git a/client/src/ipu/i_sound.h b/client/src/ipu/i_sound.h new file mode 100644 index 0000000..2605414 --- /dev/null +++ b/client/src/ipu/i_sound.h @@ -0,0 +1,243 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// The not so system specific sound interface. +// + + +#ifndef __I_SOUND__ +#define __I_SOUND__ + +#include "doomtype.h" + +struct sfxinfo_struct; + +// so that the individual game logic and sound driver code agree +#define NORM_PITCH 127 + +// +// SoundFX struct. +// +typedef struct sfxinfo_struct sfxinfo_t; + +struct sfxinfo_struct +{ + // tag name, used for hexen. + char *tagname; + + // lump name. If we are running with use_sfx_prefix=true, a + // 'DS' (or 'DP' for PC speaker sounds) is prepended to this. + + char name[9]; + + // Sfx priority + int priority; + + // referenced sound if a link + sfxinfo_t *link; + + // pitch if a link (Doom), whether to pitch-shift (Hexen) + int pitch; + + // volume if a link + int volume; + + // this is checked every second to see if sound + // can be thrown out (if 0, then decrement, if -1, + // then throw out, if > 0, then it is in use) + int usefulness; + + // lump number of sfx + int lumpnum; + + // Maximum number of channels that the sound can be played on + // (Heretic) + int numchannels; + + // data used by the low level code + void *driver_data; +}; + +// +// MusicInfo struct. +// +typedef struct +{ + // up to 6-character name + char *name; + + // lump number of music + int lumpnum; + + // music data + void *data; + + // music handle once registered + void *handle; + +} musicinfo_t; + +typedef enum +{ + SNDDEVICE_NONE = 0, + SNDDEVICE_PCSPEAKER = 1, + SNDDEVICE_ADLIB = 2, + SNDDEVICE_SB = 3, + SNDDEVICE_PAS = 4, + SNDDEVICE_GUS = 5, + SNDDEVICE_WAVEBLASTER = 6, + SNDDEVICE_SOUNDCANVAS = 7, + SNDDEVICE_GENMIDI = 8, + SNDDEVICE_AWE32 = 9, + SNDDEVICE_CD = 10, +} snddevice_t; + +// Interface for sound modules + +typedef struct +{ + // List of sound devices that this sound module is used for. + + snddevice_t *sound_devices; + int num_sound_devices; + + // Initialise sound module + // Returns true if successfully initialised + + boolean (*Init)(boolean use_sfx_prefix); + + // Shutdown sound module + + void (*Shutdown)(void); + + // Returns the lump index of the given sound. + + int (*GetSfxLumpNum)(sfxinfo_t *sfxinfo); + + // Called periodically to update the subsystem. + + void (*Update)(void); + + // Update the sound settings on the given channel. + + void (*UpdateSoundParams)(int channel, int vol, int sep); + + // Start a sound on a given channel. Returns the channel id + // or -1 on failure. + + int (*StartSound)(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch); + + // Stop the sound playing on the given channel. + + void (*StopSound)(int channel); + + // Query if a sound is playing on the given channel + + boolean (*SoundIsPlaying)(int channel); + + // Called on startup to precache sound effects (if necessary) + + void (*CacheSounds)(sfxinfo_t *sounds, int num_sounds); + +} sound_module_t; + +void I_InitSound(boolean use_sfx_prefix); +void I_ShutdownSound(void); +int I_GetSfxLumpNum(sfxinfo_t *sfxinfo); +void I_UpdateSound(void); +void I_UpdateSoundParams(int channel, int vol, int sep); +int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch); +void I_StopSound(int channel); +boolean I_SoundIsPlaying(int channel); +void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds); + +// Interface for music modules + +typedef struct +{ + // List of sound devices that this music module is used for. + + snddevice_t *sound_devices; + int num_sound_devices; + + // Initialise the music subsystem + + boolean (*Init)(void); + + // Shutdown the music subsystem + + void (*Shutdown)(void); + + // Set music volume - range 0-127 + + void (*SetMusicVolume)(int volume); + + // Pause music + + void (*PauseMusic)(void); + + // Un-pause music + + void (*ResumeMusic)(void); + + // Register a song handle from data + // Returns a handle that can be used to play the song + + void *(*RegisterSong)(void *data, int len); + + // Un-register (free) song data + + void (*UnRegisterSong)(void *handle); + + // Play the song + + void (*PlaySong)(void *handle, boolean looping); + + // Stop playing the current song. + + void (*StopSong)(void); + + // Query if music is playing. + + boolean (*MusicIsPlaying)(void); + + // Invoked periodically to poll. + + void (*Poll)(void); +} music_module_t; + +void I_InitMusic(void); +void I_ShutdownMusic(void); +void I_SetMusicVolume(int volume); +void I_PauseSong(void); +void I_ResumeSong(void); +void *I_RegisterSong(void *data, int len); +void I_UnRegisterSong(void *handle); +void I_PlaySong(void *handle, boolean looping); +void I_StopSong(void); +boolean I_MusicIsPlaying(void); + +extern int snd_sfxdevice; +extern int snd_musicdevice; +extern int snd_samplerate; +extern int snd_cachesize; +extern int snd_maxslicetime_ms; +extern char *snd_musiccmd; +extern int snd_pitchshift; + +void I_BindSoundVariables(void); + +#endif + diff --git a/client/src/ipu/i_swap.h b/client/src/ipu/i_swap.h new file mode 100644 index 0000000..19bd87c --- /dev/null +++ b/client/src/ipu/i_swap.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Endianess handling, swapping 16bit and 32bit. +// + + +#ifndef __I_SWAP__ +#define __I_SWAP__ + +// #include "SDL2/SDL_endian.h" // JOSEF + +// Endianess handling. +// WAD files are stored little endian. + +// Just use SDL's endianness swapping functions. + +// These are deliberately cast to signed values; this is the behaviour +// of the macros in the original source and some code relies on it. + +// #define SHORT(x) ((signed short) SDL_SwapLE16(x)) // JOSEF +// #define LONG(x) ((signed int) SDL_SwapLE32(x)) // JOSEF +#define SHORT(x) ((signed short) x) +#define LONG(x) ((signed int) x) + +// Defines for checking the endianness of the system. + +// #if SDL_BYTEORDER == SDL_BIG_ENDIAN +// #define SYS_BIG_ENDIAN +// #endif + +#endif + diff --git a/client/src/ipu/i_system.h b/client/src/ipu/i_system.h new file mode 100644 index 0000000..f0a1c29 --- /dev/null +++ b/client/src/ipu/i_system.h @@ -0,0 +1,84 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + + +#ifndef __I_SYSTEM__ +#define __I_SYSTEM__ + +#include "d_ticcmd.h" +#include "doomtype.h" + + +typedef void (*atexit_func_t)(void); + +// Called by DoomMain. +void I_Init (void); + +// Called by startup code +// to get the ammount of memory to malloc +// for the zone management. +byte* I_ZoneBase (int *size); + +boolean I_ConsoleStdout(void); + + +// Asynchronous interrupt functions should maintain private queues +// that are read by the synchronous functions +// to be converted into events. + +// Either returns a null ticcmd, +// or calls a loadable driver to build it. +// This ticcmd will then be modified by the gameloop +// for normal input. +ticcmd_t* I_BaseTiccmd (void); + + +// Called by M_Responder when quit is selected. +// Clean exit, displays sell blurb. +void I_Quit (void); + +void I_Error (char *error, ...); + +void I_Tactile (int on, int off, int total); + +boolean I_GetMemoryValue(unsigned int offset, void *value, int size); + +// Schedule a function to be called when the program exits. +// If run_if_error is true, the function is called if the exit +// is due to an error (I_Error) + +void I_AtExit(atexit_func_t func, boolean run_if_error); + +// Add all system-specific config file variable bindings. + +void I_BindVariables(void); + +// Print startup banner copyright message. + +void I_PrintStartupBanner(char *gamedescription); + +// Print a centered text banner displaying the given string. + +void I_PrintBanner(char *text); + +// Print a dividing line for startup banners. + +void I_PrintDivider(void); + +#endif + diff --git a/client/src/ipu/i_timer.h b/client/src/ipu/i_timer.h new file mode 100644 index 0000000..9b3dbb8 --- /dev/null +++ b/client/src/ipu/i_timer.h @@ -0,0 +1,42 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System-specific timer interface +// + + +#ifndef __I_TIMER__ +#define __I_TIMER__ + +#define TICRATE 35 + +// Called by D_DoomLoop, +// returns current time in tics. +int I_GetTime (void); + +// returns current time in ms +int I_GetTimeMS (void); + +// Pause for a specified number of ms +void I_Sleep(int ms); + +// Initialize timer +void I_InitTimer(void); + +// Wait for vertical retrace or pause a bit. +void I_WaitVBL(int count); + +#endif + diff --git a/client/src/ipu/i_video.c b/client/src/ipu/i_video.c new file mode 100644 index 0000000..e56a344 --- /dev/null +++ b/client/src/ipu/i_video.c @@ -0,0 +1,1471 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// DOOM graphics stuff for SDL. +// + +/* LATER +#include +#include +#include +#include +#include + +#include "SDL2/SDL.h" +#include "SDL2/SDL_error.h" +#include "SDL2/SDL_events.h" +#include "SDL2/SDL_hints.h" +#include "SDL2/SDL_keyboard.h" +#include "SDL2/SDL_keycode.h" +#include "SDL2/SDL_mouse.h" +#include "SDL2/SDL_opengl.h" +#include "SDL2/SDL_pixels.h" +#include "SDL2/SDL_platform.h" +#include "SDL2/SDL_rect.h" +#include "SDL2/SDL_render.h" +#include "SDL2/SDL_scancode.h" +#include "SDL2/SDL_stdinc.h" +#include "SDL2/SDL_surface.h" +#include "SDL2/SDL_timer.h" +#include "SDL2/SDL_version.h" +#include "SDL2/SDL_video.h" +#include "d_event.h" +#include "d_loop.h" +#include "i_input.h" +#include "i_joystick.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" +#include "m_argv.h" +#include "m_config.h" +#include "m_misc.h" +#include "tables.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" +*/ + +#include "doomtype.h" + +/* LATER +// These are (1) the window (or the full screen) that our game is rendered to +// and (2) the renderer that scales the texture (see below) into this window. + +static SDL_Window *screen; +static SDL_Renderer *renderer; + +// Window title + +static char *window_title = ""; + +// These are (1) the 320x200x8 paletted buffer that we draw to (i.e. the one +// that holds I_VideoBuffer), (2) the 320x200x32 RGBA intermediate buffer that +// we blit the former buffer to, (3) the intermediate 320x200 texture that we +// load the RGBA buffer to and that we render into another texture (4) which +// is upscaled by an integer factor UPSCALE using "nearest" scaling and which +// in turn is finally rendered to screen using "linear" scaling. + +static SDL_Surface *screenbuffer = NULL; +static SDL_Surface *rgbabuffer = NULL; +static SDL_Texture *texture = NULL; +static SDL_Texture *texture_upscaled = NULL; + +static SDL_Rect blit_rect = { + 0, + 0, + SCREENWIDTH, + SCREENHEIGHT +}; + +static uint32_t pixel_format; + +// palette + +static SDL_Color palette[256]; +static boolean palette_to_set; + +// display has been set up? + +static boolean initialized = false; + +// disable mouse? + +static boolean nomouse = false; +int usemouse = 1; + +// Save screenshots in PNG format. + +int png_screenshots = 0; + +// SDL video driver name + +char *video_driver = ""; + +// Window position: + +char *window_position = "center"; + +// SDL display number on which to run. + +int video_display = 0; + +// Screen width and height, from configuration file. + +int window_width = SCREENWIDTH * 2; +int window_height = SCREENHEIGHT_4_3 * 2; + +// Fullscreen mode, 0x0 for SDL_WINDOW_FULLSCREEN_DESKTOP. + +int fullscreen_width = 0, fullscreen_height = 0; + +// Maximum number of pixels to use for intermediate scale buffer. + +static int max_scaling_buffer_pixels = 16000000; + +// Run in full screen mode? (int type for config code) + +int fullscreen = true; + +// Aspect ratio correction mode + +int aspect_ratio_correct = true; + +// Force integer scales for resolution-independent rendering + +int integer_scaling = false; + +// VGA Porch palette change emulation + +int vga_porch_flash = false; + +// Force software rendering, for systems which lack effective hardware +// acceleration + +int force_software_renderer = false; + +// Time to wait for the screen to settle on startup before starting the +// game (ms) + +static int startup_delay = 10; + +// Grab the mouse? (int type for config code). nograbmouse_override allows +// this to be temporarily disabled via the command line. + +static int grabmouse = true; +static boolean nograbmouse_override = false; + +*/ + +// The screen buffer; this is modified to draw things to the screen +pixel_t *I_VideoBuffer = NULL; + +/* LATER +// If true, game is running as a screensaver + +boolean screensaver_mode = false; + +// Flag indicating whether the screen is currently visible: +// when the screen isnt visible, don't render the screen + +boolean screenvisible = true; + +// If true, we display dots at the bottom of the screen to +// indicate FPS. + +static boolean display_fps_dots; + +// If this is true, the screen is rendered but not blitted to the +// video buffer. + +static boolean noblit; + +// Callback function to invoke to determine whether to grab the +// mouse pointer. + +static grabmouse_callback_t grabmouse_callback = NULL; + +// Does the window currently have focus? + +static boolean window_focused = true; + +// Window resize state. + +static boolean need_resize = false; +static unsigned int last_resize_time; +#define RESIZE_DELAY 500 + +// Gamma correction level to use + +int usegamma = 0; + +// Joystick/gamepad hysteresis +unsigned int joywait = 0; + +static boolean MouseShouldBeGrabbed() +{ + // never grab the mouse when in screensaver mode + + if (screensaver_mode) + return false; + + // if the window doesn't have focus, never grab it + + if (!window_focused) + return false; + + // always grab the mouse when full screen (dont want to + // see the mouse pointer) + + if (fullscreen) + return true; + + // Don't grab the mouse if mouse input is disabled + + if (!usemouse || nomouse) + return false; + + // if we specify not to grab the mouse, never grab + + if (nograbmouse_override || !grabmouse) + return false; + + // Invoke the grabmouse callback function to determine whether + // the mouse should be grabbed + + if (grabmouse_callback != NULL) + { + return grabmouse_callback(); + } + else + { + return true; + } +} + +void I_SetGrabMouseCallback(grabmouse_callback_t func) +{ + grabmouse_callback = func; +} + +// Set the variable controlling FPS dots. + +void I_DisplayFPSDots(boolean dots_on) +{ + display_fps_dots = dots_on; +} + +static void SetShowCursor(boolean show) +{ + if (!screensaver_mode) + { + // When the cursor is hidden, grab the input. + // Relative mode implicitly hides the cursor. + SDL_SetRelativeMouseMode(!show); + SDL_GetRelativeMouseState(NULL, NULL); + } +} + +void I_ShutdownGraphics(void) +{ + if (initialized) + { + SetShowCursor(true); + + SDL_QuitSubSystem(SDL_INIT_VIDEO); + + initialized = false; + } +} + + + +// +// I_StartFrame +// +void I_StartFrame (void) +{ + // er? + +} + +// Returns base screen height - either SCREENHEIGHT_4_3 or SCREENHEIGHT, +// dependent on aspect_ratio_correct value. +static int EffectiveScreenHeight(void) +{ + if (aspect_ratio_correct) + { + return SCREENHEIGHT_4_3; + } + else + { + return SCREENHEIGHT; + } +} + +// Adjust window_width / window_height variables to be an an aspect +// ratio consistent with the aspect_ratio_correct variable. +static void AdjustWindowSize(void) +{ + int h; + + h = EffectiveScreenHeight(); + + if (window_width * h <= window_height * SCREENWIDTH) + { + // We round up window_height if the ratio is not exact; this leaves + // the result stable. + window_height = (window_width * h + SCREENWIDTH - 1) / SCREENWIDTH; + } + else + { + window_width = window_height * SCREENWIDTH / h; + } +} + +static void HandleWindowEvent(SDL_WindowEvent *event) +{ + int i; + + switch (event->event) + { +#if 0 // SDL2-TODO + case SDL_ACTIVEEVENT: + // need to update our focus state + UpdateFocus(); + break; +#endif + case SDL_WINDOWEVENT_EXPOSED: + palette_to_set = true; + break; + + case SDL_WINDOWEVENT_RESIZED: + need_resize = true; + last_resize_time = SDL_GetTicks(); + break; + + // Don't render the screen when the window is minimized: + + case SDL_WINDOWEVENT_MINIMIZED: + screenvisible = false; + break; + + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + screenvisible = true; + break; + + // Update the value of window_focused when we get a focus event + // + // We try to make ourselves be well-behaved: the grab on the mouse + // is removed if we lose focus (such as a popup window appearing), + // and we dont move the mouse around if we aren't focused either. + + case SDL_WINDOWEVENT_FOCUS_GAINED: + window_focused = true; + break; + + case SDL_WINDOWEVENT_FOCUS_LOST: + window_focused = false; + break; + + // We want to save the user's preferred monitor to use for running the + // game, so that next time we're run we start on the same display. So + // every time the window is moved, find which display we're now on and + // update the video_display config variable. + + case SDL_WINDOWEVENT_MOVED: + i = SDL_GetWindowDisplayIndex(screen); + if (i >= 0) + { + video_display = i; + } + break; + + default: + break; + } +} + +static boolean ToggleFullScreenKeyShortcut(SDL_Keysym *sym) +{ + Uint16 flags = (KMOD_LALT | KMOD_RALT); +#if defined(__MACOSX__) + flags |= (KMOD_LGUI | KMOD_RGUI); +#endif + return sym->scancode == SDL_SCANCODE_RETURN && (sym->mod & flags) != 0; +} + +static void I_ToggleFullScreen(void) +{ + unsigned int flags = 0; + + // TODO: Consider implementing fullscreen toggle for SDL_WINDOW_FULLSCREEN + // (mode-changing) setup. This is hard because we have to shut down and + // restart again. + if (fullscreen_width != 0 || fullscreen_height != 0) + { + return; + } + + fullscreen = !fullscreen; + + if (fullscreen) + { + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + + SDL_SetWindowFullscreen(screen, flags); + + if (!fullscreen) + { + AdjustWindowSize(); + SDL_SetWindowSize(screen, window_width, window_height); + } +} + +void I_GetEvent(void) +{ + extern void I_HandleKeyboardEvent(SDL_Event *sdlevent); + extern void I_HandleMouseEvent(SDL_Event *sdlevent); + SDL_Event sdlevent; + + SDL_PumpEvents(); + + while (SDL_PollEvent(&sdlevent)) + { + switch (sdlevent.type) + { + case SDL_KEYDOWN: + if (ToggleFullScreenKeyShortcut(&sdlevent.key.keysym)) + { + I_ToggleFullScreen(); + break; + } + // deliberate fall-though + + case SDL_KEYUP: + I_HandleKeyboardEvent(&sdlevent); + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEWHEEL: + if (usemouse && !nomouse && window_focused) + { + I_HandleMouseEvent(&sdlevent); + } + break; + + case SDL_QUIT: + if (screensaver_mode) + { + I_Quit(); + } + else + { + event_t event; + event.type = ev_quit; + D_PostEvent(&event); + } + break; + + case SDL_WINDOWEVENT: + if (sdlevent.window.windowID == SDL_GetWindowID(screen)) + { + HandleWindowEvent(&sdlevent.window); + } + break; + + default: + break; + } + } +} + +// +// I_StartTic +// +void I_StartTic (void) +{ + if (!initialized) + { + return; + } + + I_GetEvent(); + + if (usemouse && !nomouse) + { + I_ReadMouse(); + } + + if (joywait < I_GetTime()) + { + I_UpdateJoystick(); + } +} + + +// +// I_UpdateNoBlit +// +void I_UpdateNoBlit (void) +{ + // what is this? +} + +static void UpdateGrab(void) +{ + static boolean currently_grabbed = false; + boolean grab; + + grab = MouseShouldBeGrabbed(); + + if (screensaver_mode) + { + // Hide the cursor in screensaver mode + + SetShowCursor(false); + } + else if (grab && !currently_grabbed) + { + SetShowCursor(false); + } + else if (!grab && currently_grabbed) + { + int screen_w, screen_h; + + SetShowCursor(true); + + // When releasing the mouse from grab, warp the mouse cursor to + // the bottom-right of the screen. This is a minimally distracting + // place for it to appear - we may only have released the grab + // because we're at an end of level intermission screen, for + // example. + + SDL_GetWindowSize(screen, &screen_w, &screen_h); + SDL_WarpMouseInWindow(screen, screen_w - 16, screen_h - 16); + SDL_GetRelativeMouseState(NULL, NULL); + } + + currently_grabbed = grab; +} + +static void LimitTextureSize(int *w_upscale, int *h_upscale) +{ + SDL_RendererInfo rinfo; + int orig_w, orig_h; + + orig_w = *w_upscale; + orig_h = *h_upscale; + + // Query renderer and limit to maximum texture dimensions of hardware: + if (SDL_GetRendererInfo(renderer, &rinfo) != 0) + { + I_Error("CreateUpscaledTexture: SDL_GetRendererInfo() call failed: %s", + SDL_GetError()); + } + + while (*w_upscale * SCREENWIDTH > rinfo.max_texture_width) + { + --*w_upscale; + } + while (*h_upscale * SCREENHEIGHT > rinfo.max_texture_height) + { + --*h_upscale; + } + + if ((*w_upscale < 1 && rinfo.max_texture_width > 0) || + (*h_upscale < 1 && rinfo.max_texture_height > 0)) + { + I_Error("CreateUpscaledTexture: Can't create a texture big enough for " + "the whole screen! Maximum texture size %dx%d", + rinfo.max_texture_width, rinfo.max_texture_height); + } + + // We limit the amount of texture memory used for the intermediate buffer. + // By default we limit to 1600x1200, which gives pretty good results, but + // we allow the user to override this and use more if they want to use + // even more (or less, if their graphics card can't handle it). + + if (max_scaling_buffer_pixels < SCREENWIDTH * SCREENHEIGHT) + { + I_Error("CreateUpscaledTexture: max_scaling_buffer_pixels too small " + "to create a texture buffer: %d < %d", + max_scaling_buffer_pixels, SCREENWIDTH * SCREENHEIGHT); + } + + while (*w_upscale * *h_upscale * SCREENWIDTH * SCREENHEIGHT + > max_scaling_buffer_pixels) + { + if (*w_upscale > *h_upscale) + { + --*w_upscale; + } + else + { + --*h_upscale; + } + } + + if (*w_upscale != orig_w || *h_upscale != orig_h) + { + printf("CreateUpscaledTexture: Limited texture size to %dx%d " + "(max %d pixels, max texture size %dx%d)\n", + *w_upscale * SCREENWIDTH, *h_upscale * SCREENHEIGHT, + max_scaling_buffer_pixels, + rinfo.max_texture_width, rinfo.max_texture_height); + } +} + +static void CreateUpscaledTexture(boolean force) +{ + const int actualheight = EffectiveScreenHeight(); + int w, h; + int h_upscale, w_upscale; + static int h_upscale_old, w_upscale_old; + + // Get the size of the renderer output. The units this gives us will be + // real world pixels, which are not necessarily equivalent to the screen's + // window size (because of highdpi). + if (SDL_GetRendererOutputSize(renderer, &w, &h) != 0) + { + I_Error("Failed to get renderer output size: %s", SDL_GetError()); + } + + // When the screen or window dimensions do not match the aspect ratio + // of the texture, the rendered area is scaled down to fit. Calculate + // the actual dimensions of the rendered area. + + if (w * actualheight < h * SCREENWIDTH) + { + // Tall window. + + h = w * actualheight / SCREENWIDTH; + } + else + { + // Wide window. + + w = h * SCREENWIDTH / actualheight; + } + + // Pick texture size the next integer multiple of the screen dimensions. + // If one screen dimension matches an integer multiple of the original + // resolution, there is no need to overscale in this direction. + + w_upscale = (w + SCREENWIDTH - 1) / SCREENWIDTH; + h_upscale = (h + SCREENHEIGHT - 1) / SCREENHEIGHT; + + // Minimum texture dimensions of 320x200. + + if (w_upscale < 1) + { + w_upscale = 1; + } + if (h_upscale < 1) + { + h_upscale = 1; + } + + LimitTextureSize(&w_upscale, &h_upscale); + + // Create a new texture only if the upscale factors have actually changed. + + if (h_upscale == h_upscale_old && w_upscale == w_upscale_old && !force) + { + return; + } + + h_upscale_old = h_upscale; + w_upscale_old = w_upscale; + + if (texture_upscaled) + { + SDL_DestroyTexture(texture_upscaled); + } + + // Set the scaling quality for rendering the upscaled texture to "linear", + // which looks much softer and smoother than "nearest" but does a better + // job at downscaling from the upscaled texture to screen. + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); + + texture_upscaled = SDL_CreateTexture(renderer, + pixel_format, + SDL_TEXTUREACCESS_TARGET, + w_upscale*SCREENWIDTH, + h_upscale*SCREENHEIGHT); +} + +// +// I_FinishUpdate +// +void I_FinishUpdate (void) +{ + static int lasttic; + int tics; + int i; + + if (!initialized) + return; + + if (noblit) + return; + + if (need_resize) + { + if (SDL_GetTicks() > last_resize_time + RESIZE_DELAY) + { + int flags; + // When the window is resized (we're not in fullscreen mode), + // save the new window size. + flags = SDL_GetWindowFlags(screen); + if ((flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) + { + SDL_GetWindowSize(screen, &window_width, &window_height); + + // Adjust the window by resizing again so that the window + // is the right aspect ratio. + AdjustWindowSize(); + SDL_SetWindowSize(screen, window_width, window_height); + } + CreateUpscaledTexture(false); + need_resize = false; + palette_to_set = true; + } + else + { + return; + } + } + + UpdateGrab(); + +#if 0 // SDL2-TODO + // Don't update the screen if the window isn't visible. + // Not doing this breaks under Windows when we alt-tab away + // while fullscreen. + + if (!(SDL_GetAppState() & SDL_APPACTIVE)) + return; +#endif + + // draws little dots on the bottom of the screen + + if (display_fps_dots) + { + i = I_GetTime(); + tics = i - lasttic; + lasttic = i; + if (tics > 20) tics = 20; + + for (i=0 ; iformat->palette, palette, 0, 256); + palette_to_set = false; + + if (vga_porch_flash) + { + // "flash" the pillars/letterboxes with palette changes, emulating + // VGA "porch" behaviour (GitHub issue #832) + SDL_SetRenderDrawColor(renderer, palette[0].r, palette[0].g, + palette[0].b, SDL_ALPHA_OPAQUE); + } + } + + // Blit from the paletted 8-bit screen buffer to the intermediate + // 32-bit RGBA buffer that we can load into the texture. + + SDL_LowerBlit(screenbuffer, &blit_rect, rgbabuffer, &blit_rect); + + // Update the intermediate texture with the contents of the RGBA buffer. + + SDL_UpdateTexture(texture, NULL, rgbabuffer->pixels, rgbabuffer->pitch); + + // Make sure the pillarboxes are kept clear each frame. + + SDL_RenderClear(renderer); + + // Render this intermediate texture into the upscaled texture + // using "nearest" integer scaling. + + SDL_SetRenderTarget(renderer, texture_upscaled); + SDL_RenderCopy(renderer, texture, NULL, NULL); + + // Finally, render this upscaled texture to screen using linear scaling. + + SDL_SetRenderTarget(renderer, NULL); + SDL_RenderCopy(renderer, texture_upscaled, NULL, NULL); + + // Draw! + + SDL_RenderPresent(renderer); +} + + +// +// I_ReadScreen +// +void I_ReadScreen (pixel_t* scr) +{ + memcpy(scr, I_VideoBuffer, SCREENWIDTH*SCREENHEIGHT*sizeof(*scr)); +} + + +// +// I_SetPalette +// +void I_SetPalette (byte *doompalette) +{ + int i; + + for (i=0; i<256; ++i) + { + // Zero out the bottom two bits of each channel - the PC VGA + // controller only supports 6 bits of accuracy. + + palette[i].r = gammatable[usegamma][*doompalette++] & ~3; + palette[i].g = gammatable[usegamma][*doompalette++] & ~3; + palette[i].b = gammatable[usegamma][*doompalette++] & ~3; + } + + palette_to_set = true; +} + +// Given an RGB value, find the closest matching palette index. + +int I_GetPaletteIndex(int r, int g, int b) +{ + int best, best_diff, diff; + int i; + + best = 0; best_diff = INT_MAX; + + for (i = 0; i < 256; ++i) + { + diff = (r - palette[i].r) * (r - palette[i].r) + + (g - palette[i].g) * (g - palette[i].g) + + (b - palette[i].b) * (b - palette[i].b); + + if (diff < best_diff) + { + best = i; + best_diff = diff; + } + + if (diff == 0) + { + break; + } + } + + return best; +} + +// +// Set the window title +// + +void I_SetWindowTitle(char *title) +{ + window_title = title; +} + +// +// Call the SDL function to set the window title, based on +// the title set with I_SetWindowTitle. +// + +void I_InitWindowTitle(void) +{ + char *buf; + + buf = M_StringJoin(window_title, " - ", "Chocolate Doom 3", NULL); + SDL_SetWindowTitle(screen, buf); + free(buf); +} + +// Set video size to a particular scale factor (1x, 2x, 3x, etc.) + +static void SetScaleFactor(int factor) +{ + // Pick 320x200 or 320x240, depending on aspect ratio correct + + window_width = factor * SCREENWIDTH; + window_height = factor * EffectiveScreenHeight(); + fullscreen = false; +} + +void I_GraphicsCheckCommandLine(void) +{ + int i; + + //! + // @category video + // @vanilla + // + // Disable blitting the screen. + // + + noblit = M_CheckParm ("-noblit"); + + //! + // @category video + // + // Don't grab the mouse when running in windowed mode. + // + + nograbmouse_override = M_ParmExists("-nograbmouse"); + + // default to fullscreen mode, allow override with command line + // nofullscreen because we love prboom + + //! + // @category video + // + // Run in a window. + // + + if (M_CheckParm("-window") || M_CheckParm("-nofullscreen")) + { + fullscreen = false; + } + + //! + // @category video + // + // Run in fullscreen mode. + // + + if (M_CheckParm("-fullscreen")) + { + fullscreen = true; + } + + //! + // @category video + // + // Disable the mouse. + // + + nomouse = M_CheckParm("-nomouse") > 0; + + //! + // @category video + // @arg + // + // Specify the screen width, in pixels. Implies -window. + // + + i = M_CheckParmWithArgs("-width", 1); + + if (i > 0) + { + window_width = atoi(myargv[i + 1]); + window_height = window_width * 2; + AdjustWindowSize(); + fullscreen = false; + } + + //! + // @category video + // @arg + // + // Specify the screen height, in pixels. Implies -window. + // + + i = M_CheckParmWithArgs("-height", 1); + + if (i > 0) + { + window_height = atoi(myargv[i + 1]); + window_width = window_height * 2; + AdjustWindowSize(); + fullscreen = false; + } + + //! + // @category video + // @arg + // + // Specify the dimensions of the window. Implies -window. + // + + i = M_CheckParmWithArgs("-geometry", 1); + + if (i > 0) + { + int w, h, s; + + s = sscanf(myargv[i + 1], "%ix%i", &w, &h); + if (s == 2) + { + window_width = w; + window_height = h; + fullscreen = false; + } + } + + //! + // @category video + // + // Don't scale up the screen. Implies -window. + // + + if (M_CheckParm("-1")) + { + SetScaleFactor(1); + } + + //! + // @category video + // + // Double up the screen to 2x its normal size. Implies -window. + // + + if (M_CheckParm("-2")) + { + SetScaleFactor(2); + } + + //! + // @category video + // + // Double up the screen to 3x its normal size. Implies -window. + // + + if (M_CheckParm("-3")) + { + SetScaleFactor(3); + } +} + +// Check if we have been invoked as a screensaver by xscreensaver. + +void I_CheckIsScreensaver(void) +{ + char *env; + + env = getenv("XSCREENSAVER_WINDOW"); + + if (env != NULL) + { + screensaver_mode = true; + } +} + +static void SetSDLVideoDriver(void) +{ + // Allow a default value for the SDL video driver to be specified + // in the configuration file. + + if (strcmp(video_driver, "") != 0) + { + char *env_string; + + env_string = M_StringJoin("SDL_VIDEODRIVER=", video_driver, NULL); + putenv(env_string); + free(env_string); + } +} + +// Check the display bounds of the display referred to by 'video_display' and +// set x and y to a location that places the window in the center of that +// display. +static void CenterWindow(int *x, int *y, int w, int h) +{ + SDL_Rect bounds; + + if (SDL_GetDisplayBounds(video_display, &bounds) < 0) + { + fprintf(stderr, "CenterWindow: Failed to read display bounds " + "for display #%d!\n", video_display); + return; + } + + *x = bounds.x + SDL_max((bounds.w - w) / 2, 0); + *y = bounds.y + SDL_max((bounds.h - h) / 2, 0); +} + +void I_GetWindowPosition(int *x, int *y, int w, int h) +{ + // Check that video_display corresponds to a display that really exists, + // and if it doesn't, reset it. + if (video_display < 0 || video_display >= SDL_GetNumVideoDisplays()) + { + fprintf(stderr, + "I_GetWindowPosition: We were configured to run on display #%d, " + "but it no longer exists (max %d). Moving to display 0.\n", + video_display, SDL_GetNumVideoDisplays() - 1); + video_display = 0; + } + + // in fullscreen mode, the window "position" still matters, because + // we use it to control which display we run fullscreen on. + + if (fullscreen) + { + CenterWindow(x, y, w, h); + return; + } + + // in windowed mode, the desired window position can be specified + // in the configuration file. + + if (window_position == NULL || !strcmp(window_position, "")) + { + *x = *y = SDL_WINDOWPOS_UNDEFINED; + } + else if (!strcmp(window_position, "center")) + { + // Note: SDL has a SDL_WINDOWPOS_CENTER, but this is useless for our + // purposes, since we also want to control which display we appear on. + // So we have to do this ourselves. + CenterWindow(x, y, w, h); + } + else if (sscanf(window_position, "%i,%i", x, y) != 2) + { + // invalid format: revert to default + fprintf(stderr, "I_GetWindowPosition: invalid window_position setting\n"); + *x = *y = SDL_WINDOWPOS_UNDEFINED; + } +} + +static void SetVideoMode(void) +{ + int w, h; + int x, y; + unsigned int rmask, gmask, bmask, amask; + int unused_bpp; + int window_flags = 0, renderer_flags = 0; + SDL_DisplayMode mode; + + w = window_width; + h = window_height; + + // In windowed mode, the window can be resized while the game is + // running. + window_flags = SDL_WINDOW_RESIZABLE; + + // Set the highdpi flag - this makes a big difference on Macs with + // retina displays, especially when using small window sizes. + window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; + + if (fullscreen) + { + if (fullscreen_width == 0 && fullscreen_height == 0) + { + // This window_flags means "Never change the screen resolution! + // Instead, draw to the entire screen by scaling the texture + // appropriately". + window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + else + { + w = fullscreen_width; + h = fullscreen_height; + window_flags |= SDL_WINDOW_FULLSCREEN; + } + } + + I_GetWindowPosition(&x, &y, w, h); + + // Create window and renderer contexts. We set the window title + // later anyway and leave the window position "undefined". If + // "window_flags" contains the fullscreen flag (see above), then + // w and h are ignored. + + if (screen == NULL) + { + screen = SDL_CreateWindow(NULL, x, y, w, h, window_flags); + + if (screen == NULL) + { + I_Error("Error creating window for video startup: %s", + SDL_GetError()); + } + + pixel_format = SDL_GetWindowPixelFormat(screen); + + SDL_SetWindowMinimumSize(screen, SCREENWIDTH, EffectiveScreenHeight()); + + I_InitWindowTitle(); + } + + // The SDL_RENDERER_TARGETTEXTURE flag is required to render the + // intermediate texture into the upscaled texture. + renderer_flags = SDL_RENDERER_TARGETTEXTURE; + + if (SDL_GetCurrentDisplayMode(video_display, &mode) != 0) + { + I_Error("Could not get display mode for video display #%d: %s", + video_display, SDL_GetError()); + } + + // Turn on vsync if we aren't in a -timedemo + if (!singletics && mode.refresh_rate > 0) + { + renderer_flags |= SDL_RENDERER_PRESENTVSYNC; + } + + if (force_software_renderer) + { + renderer_flags |= SDL_RENDERER_SOFTWARE; + } + + if (renderer != NULL) + { + SDL_DestroyRenderer(renderer); + } + + renderer = SDL_CreateRenderer(screen, -1, renderer_flags); + + if (renderer == NULL) + { + I_Error("Error creating renderer for screen window: %s", + SDL_GetError()); + } + + // Important: Set the "logical size" of the rendering context. At the same + // time this also defines the aspect ratio that is preserved while scaling + // and stretching the texture into the window. + + SDL_RenderSetLogicalSize(renderer, + SCREENWIDTH, + EffectiveScreenHeight()); + + // Force integer scales for resolution-independent rendering. + +#if SDL_VERSION_ATLEAST(2, 0, 5) + SDL_RenderSetIntegerScale(renderer, integer_scaling); +#endif + + // Blank out the full screen area in case there is any junk in + // the borders that won't otherwise be overwritten. + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); + + // Create the 8-bit paletted and the 32-bit RGBA screenbuffer surfaces. + + if (screenbuffer == NULL) + { + screenbuffer = SDL_CreateRGBSurface(0, + SCREENWIDTH, SCREENHEIGHT, 8, + 0, 0, 0, 0); + SDL_FillRect(screenbuffer, NULL, 0); + } + + // Format of rgbabuffer must match the screen pixel format because we + // import the surface data into the texture. + if (rgbabuffer == NULL) + { + SDL_PixelFormatEnumToMasks(pixel_format, &unused_bpp, + &rmask, &gmask, &bmask, &amask); + rgbabuffer = SDL_CreateRGBSurface(0, + SCREENWIDTH, SCREENHEIGHT, 32, + rmask, gmask, bmask, amask); + SDL_FillRect(rgbabuffer, NULL, 0); + } + + if (texture != NULL) + { + SDL_DestroyTexture(texture); + } + + // Set the scaling quality for rendering the intermediate texture into + // the upscaled texture to "nearest", which is gritty and pixelated and + // resembles software scaling pretty well. + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); + + // Create the intermediate texture that the RGBA surface gets loaded into. + // The SDL_TEXTUREACCESS_STREAMING flag means that this texture's content + // is going to change frequently. + + texture = SDL_CreateTexture(renderer, + pixel_format, + SDL_TEXTUREACCESS_STREAMING, + SCREENWIDTH, SCREENHEIGHT); + + // Initially create the upscaled texture for rendering to screen + + CreateUpscaledTexture(true); +} + +static const char *hw_emu_warning = +"===========================================================================\n" +"WARNING: it looks like you are using a software GL implementation.\n" +"To improve performance, try setting force_software_renderer in your\n" +"configuration file.\n" +"===========================================================================\n"; + +static void CheckGLVersion(void) +{ + const char * version; + typedef const GLubyte* (APIENTRY * glStringFn_t)(GLenum); + glStringFn_t glfp = (glStringFn_t)SDL_GL_GetProcAddress("glGetString"); + + if (glfp) + { + version = (const char *)glfp(GL_VERSION); + + if (version && strstr(version, "Mesa")) + { + printf("%s", hw_emu_warning); + } + } +} + +void I_InitGraphics(void) +{ + SDL_Event dummy; + byte *doompal; + char *env; + + // Pass through the XSCREENSAVER_WINDOW environment variable to + // SDL_WINDOWID, to embed the SDL window into the Xscreensaver + // window. + + env = getenv("XSCREENSAVER_WINDOW"); + + if (env != NULL) + { + char winenv[30]; + int winid; + + sscanf(env, "0x%x", &winid); + M_snprintf(winenv, sizeof(winenv), "SDL_WINDOWID=%i", winid); + + putenv(winenv); + } + + SetSDLVideoDriver(); + + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { + I_Error("Failed to initialize video: %s", SDL_GetError()); + } + + // When in screensaver mode, run full screen and auto detect + // screen dimensions (don't change video mode) + if (screensaver_mode) + { + fullscreen = true; + } + + // Create the game window; this may switch graphic modes depending + // on configuration. + AdjustWindowSize(); + SetVideoMode(); + + // We might have poor performance if we are using an emulated + // HW accelerator. Check for Mesa and warn if we're using it. + CheckGLVersion(); + + // Start with a clear black screen + // (screen will be flipped after we set the palette) + + SDL_FillRect(screenbuffer, NULL, 0); + + // Set the palette + + doompal = W_CacheLumpName(("PLAYPAL"), PU_CACHE); + I_SetPalette(doompal); + SDL_SetPaletteColors(screenbuffer->format->palette, palette, 0, 256); + + // SDL2-TODO UpdateFocus(); + UpdateGrab(); + + // On some systems, it takes a second or so for the screen to settle + // after changing modes. We include the option to add a delay when + // setting the screen mode, so that the game doesn't start immediately + // with the player unable to see anything. + + if (fullscreen && !screensaver_mode) + { + SDL_Delay(startup_delay); + } + + // The actual 320x200 canvas that we draw to. This is the pixel buffer of + // the 8-bit paletted screen buffer that gets blit on an intermediate + // 32-bit RGBA screen buffer that gets loaded into a texture that gets + // finally rendered into our window or full screen in I_FinishUpdate(). + + I_VideoBuffer = screenbuffer->pixels; + V_RestoreBuffer(); + + // Clear the screen to black. + + memset(I_VideoBuffer, 0, SCREENWIDTH * SCREENHEIGHT); + + // clear out any events waiting at the start and center the mouse + + while (SDL_PollEvent(&dummy)); + + initialized = true; + + // Call I_ShutdownGraphics on quit + + I_AtExit(I_ShutdownGraphics, true); +} + +// Bind all variables controlling video options into the configuration +// file system. +void I_BindVideoVariables(void) +{ + M_BindIntVariable("use_mouse", &usemouse); + M_BindIntVariable("fullscreen", &fullscreen); + M_BindIntVariable("video_display", &video_display); + M_BindIntVariable("aspect_ratio_correct", &aspect_ratio_correct); + M_BindIntVariable("integer_scaling", &integer_scaling); + M_BindIntVariable("vga_porch_flash", &vga_porch_flash); + M_BindIntVariable("startup_delay", &startup_delay); + M_BindIntVariable("fullscreen_width", &fullscreen_width); + M_BindIntVariable("fullscreen_height", &fullscreen_height); + M_BindIntVariable("force_software_renderer", &force_software_renderer); + M_BindIntVariable("max_scaling_buffer_pixels", &max_scaling_buffer_pixels); + M_BindIntVariable("window_width", &window_width); + M_BindIntVariable("window_height", &window_height); + M_BindIntVariable("grabmouse", &grabmouse); + M_BindStringVariable("video_driver", &video_driver); + M_BindStringVariable("window_position", &window_position); + M_BindIntVariable("usegamma", &usegamma); + M_BindIntVariable("png_screenshots", &png_screenshots); +} + +*/ \ No newline at end of file diff --git a/client/src/ipu/i_video.h b/client/src/ipu/i_video.h new file mode 100644 index 0000000..6e5c2e0 --- /dev/null +++ b/client/src/ipu/i_video.h @@ -0,0 +1,103 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + + +#ifndef __I_VIDEO__ +#define __I_VIDEO__ + +#include "doomtype.h" + +// Screen width and height. + +#define SCREENWIDTH 320 +#define SCREENHEIGHT 200 + +// Screen height used when aspect_ratio_correct=true. + +#define SCREENHEIGHT_4_3 240 + +typedef boolean (*grabmouse_callback_t)(void); + +// Called by D_DoomMain, +// determines the hardware configuration +// and sets up the video mode +void I_InitGraphics (void); + +void I_GraphicsCheckCommandLine(void); + +void I_ShutdownGraphics(void); + +// Takes full 8 bit values. +void I_SetPalette (byte* palette); +int I_GetPaletteIndex(int r, int g, int b); + +void I_UpdateNoBlit (void); +void I_FinishUpdate (void); + +void I_ReadScreen (pixel_t* scr); + +void I_BeginRead (void); + +void I_SetWindowTitle(char *title); + +void I_CheckIsScreensaver(void); +void I_SetGrabMouseCallback(grabmouse_callback_t func); + +void I_DisplayFPSDots(boolean dots_on); +void I_BindVideoVariables(void); + +void I_InitWindowTitle(void); +void I_InitWindowIcon(void); + +// Called before processing any tics in a frame (just after displaying a frame). +// Time consuming syncronous operations are performed here (joystick reading). + +void I_StartFrame (void); + +// Called before processing each tic in a frame. +// Quick syncronous operations are performed here. + +void I_StartTic (void); + +// Enable the loading disk image displayed when reading from disk. + +void I_EnableLoadingDisk(int xoffs, int yoffs); + +extern char *video_driver; +extern boolean screenvisible; + +extern int vanilla_keyboard_mapping; +extern boolean screensaver_mode; +extern int usegamma; +extern pixel_t *I_VideoBuffer; + +extern int screen_width; +extern int screen_height; +extern int fullscreen; +extern int aspect_ratio_correct; +extern int integer_scaling; +extern int vga_porch_flash; +extern int force_software_renderer; + +extern char *window_position; +void I_GetWindowPosition(int *x, int *y, int w, int h); + +// Joystic/gamepad hysteresis +extern unsigned int joywait; + +#endif diff --git a/client/src/ipu/i_videohr.h b/client/src/ipu/i_videohr.h new file mode 100644 index 0000000..cbda2ba --- /dev/null +++ b/client/src/ipu/i_videohr.h @@ -0,0 +1,35 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// SDL emulation of VGA 640x480x4 planar video mode, +// for Hexen startup loading screen. +// + +#ifndef I_VIDEOHR_H +#define I_VIDEOHR_H + +boolean I_SetVideoModeHR(void); +void I_UnsetVideoModeHR(void); +void I_SetWindowTitleHR(char *title); +void I_ClearScreenHR(void); +void I_SlamBlockHR(int x, int y, int w, int h, const byte *src); +void I_SlamHR(const byte *buffer); +void I_InitPaletteHR(void); +void I_SetPaletteHR(const byte *palette); +void I_FadeToPaletteHR(const byte *palette); +void I_BlackPaletteHR(void); +boolean I_CheckAbortHR(void); + +#endif /* #ifndef I_VIDEOHR_H */ + diff --git a/client/src/ipu/info.h b/client/src/ipu/info.h new file mode 100644 index 0000000..a971424 --- /dev/null +++ b/client/src/ipu/info.h @@ -0,0 +1,1326 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Thing frame/state LUT, +// generated by multigen utilitiy. +// This one is the original DOOM version, preserved. +// + +#ifndef __INFO__ +#define __INFO__ + +// Needed for action function pointer handling. +#include "d_think.h" + +typedef enum { + SPR_TROO, + SPR_SHTG, + SPR_PUNG, + SPR_PISG, + SPR_PISF, + SPR_SHTF, + SPR_SHT2, + SPR_CHGG, + SPR_CHGF, + SPR_MISG, + SPR_MISF, + SPR_SAWG, + SPR_PLSG, + SPR_PLSF, + SPR_BFGG, + SPR_BFGF, + SPR_BLUD, + SPR_PUFF, + SPR_BAL1, + SPR_BAL2, + SPR_PLSS, + SPR_PLSE, + SPR_MISL, + SPR_BFS1, + SPR_BFE1, + SPR_BFE2, + SPR_TFOG, + SPR_IFOG, + SPR_PLAY, + SPR_POSS, + SPR_SPOS, + SPR_VILE, + SPR_FIRE, + SPR_FATB, + SPR_FBXP, + SPR_SKEL, + SPR_MANF, + SPR_FATT, + SPR_CPOS, + SPR_SARG, + SPR_HEAD, + SPR_BAL7, + SPR_BOSS, + SPR_BOS2, + SPR_SKUL, + SPR_SPID, + SPR_BSPI, + SPR_APLS, + SPR_APBX, + SPR_CYBR, + SPR_PAIN, + SPR_SSWV, + SPR_KEEN, + SPR_BBRN, + SPR_BOSF, + SPR_ARM1, + SPR_ARM2, + SPR_BAR1, + SPR_BEXP, + SPR_FCAN, + SPR_BON1, + SPR_BON2, + SPR_BKEY, + SPR_RKEY, + SPR_YKEY, + SPR_BSKU, + SPR_RSKU, + SPR_YSKU, + SPR_STIM, + SPR_MEDI, + SPR_SOUL, + SPR_PINV, + SPR_PSTR, + SPR_PINS, + SPR_MEGA, + SPR_SUIT, + SPR_PMAP, + SPR_PVIS, + SPR_CLIP, + SPR_AMMO, + SPR_ROCK, + SPR_BROK, + SPR_CELL, + SPR_CELP, + SPR_SHEL, + SPR_SBOX, + SPR_BPAK, + SPR_BFUG, + SPR_MGUN, + SPR_CSAW, + SPR_LAUN, + SPR_PLAS, + SPR_SHOT, + SPR_SGN2, + SPR_COLU, + SPR_SMT2, + SPR_GOR1, + SPR_POL2, + SPR_POL5, + SPR_POL4, + SPR_POL3, + SPR_POL1, + SPR_POL6, + SPR_GOR2, + SPR_GOR3, + SPR_GOR4, + SPR_GOR5, + SPR_SMIT, + SPR_COL1, + SPR_COL2, + SPR_COL3, + SPR_COL4, + SPR_CAND, + SPR_CBRA, + SPR_COL6, + SPR_TRE1, + SPR_TRE2, + SPR_ELEC, + SPR_CEYE, + SPR_FSKU, + SPR_COL5, + SPR_TBLU, + SPR_TGRN, + SPR_TRED, + SPR_SMBT, + SPR_SMGT, + SPR_SMRT, + SPR_HDB1, + SPR_HDB2, + SPR_HDB3, + SPR_HDB4, + SPR_HDB5, + SPR_HDB6, + SPR_POB1, + SPR_POB2, + SPR_BRS1, + SPR_TLMP, + SPR_TLP2, + NUMSPRITES + +} spritenum_t; + +typedef enum { + S_NULL, + S_LIGHTDONE, + S_PUNCH, + S_PUNCHDOWN, + S_PUNCHUP, + S_PUNCH1, + S_PUNCH2, + S_PUNCH3, + S_PUNCH4, + S_PUNCH5, + S_PISTOL, + S_PISTOLDOWN, + S_PISTOLUP, + S_PISTOL1, + S_PISTOL2, + S_PISTOL3, + S_PISTOL4, + S_PISTOLFLASH, + S_SGUN, + S_SGUNDOWN, + S_SGUNUP, + S_SGUN1, + S_SGUN2, + S_SGUN3, + S_SGUN4, + S_SGUN5, + S_SGUN6, + S_SGUN7, + S_SGUN8, + S_SGUN9, + S_SGUNFLASH1, + S_SGUNFLASH2, + S_DSGUN, + S_DSGUNDOWN, + S_DSGUNUP, + S_DSGUN1, + S_DSGUN2, + S_DSGUN3, + S_DSGUN4, + S_DSGUN5, + S_DSGUN6, + S_DSGUN7, + S_DSGUN8, + S_DSGUN9, + S_DSGUN10, + S_DSNR1, + S_DSNR2, + S_DSGUNFLASH1, + S_DSGUNFLASH2, + S_CHAIN, + S_CHAINDOWN, + S_CHAINUP, + S_CHAIN1, + S_CHAIN2, + S_CHAIN3, + S_CHAINFLASH1, + S_CHAINFLASH2, + S_MISSILE, + S_MISSILEDOWN, + S_MISSILEUP, + S_MISSILE1, + S_MISSILE2, + S_MISSILE3, + S_MISSILEFLASH1, + S_MISSILEFLASH2, + S_MISSILEFLASH3, + S_MISSILEFLASH4, + S_SAW, + S_SAWB, + S_SAWDOWN, + S_SAWUP, + S_SAW1, + S_SAW2, + S_SAW3, + S_PLASMA, + S_PLASMADOWN, + S_PLASMAUP, + S_PLASMA1, + S_PLASMA2, + S_PLASMAFLASH1, + S_PLASMAFLASH2, + S_BFG, + S_BFGDOWN, + S_BFGUP, + S_BFG1, + S_BFG2, + S_BFG3, + S_BFG4, + S_BFGFLASH1, + S_BFGFLASH2, + S_BLOOD1, + S_BLOOD2, + S_BLOOD3, + S_PUFF1, + S_PUFF2, + S_PUFF3, + S_PUFF4, + S_TBALL1, + S_TBALL2, + S_TBALLX1, + S_TBALLX2, + S_TBALLX3, + S_RBALL1, + S_RBALL2, + S_RBALLX1, + S_RBALLX2, + S_RBALLX3, + S_PLASBALL, + S_PLASBALL2, + S_PLASEXP, + S_PLASEXP2, + S_PLASEXP3, + S_PLASEXP4, + S_PLASEXP5, + S_ROCKET, + S_BFGSHOT, + S_BFGSHOT2, + S_BFGLAND, + S_BFGLAND2, + S_BFGLAND3, + S_BFGLAND4, + S_BFGLAND5, + S_BFGLAND6, + S_BFGEXP, + S_BFGEXP2, + S_BFGEXP3, + S_BFGEXP4, + S_EXPLODE1, + S_EXPLODE2, + S_EXPLODE3, + S_TFOG, + S_TFOG01, + S_TFOG02, + S_TFOG2, + S_TFOG3, + S_TFOG4, + S_TFOG5, + S_TFOG6, + S_TFOG7, + S_TFOG8, + S_TFOG9, + S_TFOG10, + S_IFOG, + S_IFOG01, + S_IFOG02, + S_IFOG2, + S_IFOG3, + S_IFOG4, + S_IFOG5, + S_PLAY, + S_PLAY_RUN1, + S_PLAY_RUN2, + S_PLAY_RUN3, + S_PLAY_RUN4, + S_PLAY_ATK1, + S_PLAY_ATK2, + S_PLAY_PAIN, + S_PLAY_PAIN2, + S_PLAY_DIE1, + S_PLAY_DIE2, + S_PLAY_DIE3, + S_PLAY_DIE4, + S_PLAY_DIE5, + S_PLAY_DIE6, + S_PLAY_DIE7, + S_PLAY_XDIE1, + S_PLAY_XDIE2, + S_PLAY_XDIE3, + S_PLAY_XDIE4, + S_PLAY_XDIE5, + S_PLAY_XDIE6, + S_PLAY_XDIE7, + S_PLAY_XDIE8, + S_PLAY_XDIE9, + S_POSS_STND, + S_POSS_STND2, + S_POSS_RUN1, + S_POSS_RUN2, + S_POSS_RUN3, + S_POSS_RUN4, + S_POSS_RUN5, + S_POSS_RUN6, + S_POSS_RUN7, + S_POSS_RUN8, + S_POSS_ATK1, + S_POSS_ATK2, + S_POSS_ATK3, + S_POSS_PAIN, + S_POSS_PAIN2, + S_POSS_DIE1, + S_POSS_DIE2, + S_POSS_DIE3, + S_POSS_DIE4, + S_POSS_DIE5, + S_POSS_XDIE1, + S_POSS_XDIE2, + S_POSS_XDIE3, + S_POSS_XDIE4, + S_POSS_XDIE5, + S_POSS_XDIE6, + S_POSS_XDIE7, + S_POSS_XDIE8, + S_POSS_XDIE9, + S_POSS_RAISE1, + S_POSS_RAISE2, + S_POSS_RAISE3, + S_POSS_RAISE4, + S_SPOS_STND, + S_SPOS_STND2, + S_SPOS_RUN1, + S_SPOS_RUN2, + S_SPOS_RUN3, + S_SPOS_RUN4, + S_SPOS_RUN5, + S_SPOS_RUN6, + S_SPOS_RUN7, + S_SPOS_RUN8, + S_SPOS_ATK1, + S_SPOS_ATK2, + S_SPOS_ATK3, + S_SPOS_PAIN, + S_SPOS_PAIN2, + S_SPOS_DIE1, + S_SPOS_DIE2, + S_SPOS_DIE3, + S_SPOS_DIE4, + S_SPOS_DIE5, + S_SPOS_XDIE1, + S_SPOS_XDIE2, + S_SPOS_XDIE3, + S_SPOS_XDIE4, + S_SPOS_XDIE5, + S_SPOS_XDIE6, + S_SPOS_XDIE7, + S_SPOS_XDIE8, + S_SPOS_XDIE9, + S_SPOS_RAISE1, + S_SPOS_RAISE2, + S_SPOS_RAISE3, + S_SPOS_RAISE4, + S_SPOS_RAISE5, + S_VILE_STND, + S_VILE_STND2, + S_VILE_RUN1, + S_VILE_RUN2, + S_VILE_RUN3, + S_VILE_RUN4, + S_VILE_RUN5, + S_VILE_RUN6, + S_VILE_RUN7, + S_VILE_RUN8, + S_VILE_RUN9, + S_VILE_RUN10, + S_VILE_RUN11, + S_VILE_RUN12, + S_VILE_ATK1, + S_VILE_ATK2, + S_VILE_ATK3, + S_VILE_ATK4, + S_VILE_ATK5, + S_VILE_ATK6, + S_VILE_ATK7, + S_VILE_ATK8, + S_VILE_ATK9, + S_VILE_ATK10, + S_VILE_ATK11, + S_VILE_HEAL1, + S_VILE_HEAL2, + S_VILE_HEAL3, + S_VILE_PAIN, + S_VILE_PAIN2, + S_VILE_DIE1, + S_VILE_DIE2, + S_VILE_DIE3, + S_VILE_DIE4, + S_VILE_DIE5, + S_VILE_DIE6, + S_VILE_DIE7, + S_VILE_DIE8, + S_VILE_DIE9, + S_VILE_DIE10, + S_FIRE1, + S_FIRE2, + S_FIRE3, + S_FIRE4, + S_FIRE5, + S_FIRE6, + S_FIRE7, + S_FIRE8, + S_FIRE9, + S_FIRE10, + S_FIRE11, + S_FIRE12, + S_FIRE13, + S_FIRE14, + S_FIRE15, + S_FIRE16, + S_FIRE17, + S_FIRE18, + S_FIRE19, + S_FIRE20, + S_FIRE21, + S_FIRE22, + S_FIRE23, + S_FIRE24, + S_FIRE25, + S_FIRE26, + S_FIRE27, + S_FIRE28, + S_FIRE29, + S_FIRE30, + S_SMOKE1, + S_SMOKE2, + S_SMOKE3, + S_SMOKE4, + S_SMOKE5, + S_TRACER, + S_TRACER2, + S_TRACEEXP1, + S_TRACEEXP2, + S_TRACEEXP3, + S_SKEL_STND, + S_SKEL_STND2, + S_SKEL_RUN1, + S_SKEL_RUN2, + S_SKEL_RUN3, + S_SKEL_RUN4, + S_SKEL_RUN5, + S_SKEL_RUN6, + S_SKEL_RUN7, + S_SKEL_RUN8, + S_SKEL_RUN9, + S_SKEL_RUN10, + S_SKEL_RUN11, + S_SKEL_RUN12, + S_SKEL_FIST1, + S_SKEL_FIST2, + S_SKEL_FIST3, + S_SKEL_FIST4, + S_SKEL_MISS1, + S_SKEL_MISS2, + S_SKEL_MISS3, + S_SKEL_MISS4, + S_SKEL_PAIN, + S_SKEL_PAIN2, + S_SKEL_DIE1, + S_SKEL_DIE2, + S_SKEL_DIE3, + S_SKEL_DIE4, + S_SKEL_DIE5, + S_SKEL_DIE6, + S_SKEL_RAISE1, + S_SKEL_RAISE2, + S_SKEL_RAISE3, + S_SKEL_RAISE4, + S_SKEL_RAISE5, + S_SKEL_RAISE6, + S_FATSHOT1, + S_FATSHOT2, + S_FATSHOTX1, + S_FATSHOTX2, + S_FATSHOTX3, + S_FATT_STND, + S_FATT_STND2, + S_FATT_RUN1, + S_FATT_RUN2, + S_FATT_RUN3, + S_FATT_RUN4, + S_FATT_RUN5, + S_FATT_RUN6, + S_FATT_RUN7, + S_FATT_RUN8, + S_FATT_RUN9, + S_FATT_RUN10, + S_FATT_RUN11, + S_FATT_RUN12, + S_FATT_ATK1, + S_FATT_ATK2, + S_FATT_ATK3, + S_FATT_ATK4, + S_FATT_ATK5, + S_FATT_ATK6, + S_FATT_ATK7, + S_FATT_ATK8, + S_FATT_ATK9, + S_FATT_ATK10, + S_FATT_PAIN, + S_FATT_PAIN2, + S_FATT_DIE1, + S_FATT_DIE2, + S_FATT_DIE3, + S_FATT_DIE4, + S_FATT_DIE5, + S_FATT_DIE6, + S_FATT_DIE7, + S_FATT_DIE8, + S_FATT_DIE9, + S_FATT_DIE10, + S_FATT_RAISE1, + S_FATT_RAISE2, + S_FATT_RAISE3, + S_FATT_RAISE4, + S_FATT_RAISE5, + S_FATT_RAISE6, + S_FATT_RAISE7, + S_FATT_RAISE8, + S_CPOS_STND, + S_CPOS_STND2, + S_CPOS_RUN1, + S_CPOS_RUN2, + S_CPOS_RUN3, + S_CPOS_RUN4, + S_CPOS_RUN5, + S_CPOS_RUN6, + S_CPOS_RUN7, + S_CPOS_RUN8, + S_CPOS_ATK1, + S_CPOS_ATK2, + S_CPOS_ATK3, + S_CPOS_ATK4, + S_CPOS_PAIN, + S_CPOS_PAIN2, + S_CPOS_DIE1, + S_CPOS_DIE2, + S_CPOS_DIE3, + S_CPOS_DIE4, + S_CPOS_DIE5, + S_CPOS_DIE6, + S_CPOS_DIE7, + S_CPOS_XDIE1, + S_CPOS_XDIE2, + S_CPOS_XDIE3, + S_CPOS_XDIE4, + S_CPOS_XDIE5, + S_CPOS_XDIE6, + S_CPOS_RAISE1, + S_CPOS_RAISE2, + S_CPOS_RAISE3, + S_CPOS_RAISE4, + S_CPOS_RAISE5, + S_CPOS_RAISE6, + S_CPOS_RAISE7, + S_TROO_STND, + S_TROO_STND2, + S_TROO_RUN1, + S_TROO_RUN2, + S_TROO_RUN3, + S_TROO_RUN4, + S_TROO_RUN5, + S_TROO_RUN6, + S_TROO_RUN7, + S_TROO_RUN8, + S_TROO_ATK1, + S_TROO_ATK2, + S_TROO_ATK3, + S_TROO_PAIN, + S_TROO_PAIN2, + S_TROO_DIE1, + S_TROO_DIE2, + S_TROO_DIE3, + S_TROO_DIE4, + S_TROO_DIE5, + S_TROO_XDIE1, + S_TROO_XDIE2, + S_TROO_XDIE3, + S_TROO_XDIE4, + S_TROO_XDIE5, + S_TROO_XDIE6, + S_TROO_XDIE7, + S_TROO_XDIE8, + S_TROO_RAISE1, + S_TROO_RAISE2, + S_TROO_RAISE3, + S_TROO_RAISE4, + S_TROO_RAISE5, + S_SARG_STND, + S_SARG_STND2, + S_SARG_RUN1, + S_SARG_RUN2, + S_SARG_RUN3, + S_SARG_RUN4, + S_SARG_RUN5, + S_SARG_RUN6, + S_SARG_RUN7, + S_SARG_RUN8, + S_SARG_ATK1, + S_SARG_ATK2, + S_SARG_ATK3, + S_SARG_PAIN, + S_SARG_PAIN2, + S_SARG_DIE1, + S_SARG_DIE2, + S_SARG_DIE3, + S_SARG_DIE4, + S_SARG_DIE5, + S_SARG_DIE6, + S_SARG_RAISE1, + S_SARG_RAISE2, + S_SARG_RAISE3, + S_SARG_RAISE4, + S_SARG_RAISE5, + S_SARG_RAISE6, + S_HEAD_STND, + S_HEAD_RUN1, + S_HEAD_ATK1, + S_HEAD_ATK2, + S_HEAD_ATK3, + S_HEAD_PAIN, + S_HEAD_PAIN2, + S_HEAD_PAIN3, + S_HEAD_DIE1, + S_HEAD_DIE2, + S_HEAD_DIE3, + S_HEAD_DIE4, + S_HEAD_DIE5, + S_HEAD_DIE6, + S_HEAD_RAISE1, + S_HEAD_RAISE2, + S_HEAD_RAISE3, + S_HEAD_RAISE4, + S_HEAD_RAISE5, + S_HEAD_RAISE6, + S_BRBALL1, + S_BRBALL2, + S_BRBALLX1, + S_BRBALLX2, + S_BRBALLX3, + S_BOSS_STND, + S_BOSS_STND2, + S_BOSS_RUN1, + S_BOSS_RUN2, + S_BOSS_RUN3, + S_BOSS_RUN4, + S_BOSS_RUN5, + S_BOSS_RUN6, + S_BOSS_RUN7, + S_BOSS_RUN8, + S_BOSS_ATK1, + S_BOSS_ATK2, + S_BOSS_ATK3, + S_BOSS_PAIN, + S_BOSS_PAIN2, + S_BOSS_DIE1, + S_BOSS_DIE2, + S_BOSS_DIE3, + S_BOSS_DIE4, + S_BOSS_DIE5, + S_BOSS_DIE6, + S_BOSS_DIE7, + S_BOSS_RAISE1, + S_BOSS_RAISE2, + S_BOSS_RAISE3, + S_BOSS_RAISE4, + S_BOSS_RAISE5, + S_BOSS_RAISE6, + S_BOSS_RAISE7, + S_BOS2_STND, + S_BOS2_STND2, + S_BOS2_RUN1, + S_BOS2_RUN2, + S_BOS2_RUN3, + S_BOS2_RUN4, + S_BOS2_RUN5, + S_BOS2_RUN6, + S_BOS2_RUN7, + S_BOS2_RUN8, + S_BOS2_ATK1, + S_BOS2_ATK2, + S_BOS2_ATK3, + S_BOS2_PAIN, + S_BOS2_PAIN2, + S_BOS2_DIE1, + S_BOS2_DIE2, + S_BOS2_DIE3, + S_BOS2_DIE4, + S_BOS2_DIE5, + S_BOS2_DIE6, + S_BOS2_DIE7, + S_BOS2_RAISE1, + S_BOS2_RAISE2, + S_BOS2_RAISE3, + S_BOS2_RAISE4, + S_BOS2_RAISE5, + S_BOS2_RAISE6, + S_BOS2_RAISE7, + S_SKULL_STND, + S_SKULL_STND2, + S_SKULL_RUN1, + S_SKULL_RUN2, + S_SKULL_ATK1, + S_SKULL_ATK2, + S_SKULL_ATK3, + S_SKULL_ATK4, + S_SKULL_PAIN, + S_SKULL_PAIN2, + S_SKULL_DIE1, + S_SKULL_DIE2, + S_SKULL_DIE3, + S_SKULL_DIE4, + S_SKULL_DIE5, + S_SKULL_DIE6, + S_SPID_STND, + S_SPID_STND2, + S_SPID_RUN1, + S_SPID_RUN2, + S_SPID_RUN3, + S_SPID_RUN4, + S_SPID_RUN5, + S_SPID_RUN6, + S_SPID_RUN7, + S_SPID_RUN8, + S_SPID_RUN9, + S_SPID_RUN10, + S_SPID_RUN11, + S_SPID_RUN12, + S_SPID_ATK1, + S_SPID_ATK2, + S_SPID_ATK3, + S_SPID_ATK4, + S_SPID_PAIN, + S_SPID_PAIN2, + S_SPID_DIE1, + S_SPID_DIE2, + S_SPID_DIE3, + S_SPID_DIE4, + S_SPID_DIE5, + S_SPID_DIE6, + S_SPID_DIE7, + S_SPID_DIE8, + S_SPID_DIE9, + S_SPID_DIE10, + S_SPID_DIE11, + S_BSPI_STND, + S_BSPI_STND2, + S_BSPI_SIGHT, + S_BSPI_RUN1, + S_BSPI_RUN2, + S_BSPI_RUN3, + S_BSPI_RUN4, + S_BSPI_RUN5, + S_BSPI_RUN6, + S_BSPI_RUN7, + S_BSPI_RUN8, + S_BSPI_RUN9, + S_BSPI_RUN10, + S_BSPI_RUN11, + S_BSPI_RUN12, + S_BSPI_ATK1, + S_BSPI_ATK2, + S_BSPI_ATK3, + S_BSPI_ATK4, + S_BSPI_PAIN, + S_BSPI_PAIN2, + S_BSPI_DIE1, + S_BSPI_DIE2, + S_BSPI_DIE3, + S_BSPI_DIE4, + S_BSPI_DIE5, + S_BSPI_DIE6, + S_BSPI_DIE7, + S_BSPI_RAISE1, + S_BSPI_RAISE2, + S_BSPI_RAISE3, + S_BSPI_RAISE4, + S_BSPI_RAISE5, + S_BSPI_RAISE6, + S_BSPI_RAISE7, + S_ARACH_PLAZ, + S_ARACH_PLAZ2, + S_ARACH_PLEX, + S_ARACH_PLEX2, + S_ARACH_PLEX3, + S_ARACH_PLEX4, + S_ARACH_PLEX5, + S_CYBER_STND, + S_CYBER_STND2, + S_CYBER_RUN1, + S_CYBER_RUN2, + S_CYBER_RUN3, + S_CYBER_RUN4, + S_CYBER_RUN5, + S_CYBER_RUN6, + S_CYBER_RUN7, + S_CYBER_RUN8, + S_CYBER_ATK1, + S_CYBER_ATK2, + S_CYBER_ATK3, + S_CYBER_ATK4, + S_CYBER_ATK5, + S_CYBER_ATK6, + S_CYBER_PAIN, + S_CYBER_DIE1, + S_CYBER_DIE2, + S_CYBER_DIE3, + S_CYBER_DIE4, + S_CYBER_DIE5, + S_CYBER_DIE6, + S_CYBER_DIE7, + S_CYBER_DIE8, + S_CYBER_DIE9, + S_CYBER_DIE10, + S_PAIN_STND, + S_PAIN_RUN1, + S_PAIN_RUN2, + S_PAIN_RUN3, + S_PAIN_RUN4, + S_PAIN_RUN5, + S_PAIN_RUN6, + S_PAIN_ATK1, + S_PAIN_ATK2, + S_PAIN_ATK3, + S_PAIN_ATK4, + S_PAIN_PAIN, + S_PAIN_PAIN2, + S_PAIN_DIE1, + S_PAIN_DIE2, + S_PAIN_DIE3, + S_PAIN_DIE4, + S_PAIN_DIE5, + S_PAIN_DIE6, + S_PAIN_RAISE1, + S_PAIN_RAISE2, + S_PAIN_RAISE3, + S_PAIN_RAISE4, + S_PAIN_RAISE5, + S_PAIN_RAISE6, + S_SSWV_STND, + S_SSWV_STND2, + S_SSWV_RUN1, + S_SSWV_RUN2, + S_SSWV_RUN3, + S_SSWV_RUN4, + S_SSWV_RUN5, + S_SSWV_RUN6, + S_SSWV_RUN7, + S_SSWV_RUN8, + S_SSWV_ATK1, + S_SSWV_ATK2, + S_SSWV_ATK3, + S_SSWV_ATK4, + S_SSWV_ATK5, + S_SSWV_ATK6, + S_SSWV_PAIN, + S_SSWV_PAIN2, + S_SSWV_DIE1, + S_SSWV_DIE2, + S_SSWV_DIE3, + S_SSWV_DIE4, + S_SSWV_DIE5, + S_SSWV_XDIE1, + S_SSWV_XDIE2, + S_SSWV_XDIE3, + S_SSWV_XDIE4, + S_SSWV_XDIE5, + S_SSWV_XDIE6, + S_SSWV_XDIE7, + S_SSWV_XDIE8, + S_SSWV_XDIE9, + S_SSWV_RAISE1, + S_SSWV_RAISE2, + S_SSWV_RAISE3, + S_SSWV_RAISE4, + S_SSWV_RAISE5, + S_KEENSTND, + S_COMMKEEN, + S_COMMKEEN2, + S_COMMKEEN3, + S_COMMKEEN4, + S_COMMKEEN5, + S_COMMKEEN6, + S_COMMKEEN7, + S_COMMKEEN8, + S_COMMKEEN9, + S_COMMKEEN10, + S_COMMKEEN11, + S_COMMKEEN12, + S_KEENPAIN, + S_KEENPAIN2, + S_BRAIN, + S_BRAIN_PAIN, + S_BRAIN_DIE1, + S_BRAIN_DIE2, + S_BRAIN_DIE3, + S_BRAIN_DIE4, + S_BRAINEYE, + S_BRAINEYESEE, + S_BRAINEYE1, + S_SPAWN1, + S_SPAWN2, + S_SPAWN3, + S_SPAWN4, + S_SPAWNFIRE1, + S_SPAWNFIRE2, + S_SPAWNFIRE3, + S_SPAWNFIRE4, + S_SPAWNFIRE5, + S_SPAWNFIRE6, + S_SPAWNFIRE7, + S_SPAWNFIRE8, + S_BRAINEXPLODE1, + S_BRAINEXPLODE2, + S_BRAINEXPLODE3, + S_ARM1, + S_ARM1A, + S_ARM2, + S_ARM2A, + S_BAR1, + S_BAR2, + S_BEXP, + S_BEXP2, + S_BEXP3, + S_BEXP4, + S_BEXP5, + S_BBAR1, + S_BBAR2, + S_BBAR3, + S_BON1, + S_BON1A, + S_BON1B, + S_BON1C, + S_BON1D, + S_BON1E, + S_BON2, + S_BON2A, + S_BON2B, + S_BON2C, + S_BON2D, + S_BON2E, + S_BKEY, + S_BKEY2, + S_RKEY, + S_RKEY2, + S_YKEY, + S_YKEY2, + S_BSKULL, + S_BSKULL2, + S_RSKULL, + S_RSKULL2, + S_YSKULL, + S_YSKULL2, + S_STIM, + S_MEDI, + S_SOUL, + S_SOUL2, + S_SOUL3, + S_SOUL4, + S_SOUL5, + S_SOUL6, + S_PINV, + S_PINV2, + S_PINV3, + S_PINV4, + S_PSTR, + S_PINS, + S_PINS2, + S_PINS3, + S_PINS4, + S_MEGA, + S_MEGA2, + S_MEGA3, + S_MEGA4, + S_SUIT, + S_PMAP, + S_PMAP2, + S_PMAP3, + S_PMAP4, + S_PMAP5, + S_PMAP6, + S_PVIS, + S_PVIS2, + S_CLIP, + S_AMMO, + S_ROCK, + S_BROK, + S_CELL, + S_CELP, + S_SHEL, + S_SBOX, + S_BPAK, + S_BFUG, + S_MGUN, + S_CSAW, + S_LAUN, + S_PLAS, + S_SHOT, + S_SHOT2, + S_COLU, + S_STALAG, + S_BLOODYTWITCH, + S_BLOODYTWITCH2, + S_BLOODYTWITCH3, + S_BLOODYTWITCH4, + S_DEADTORSO, + S_DEADBOTTOM, + S_HEADSONSTICK, + S_GIBS, + S_HEADONASTICK, + S_HEADCANDLES, + S_HEADCANDLES2, + S_DEADSTICK, + S_LIVESTICK, + S_LIVESTICK2, + S_MEAT2, + S_MEAT3, + S_MEAT4, + S_MEAT5, + S_STALAGTITE, + S_TALLGRNCOL, + S_SHRTGRNCOL, + S_TALLREDCOL, + S_SHRTREDCOL, + S_CANDLESTIK, + S_CANDELABRA, + S_SKULLCOL, + S_TORCHTREE, + S_BIGTREE, + S_TECHPILLAR, + S_EVILEYE, + S_EVILEYE2, + S_EVILEYE3, + S_EVILEYE4, + S_FLOATSKULL, + S_FLOATSKULL2, + S_FLOATSKULL3, + S_HEARTCOL, + S_HEARTCOL2, + S_BLUETORCH, + S_BLUETORCH2, + S_BLUETORCH3, + S_BLUETORCH4, + S_GREENTORCH, + S_GREENTORCH2, + S_GREENTORCH3, + S_GREENTORCH4, + S_REDTORCH, + S_REDTORCH2, + S_REDTORCH3, + S_REDTORCH4, + S_BTORCHSHRT, + S_BTORCHSHRT2, + S_BTORCHSHRT3, + S_BTORCHSHRT4, + S_GTORCHSHRT, + S_GTORCHSHRT2, + S_GTORCHSHRT3, + S_GTORCHSHRT4, + S_RTORCHSHRT, + S_RTORCHSHRT2, + S_RTORCHSHRT3, + S_RTORCHSHRT4, + S_HANGNOGUTS, + S_HANGBNOBRAIN, + S_HANGTLOOKDN, + S_HANGTSKULL, + S_HANGTLOOKUP, + S_HANGTNOBRAIN, + S_COLONGIBS, + S_SMALLPOOL, + S_BRAINSTEM, + S_TECHLAMP, + S_TECHLAMP2, + S_TECHLAMP3, + S_TECHLAMP4, + S_TECH2LAMP, + S_TECH2LAMP2, + S_TECH2LAMP3, + S_TECH2LAMP4, + NUMSTATES +} statenum_t; + +typedef struct { + spritenum_t sprite; + int frame; + int tics; + // void (*action) (); + actionf_t action; + statenum_t nextstate; + int misc1; + int misc2; +} state_t; + +extern state_t states[NUMSTATES]; +extern char *sprnames[]; + +typedef enum { + MT_PLAYER, + MT_POSSESSED, + MT_SHOTGUY, + MT_VILE, + MT_FIRE, + MT_UNDEAD, + MT_TRACER, + MT_SMOKE, + MT_FATSO, + MT_FATSHOT, + MT_CHAINGUY, + MT_TROOP, + MT_SERGEANT, + MT_SHADOWS, + MT_HEAD, + MT_BRUISER, + MT_BRUISERSHOT, + MT_KNIGHT, + MT_SKULL, + MT_SPIDER, + MT_BABY, + MT_CYBORG, + MT_PAIN, + MT_WOLFSS, + MT_KEEN, + MT_BOSSBRAIN, + MT_BOSSSPIT, + MT_BOSSTARGET, + MT_SPAWNSHOT, + MT_SPAWNFIRE, + MT_BARREL, + MT_TROOPSHOT, + MT_HEADSHOT, + MT_ROCKET, + MT_PLASMA, + MT_BFG, + MT_ARACHPLAZ, + MT_PUFF, + MT_BLOOD, + MT_TFOG, + MT_IFOG, + MT_TELEPORTMAN, + MT_EXTRABFG, + MT_MISC0, + MT_MISC1, + MT_MISC2, + MT_MISC3, + MT_MISC4, + MT_MISC5, + MT_MISC6, + MT_MISC7, + MT_MISC8, + MT_MISC9, + MT_MISC10, + MT_MISC11, + MT_MISC12, + MT_INV, + MT_MISC13, + MT_INS, + MT_MISC14, + MT_MISC15, + MT_MISC16, + MT_MEGA, + MT_CLIP, + MT_MISC17, + MT_MISC18, + MT_MISC19, + MT_MISC20, + MT_MISC21, + MT_MISC22, + MT_MISC23, + MT_MISC24, + MT_MISC25, + MT_CHAINGUN, + MT_MISC26, + MT_MISC27, + MT_MISC28, + MT_SHOTGUN, + MT_SUPERSHOTGUN, + MT_MISC29, + MT_MISC30, + MT_MISC31, + MT_MISC32, + MT_MISC33, + MT_MISC34, + MT_MISC35, + MT_MISC36, + MT_MISC37, + MT_MISC38, + MT_MISC39, + MT_MISC40, + MT_MISC41, + MT_MISC42, + MT_MISC43, + MT_MISC44, + MT_MISC45, + MT_MISC46, + MT_MISC47, + MT_MISC48, + MT_MISC49, + MT_MISC50, + MT_MISC51, + MT_MISC52, + MT_MISC53, + MT_MISC54, + MT_MISC55, + MT_MISC56, + MT_MISC57, + MT_MISC58, + MT_MISC59, + MT_MISC60, + MT_MISC61, + MT_MISC62, + MT_MISC63, + MT_MISC64, + MT_MISC65, + MT_MISC66, + MT_MISC67, + MT_MISC68, + MT_MISC69, + MT_MISC70, + MT_MISC71, + MT_MISC72, + MT_MISC73, + MT_MISC74, + MT_MISC75, + MT_MISC76, + MT_MISC77, + MT_MISC78, + MT_MISC79, + MT_MISC80, + MT_MISC81, + MT_MISC82, + MT_MISC83, + MT_MISC84, + MT_MISC85, + MT_MISC86, + NUMMOBJTYPES + +} mobjtype_t; + +typedef struct { + int doomednum; + int spawnstate; + int spawnhealth; + int seestate; + int seesound; + int reactiontime; + int attacksound; + int painstate; + int painchance; + int painsound; + int meleestate; + int missilestate; + int deathstate; + int xdeathstate; + int deathsound; + int speed; + int radius; + int height; + int mass; + int damage; + int activesound; + int flags; + int raisestate; + +} mobjinfo_t; + +extern mobjinfo_t mobjinfo[NUMMOBJTYPES]; + +#endif diff --git a/client/src/ipu/ipu_interface.h b/client/src/ipu/ipu_interface.h new file mode 100644 index 0000000..6d52545 --- /dev/null +++ b/client/src/ipu/ipu_interface.h @@ -0,0 +1,51 @@ +#ifndef __IPU_INTERFACE__ +#define __IPU_INTERFACE__ +#ifdef __cplusplus +extern "C" { +#endif + +#include "d_event.h" +#include "doomdef.h" +#include "doomtype.h" +#include "m_fixed.h" +#include "tables.h" + + +#define IPUMAXLUMPBYTES 32000 +#define IPUMISCVALUESSIZE 116 +#define IPUPRINTBUFSIZE 2048 +#define IPUMAXEVENTSPERTIC 5 +#define IPUAMMARKBUFSIZE 544 +#define IPUMAPPEDLINEUPDATES 2 + + +typedef struct { + int gameepisode; + int gamemap; + int lumpnum; +} G_LoadLevel_MiscValues_t; + +typedef struct { + fixed_t x, y, z; + angle_t angle; +} IPUPlayerPos_t; + +typedef struct { + gamestate_t gamestate; + IPUPlayerPos_t player_mobj; + int mappedline_updates[IPUMAPPEDLINEUPDATES]; +} G_Ticker_MiscValues_t; + + +typedef struct { + event_t events[IPUMAXEVENTSPERTIC]; + unsigned char num_ev; + // Other things for responder here +} G_Responder_MiscValues_t; + + + +#ifdef __cplusplus +} +#endif +#endif // __IPU_INTERFACE__ // \ No newline at end of file diff --git a/client/src/ipu/ipu_malloc.c b/client/src/ipu/ipu_malloc.c new file mode 100644 index 0000000..5d1ed48 --- /dev/null +++ b/client/src/ipu/ipu_malloc.c @@ -0,0 +1,32 @@ +#include + +// #include "ipu_malloc.h" +#include "ipu_print.h" + + +#define IPUMALLOC_MAXMAPSIZE 150000 + +#define ALIGN32(x) (((x) + 3) & (~3)) + +static unsigned char PU_LEVEL_pool[IPUMALLOC_MAXMAPSIZE]; +static int PU_LEVEL_size = 0; + + +void* IPU_level_malloc(int size) { + void* ret = (void*)(&PU_LEVEL_pool[PU_LEVEL_size]); + PU_LEVEL_size = ALIGN32(PU_LEVEL_size + size); + + if (PU_LEVEL_size > IPUMALLOC_MAXMAPSIZE) { + ipuprint("ERROR: IPUMALLOC_MAXMAPSIZE is "); + ipuprintnum(IPUMALLOC_MAXMAPSIZE); + ipuprint(", but IPU_level_malloc wants "); + ipuprintnum(PU_LEVEL_size); + ipuprint("\n"); + // exit(1701); + } + return ret; +} + +void IPU_level_free() { + PU_LEVEL_size = 0; +} \ No newline at end of file diff --git a/client/src/ipu/ipu_malloc.h b/client/src/ipu/ipu_malloc.h new file mode 100644 index 0000000..211ce8a --- /dev/null +++ b/client/src/ipu/ipu_malloc.h @@ -0,0 +1,14 @@ +#ifndef __IPU_MALLOC_H__ +#define __IPU_MALLOC_H__ +#ifdef __cplusplus +extern "C" { +#endif + +void* IPU_level_malloc(int size); +void IPU_level_free(void); + + +#ifdef __cplusplus +} +#endif +#endif // __IPU_MALLOC_H__ \ No newline at end of file diff --git a/client/src/ipu/ipu_print.c b/client/src/ipu/ipu_print.c new file mode 100644 index 0000000..772c552 --- /dev/null +++ b/client/src/ipu/ipu_print.c @@ -0,0 +1,60 @@ +#include "ipu_interface.h" + +static char printbuf[IPUPRINTBUFSIZE]; +static char* printbuf_head = printbuf; +static const char* printbuf_end = &printbuf[IPUPRINTBUFSIZE - 1]; +static int printed; + +void reset_ipuprint() { + printbuf_head = printbuf; + *printbuf_head = '\0'; + printed = 0; +} + +void ipuprint(const char* str) { + while (*str != '\0' && printbuf_head != printbuf_end) { + *(printbuf_head++) = *(str++); + printed += 1; + } + *printbuf_head = '\0'; +} + +void ipuprintnum(int x) { + if (x < 0) { + *(printbuf_head++) = '-'; + x = -x; + } + if (x == 0) { + *(printbuf_head++) = '0'; + } + const int MAX_DIGITS = 10; + char digits[MAX_DIGITS]; + int i; + for (i = 0; x != 0 && i < MAX_DIGITS; ++i) { + digits[i] = 0x30 + (x % 10); + x /= 10; + } + for (i -= 1; i >= 0; --i) { + *(printbuf_head++) = digits[i]; + } + *printbuf_head = '\0'; +} + +void get_ipuprint_data(char* dst, int dst_size) { + int limit = (dst_size < IPUPRINTBUFSIZE) ? dst_size : IPUPRINTBUFSIZE; + for (int i = 0; i < limit; ++i) { + dst[i] = printbuf[i]; + } + if (limit <= printed) { + dst[IPUPRINTBUFSIZE - 10] = '['; + dst[IPUPRINTBUFSIZE - 9] = 'c'; + dst[IPUPRINTBUFSIZE - 8] = 'o'; + dst[IPUPRINTBUFSIZE - 7] = 'n'; + dst[IPUPRINTBUFSIZE - 6] = 't'; + dst[IPUPRINTBUFSIZE - 5] = '.'; + dst[IPUPRINTBUFSIZE - 4] = '.'; + dst[IPUPRINTBUFSIZE - 3] = '.'; + dst[IPUPRINTBUFSIZE - 2] = ']'; + dst[IPUPRINTBUFSIZE - 1] = '\0'; + } +} diff --git a/client/src/ipu/ipu_print.h b/client/src/ipu/ipu_print.h new file mode 100644 index 0000000..67f38af --- /dev/null +++ b/client/src/ipu/ipu_print.h @@ -0,0 +1,17 @@ +# ifndef __IPU_PRINT_H__ +#define __IPU_PRINT_H__ +#ifdef __cplusplus +extern "C" { +#endif + + +void reset_ipuprint(); +void ipuprint(const char* str); +void ipuprintnum(int x); +void get_ipuprint_data(char* dst, int dst_size); + + +#ifdef __cplusplus +} +#endif +#endif // __IPU_PRINT_H__ \ No newline at end of file diff --git a/client/src/ipu/ipu_transfer.c b/client/src/ipu/ipu_transfer.c new file mode 100644 index 0000000..500a7f2 --- /dev/null +++ b/client/src/ipu/ipu_transfer.c @@ -0,0 +1,65 @@ +#include "doomstat.h" +#include "r_defs.h" +#include "r_state.h" +// #include "w_wad.h" + +#include "ipu_interface.h" +#include "ipu_transfer.h" +#include "ipu_print.h" + +int gamelumpnum; +int requestedlumpnum; + +void IPU_G_LoadLevel_UnpackMiscValues(G_LoadLevel_MiscValues_t* pack) { + gameepisode = pack->gameepisode; + gamemap = pack->gamemap; + gamelumpnum = pack->lumpnum; +} + +void IPU_G_Ticker_UnpackMiscValues(G_Ticker_MiscValues_t* pack) { + gamestate = pack->gamestate; + if (gamestate != GS_LEVEL) + return; + am_playerpos.x = pack->player_mobj.x; + am_playerpos.y = pack->player_mobj.y; + am_playerpos.z = pack->player_mobj.z; + am_playerpos.angle = pack->player_mobj.angle; + for (int i = 0; i < IPUMAPPEDLINEUPDATES; ++i) { + int update = pack->mappedline_updates[i]; + if (update == -1) break; + lines[update].flags |= ML_MAPPED; + } +} + +void IPU_Setup_UnpackMarkNums(const unsigned char* buf) { + short* offsets = (short*) buf; + const int offsetssize = 10 * sizeof(short); + memcpy(markbuf, &offsets[10], IPUAMMARKBUFSIZE - offsetssize); + for(int i = 0; i < 10; ++i) { + marknums[i] = (patch_t*) &markbuf[offsets[i] - offsetssize]; + } +} + +/* +void IPU_UnpackVertexes(const unsigned char* buf) { + IpuPackedLevel_t* pack = (IpuPackedLevel_t*) buf; + uint32_t pos = 0; + + numvertexes = *(uint32_t*)buf; + + mapvertex_t* ml = (mapvertex_t*)(&buf[4]); + vertex_t *li not done here; + for (int i = 0; i < numvertex) + vertexes = (vertex_t*) &pack->data[pos]; + pos += numvertexes * sizeof(vertex_t); + + numlines = pack->numlines; + lines = (line_t*) &pack->data[pos]; + pos += numlines * sizeof(line_t); + + // for (int i=0; i <) + // LATER: repoint other contents of lines + + +} + */ \ No newline at end of file diff --git a/client/src/ipu/ipu_transfer.h b/client/src/ipu/ipu_transfer.h new file mode 100644 index 0000000..c9a0a5a --- /dev/null +++ b/client/src/ipu/ipu_transfer.h @@ -0,0 +1,23 @@ +#ifndef __IPU_TRANSFER_D__ +#define __IPU_TRANSFER_D__ +#ifdef __cplusplus +extern "C" { +#endif + +#include "v_patch.h" +#include "ipu_interface.h" + + +extern int gamelumpnum; +extern int requestedlumpnum; +extern IPUPlayerPos_t am_playerpos; +extern patch_t* marknums[10]; +extern unsigned char markbuf[IPUAMMARKBUFSIZE]; + +void IPU_G_LoadLevel_UnpackMiscValues(G_LoadLevel_MiscValues_t* pack); +void IPU_G_Ticker_UnpackMiscValues(G_Ticker_MiscValues_t* pack); + +#ifdef __cplusplus +} +#endif +#endif // __IPU_TRANSFER_D__ // \ No newline at end of file diff --git a/client/src/ipu/ipu_vertices.cpp b/client/src/ipu/ipu_vertices.cpp new file mode 100644 index 0000000..214e4e5 --- /dev/null +++ b/client/src/ipu/ipu_vertices.cpp @@ -0,0 +1,65 @@ + +#include + +#include "doomtype.h" +#include "ipu_print.h" + + +typedef uint8_t pixel_t; + + +extern "C" { + void AM_LevelInit(void); + void AM_Drawer(pixel_t*); +}; + + +class AM_LevelInit_Vertex : public poplar::Vertex { + public: + bool compute() { + AM_LevelInit(); + return true; + } +}; + + +class AM_Drawer_Vertex : public poplar::Vertex { + public: + poplar::InOut> frame; + + bool compute() { + AM_Drawer(&frame[0]); + return true; + } +}; + + +class IPU_GetPrintBuf_Vertex : public poplar::Vertex { + public: + poplar::Output> printbuf; + + bool compute() { + get_ipuprint_data(&printbuf[0], printbuf.size()); + reset_ipuprint(); + return true; + } +}; + + +// class G_Ticker_Vertex : public poplar::Vertex { Call G_ticker in D_ProcessEvents +// public: + +// bool compute() { +// return true; +// } +// }; + +class D_ProcessEvents_Vertex : public poplar::Vertex { + public: + + bool compute() { + // Unpack and process events + return true; + } +}; + diff --git a/client/src/ipu/m_argv.h b/client/src/ipu/m_argv.h new file mode 100644 index 0000000..b576f6c --- /dev/null +++ b/client/src/ipu/m_argv.h @@ -0,0 +1,49 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Nil. +// + + +#ifndef __M_ARGV__ +#define __M_ARGV__ + +#include "doomtype.h" + +// +// MISC +// +extern int myargc; +extern char** myargv; + +// Returns the position of the given parameter +// in the arg list (0 if not found). +int M_CheckParm (char* check); + +// Same as M_CheckParm, but checks that num_args arguments are available +// following the specified argument. +int M_CheckParmWithArgs(char *check, int num_args); + +void M_FindResponseFile(void); + +// Parameter has been specified? + +boolean M_ParmExists(char *check); + +// Get name of executable used to run this program: + +char *M_GetExecutableName(void); + +#endif diff --git a/client/src/ipu/m_bbox.h b/client/src/ipu/m_bbox.h new file mode 100644 index 0000000..958e21f --- /dev/null +++ b/client/src/ipu/m_bbox.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Nil. +// + + +#ifndef __M_BBOX__ +#define __M_BBOX__ + +#include "m_fixed.h" + + +// Bounding box coordinate storage. +enum +{ + BOXTOP, + BOXBOTTOM, + BOXLEFT, + BOXRIGHT +}; // bbox coordinates + +// Bounding box functions. +void M_ClearBox (fixed_t* box); + +void +M_AddToBox +( fixed_t* box, + fixed_t x, + fixed_t y ); + + +#endif diff --git a/client/src/ipu/m_cheat.h b/client/src/ipu/m_cheat.h new file mode 100644 index 0000000..1a49465 --- /dev/null +++ b/client/src/ipu/m_cheat.h @@ -0,0 +1,63 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Cheat code checking. +// + + +#ifndef __M_CHEAT__ +#define __M_CHEAT__ + +#include +// +// CHEAT SEQUENCE PACKAGE +// + +// declaring a cheat + +#define CHEAT(value, parameters) \ + { value, sizeof(value) - 1, parameters, 0, 0, "" } + +#define MAX_CHEAT_LEN 25 +#define MAX_CHEAT_PARAMS 5 + +typedef struct +{ + // settings for this cheat + + char sequence[MAX_CHEAT_LEN]; + size_t sequence_len; + int parameter_chars; + + // state used during the game + + size_t chars_read; + int param_chars_read; + char parameter_buf[MAX_CHEAT_PARAMS]; +} cheatseq_t; + +int +cht_CheckCheat +( cheatseq_t* cht, + char key ); + + +void +cht_GetParam +( cheatseq_t* cht, + char* buffer ); + + +#endif diff --git a/client/src/ipu/m_config.h b/client/src/ipu/m_config.h new file mode 100644 index 0000000..43882a4 --- /dev/null +++ b/client/src/ipu/m_config.h @@ -0,0 +1,41 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Configuration file interface. +// + + +#ifndef __M_CONFIG__ +#define __M_CONFIG__ + +#include "doomtype.h" + +void M_LoadDefaults(void); +void M_SaveDefaults(void); +void M_SaveDefaultsAlternate(char *main, char *extra); +void M_SetConfigDir(char *dir); +void M_BindIntVariable(char *name, int *variable); +void M_BindFloatVariable(char *name, float *variable); +void M_BindStringVariable(char *name, char **variable); +boolean M_SetVariable(char *name, char *value); +int M_GetIntVariable(char *name); +const char *M_GetStringVariable(char *name); +float M_GetFloatVariable(char *name); +void M_SetConfigFilenames(char *main_config, char *extra_config); +char *M_GetSaveGameDir(char *iwadname); + +extern char *configdir; + +#endif diff --git a/client/src/ipu/m_controls.c b/client/src/ipu/m_controls.c new file mode 100644 index 0000000..c25f69b --- /dev/null +++ b/client/src/ipu/m_controls.c @@ -0,0 +1,401 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// + +#include "doomkeys.h" +/* LATER +#include "m_config.h" +#include "m_misc.h" +*/ + +// +// Keyboard controls +// + +int key_right = KEY_RIGHTARROW; +int key_left = KEY_LEFTARROW; + +int key_up = KEY_UPARROW; +int key_down = KEY_DOWNARROW; +int key_strafeleft = ','; +int key_straferight = '.'; +int key_fire = KEY_RCTRL; +int key_use = ' '; +int key_strafe = KEY_RALT; +int key_speed = KEY_RSHIFT; + +// +// Heretic keyboard controls +// + +int key_flyup = KEY_PGUP; +int key_flydown = KEY_INS; +int key_flycenter = KEY_HOME; + +int key_lookup = KEY_PGDN; +int key_lookdown = KEY_DEL; +int key_lookcenter = KEY_END; + +int key_invleft = '['; +int key_invright = ']'; +int key_useartifact = KEY_ENTER; + +// +// Hexen key controls +// + +int key_jump = '/'; + +int key_arti_all = KEY_BACKSPACE; +int key_arti_health = '\\'; +int key_arti_poisonbag = '0'; +int key_arti_blastradius = '9'; +int key_arti_teleport = '8'; +int key_arti_teleportother = '7'; +int key_arti_egg = '6'; +int key_arti_invulnerability = '5'; + +// +// Strife key controls +// +// haleyjd 09/01/10 +// + +// Note: Strife also uses key_invleft, key_invright, key_jump, key_lookup, and +// key_lookdown, but with different default values. + +int key_usehealth = 'h'; +int key_invquery = 'q'; +int key_mission = 'w'; +int key_invpop = 'z'; +int key_invkey = 'k'; +int key_invhome = KEY_HOME; +int key_invend = KEY_END; +int key_invuse = KEY_ENTER; +int key_invdrop = KEY_BACKSPACE; + + +// +// Mouse controls +// + +int mousebfire = 0; +int mousebstrafe = 1; +int mousebforward = 2; + +int mousebjump = -1; + +int mousebstrafeleft = -1; +int mousebstraferight = -1; +int mousebbackward = -1; +int mousebuse = -1; + +int mousebprevweapon = -1; +int mousebnextweapon = -1; + + +int key_message_refresh = KEY_ENTER; +int key_pause = KEY_PAUSE; +int key_demo_quit = 'q'; +int key_spy = KEY_F12; + +// Multiplayer chat keys: + +int key_multi_msg = 't'; +int key_multi_msgplayer[8]; + +// Weapon selection keys: + +int key_weapon1 = '1'; +int key_weapon2 = '2'; +int key_weapon3 = '3'; +int key_weapon4 = '4'; +int key_weapon5 = '5'; +int key_weapon6 = '6'; +int key_weapon7 = '7'; +int key_weapon8 = '8'; +int key_prevweapon = 0; +int key_nextweapon = 0; + +// Map control keys: + +int key_map_north = KEY_UPARROW; +int key_map_south = KEY_DOWNARROW; +int key_map_east = KEY_RIGHTARROW; +int key_map_west = KEY_LEFTARROW; +int key_map_zoomin = '='; +int key_map_zoomout = '-'; +int key_map_toggle = KEY_TAB; +int key_map_maxzoom = '0'; +int key_map_follow = 'f'; +int key_map_grid = 'g'; +int key_map_mark = 'm'; +int key_map_clearmark = 'c'; + +// menu keys: + +int key_menu_activate = KEY_ESCAPE; +int key_menu_up = KEY_UPARROW; +int key_menu_down = KEY_DOWNARROW; +int key_menu_left = KEY_LEFTARROW; +int key_menu_right = KEY_RIGHTARROW; +int key_menu_back = KEY_BACKSPACE; +int key_menu_forward = KEY_ENTER; +int key_menu_confirm = 'y'; +int key_menu_abort = 'n'; + +int key_menu_help = KEY_F1; +int key_menu_save = KEY_F2; +int key_menu_load = KEY_F3; +int key_menu_volume = KEY_F4; +int key_menu_detail = KEY_F5; +int key_menu_qsave = KEY_F6; +int key_menu_endgame = KEY_F7; +int key_menu_messages = KEY_F8; +int key_menu_qload = KEY_F9; +int key_menu_quit = KEY_F10; +int key_menu_gamma = KEY_F11; + +int key_menu_incscreen = KEY_EQUALS; +int key_menu_decscreen = KEY_MINUS; +int key_menu_screenshot = 0; + +// +// Joystick controls +// + +int joybfire = 0; +int joybstrafe = 1; +int joybuse = 3; +int joybspeed = 2; + +int joybstrafeleft = -1; +int joybstraferight = -1; + +int joybjump = -1; + +int joybprevweapon = -1; +int joybnextweapon = -1; + +int joybmenu = -1; +int joybautomap = -1; + +// Control whether if a mouse button is double clicked, it acts like +// "use" has been pressed + +int dclick_use = 1; + +// +// Bind all of the common controls used by Doom and all other games. +// + +/* LATER +void M_BindBaseControls(void) +{ + M_BindIntVariable("key_right", &key_right); + M_BindIntVariable("key_left", &key_left); + M_BindIntVariable("key_up", &key_up); + M_BindIntVariable("key_down", &key_down); + M_BindIntVariable("key_strafeleft", &key_strafeleft); + M_BindIntVariable("key_straferight", &key_straferight); + M_BindIntVariable("key_fire", &key_fire); + M_BindIntVariable("key_use", &key_use); + M_BindIntVariable("key_strafe", &key_strafe); + M_BindIntVariable("key_speed", &key_speed); + + M_BindIntVariable("mouseb_fire", &mousebfire); + M_BindIntVariable("mouseb_strafe", &mousebstrafe); + M_BindIntVariable("mouseb_forward", &mousebforward); + + M_BindIntVariable("joyb_fire", &joybfire); + M_BindIntVariable("joyb_strafe", &joybstrafe); + M_BindIntVariable("joyb_use", &joybuse); + M_BindIntVariable("joyb_speed", &joybspeed); + + M_BindIntVariable("joyb_menu_activate", &joybmenu); + M_BindIntVariable("joyb_toggle_automap", &joybautomap); + + // Extra controls that are not in the Vanilla versions: + + M_BindIntVariable("joyb_strafeleft", &joybstrafeleft); + M_BindIntVariable("joyb_straferight", &joybstraferight); + M_BindIntVariable("mouseb_strafeleft", &mousebstrafeleft); + M_BindIntVariable("mouseb_straferight", &mousebstraferight); + M_BindIntVariable("mouseb_use", &mousebuse); + M_BindIntVariable("mouseb_backward", &mousebbackward); + M_BindIntVariable("dclick_use", &dclick_use); + M_BindIntVariable("key_pause", &key_pause); + M_BindIntVariable("key_message_refresh", &key_message_refresh); +} + +void M_BindHereticControls(void) +{ + M_BindIntVariable("key_flyup", &key_flyup); + M_BindIntVariable("key_flydown", &key_flydown); + M_BindIntVariable("key_flycenter", &key_flycenter); + + M_BindIntVariable("key_lookup", &key_lookup); + M_BindIntVariable("key_lookdown", &key_lookdown); + M_BindIntVariable("key_lookcenter", &key_lookcenter); + + M_BindIntVariable("key_invleft", &key_invleft); + M_BindIntVariable("key_invright", &key_invright); + M_BindIntVariable("key_useartifact", &key_useartifact); +} + +void M_BindHexenControls(void) +{ + M_BindIntVariable("key_jump", &key_jump); + M_BindIntVariable("mouseb_jump", &mousebjump); + M_BindIntVariable("joyb_jump", &joybjump); + + M_BindIntVariable("key_arti_all", &key_arti_all); + M_BindIntVariable("key_arti_health", &key_arti_health); + M_BindIntVariable("key_arti_poisonbag", &key_arti_poisonbag); + M_BindIntVariable("key_arti_blastradius", &key_arti_blastradius); + M_BindIntVariable("key_arti_teleport", &key_arti_teleport); + M_BindIntVariable("key_arti_teleportother", &key_arti_teleportother); + M_BindIntVariable("key_arti_egg", &key_arti_egg); + M_BindIntVariable("key_arti_invulnerability", &key_arti_invulnerability); +} + +void M_BindStrifeControls(void) +{ + // These are shared with all games, but have different defaults: + key_message_refresh = '/'; + + // These keys are shared with Heretic/Hexen but have different defaults: + key_jump = 'a'; + key_lookup = KEY_PGUP; + key_lookdown = KEY_PGDN; + key_invleft = KEY_INS; + key_invright = KEY_DEL; + + M_BindIntVariable("key_jump", &key_jump); + M_BindIntVariable("key_lookUp", &key_lookup); + M_BindIntVariable("key_lookDown", &key_lookdown); + M_BindIntVariable("key_invLeft", &key_invleft); + M_BindIntVariable("key_invRight", &key_invright); + + // Custom Strife-only Keys: + M_BindIntVariable("key_useHealth", &key_usehealth); + M_BindIntVariable("key_invquery", &key_invquery); + M_BindIntVariable("key_mission", &key_mission); + M_BindIntVariable("key_invPop", &key_invpop); + M_BindIntVariable("key_invKey", &key_invkey); + M_BindIntVariable("key_invHome", &key_invhome); + M_BindIntVariable("key_invEnd", &key_invend); + M_BindIntVariable("key_invUse", &key_invuse); + M_BindIntVariable("key_invDrop", &key_invdrop); + + // Strife also supports jump on mouse and joystick, and in the exact same + // manner as Hexen! + M_BindIntVariable("mouseb_jump", &mousebjump); + M_BindIntVariable("joyb_jump", &joybjump); +} + +void M_BindWeaponControls(void) +{ + M_BindIntVariable("key_weapon1", &key_weapon1); + M_BindIntVariable("key_weapon2", &key_weapon2); + M_BindIntVariable("key_weapon3", &key_weapon3); + M_BindIntVariable("key_weapon4", &key_weapon4); + M_BindIntVariable("key_weapon5", &key_weapon5); + M_BindIntVariable("key_weapon6", &key_weapon6); + M_BindIntVariable("key_weapon7", &key_weapon7); + M_BindIntVariable("key_weapon8", &key_weapon8); + + M_BindIntVariable("key_prevweapon", &key_prevweapon); + M_BindIntVariable("key_nextweapon", &key_nextweapon); + + M_BindIntVariable("joyb_prevweapon", &joybprevweapon); + M_BindIntVariable("joyb_nextweapon", &joybnextweapon); + + M_BindIntVariable("mouseb_prevweapon", &mousebprevweapon); + M_BindIntVariable("mouseb_nextweapon", &mousebnextweapon); +} + +void M_BindMapControls(void) +{ + M_BindIntVariable("key_map_north", &key_map_north); + M_BindIntVariable("key_map_south", &key_map_south); + M_BindIntVariable("key_map_east", &key_map_east); + M_BindIntVariable("key_map_west", &key_map_west); + M_BindIntVariable("key_map_zoomin", &key_map_zoomin); + M_BindIntVariable("key_map_zoomout", &key_map_zoomout); + M_BindIntVariable("key_map_toggle", &key_map_toggle); + M_BindIntVariable("key_map_maxzoom", &key_map_maxzoom); + M_BindIntVariable("key_map_follow", &key_map_follow); + M_BindIntVariable("key_map_grid", &key_map_grid); + M_BindIntVariable("key_map_mark", &key_map_mark); + M_BindIntVariable("key_map_clearmark", &key_map_clearmark); +} + +void M_BindMenuControls(void) +{ + M_BindIntVariable("key_menu_activate", &key_menu_activate); + M_BindIntVariable("key_menu_up", &key_menu_up); + M_BindIntVariable("key_menu_down", &key_menu_down); + M_BindIntVariable("key_menu_left", &key_menu_left); + M_BindIntVariable("key_menu_right", &key_menu_right); + M_BindIntVariable("key_menu_back", &key_menu_back); + M_BindIntVariable("key_menu_forward", &key_menu_forward); + M_BindIntVariable("key_menu_confirm", &key_menu_confirm); + M_BindIntVariable("key_menu_abort", &key_menu_abort); + + M_BindIntVariable("key_menu_help", &key_menu_help); + M_BindIntVariable("key_menu_save", &key_menu_save); + M_BindIntVariable("key_menu_load", &key_menu_load); + M_BindIntVariable("key_menu_volume", &key_menu_volume); + M_BindIntVariable("key_menu_detail", &key_menu_detail); + M_BindIntVariable("key_menu_qsave", &key_menu_qsave); + M_BindIntVariable("key_menu_endgame", &key_menu_endgame); + M_BindIntVariable("key_menu_messages", &key_menu_messages); + M_BindIntVariable("key_menu_qload", &key_menu_qload); + M_BindIntVariable("key_menu_quit", &key_menu_quit); + M_BindIntVariable("key_menu_gamma", &key_menu_gamma); + + M_BindIntVariable("key_menu_incscreen", &key_menu_incscreen); + M_BindIntVariable("key_menu_decscreen", &key_menu_decscreen); + M_BindIntVariable("key_menu_screenshot",&key_menu_screenshot); + M_BindIntVariable("key_demo_quit", &key_demo_quit); + M_BindIntVariable("key_spy", &key_spy); +} + +void M_BindChatControls(unsigned int num_players) +{ + char name[32]; // haleyjd: 20 not large enough - Thank you, come again! + unsigned int i; // haleyjd: signedness conflict + + M_BindIntVariable("key_multi_msg", &key_multi_msg); + + for (i=0; i +#include + +#include "m_fixed.h" +#include + + + +int abs(int x) { + return (x < 0) ? -x : x; +} + +// Fixme. __USE_C_FIXED__ or something. + +fixed_t +FixedMul +( fixed_t a, + fixed_t b ) +{ + return ((int64_t) a * (int64_t) b) >> FRACBITS; +} + + + +// +// FixedDiv, C version. +// + +fixed_t FixedDiv(fixed_t a, fixed_t b) +{ + if ((abs(a) >> 14) >= abs(b)) + { + return (a^b) < 0 ? INT_MIN : INT_MAX; + } + else + { + int64_t result; + + result = ((int64_t) a << FRACBITS) / b; + + return (fixed_t) result; + } +} + diff --git a/client/src/ipu/m_fixed.h b/client/src/ipu/m_fixed.h new file mode 100644 index 0000000..733b290 --- /dev/null +++ b/client/src/ipu/m_fixed.h @@ -0,0 +1,39 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Fixed point arithemtics, implementation. +// + + +#ifndef __M_FIXED__ +#define __M_FIXED__ + + + + +// +// Fixed point, 32bit as 16.16. +// +#define FRACBITS 16 +#define FRACUNIT (1< +#include + +#include "doomtype.h" + +boolean M_WriteFile(char *name, void *source, int length); +int M_ReadFile(char *name, byte **buffer); +void M_MakeDirectory(char *dir); +char *M_TempFile(char *s); +boolean M_FileExists(char *file); +char *M_FileCaseExists(char *file); +long M_FileLength(FILE *handle); +boolean M_StrToInt(const char *str, int *result); +void M_ExtractFileBase(char *path, char *dest); +void M_ForceUppercase(char *text); +void M_ForceLowercase(char *text); +char *M_StrCaseStr(char *haystack, char *needle); +char *M_StringDuplicate(const char *orig); +boolean M_StringCopy(char *dest, const char *src, size_t dest_size); +boolean M_StringConcat(char *dest, const char *src, size_t dest_size); +char *M_StringReplace(const char *haystack, const char *needle, + const char *replacement); +char *M_StringJoin(const char *s, ...); +boolean M_StringStartsWith(const char *s, const char *prefix); +boolean M_StringEndsWith(const char *s, const char *suffix); +int M_vsnprintf(char *buf, size_t buf_len, const char *s, va_list args); +int M_snprintf(char *buf, size_t buf_len, const char *s, ...); + +#endif + diff --git a/client/src/ipu/m_random.h b/client/src/ipu/m_random.h new file mode 100644 index 0000000..884fea2 --- /dev/null +++ b/client/src/ipu/m_random.h @@ -0,0 +1,37 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// + +#ifndef __M_RANDOM__ +#define __M_RANDOM__ + +#include "doomtype.h" + +// Returns a number from 0 to 255, +// from a lookup table. +int M_Random(void); + +// As M_Random, but used only by the play simulation. +int P_Random(void); + +// Fix randoms for demos. +void M_ClearRandom(void); + +// Defined version of P_Random() - P_Random() +int P_SubRandom(void); + +#endif diff --git a/client/src/ipu/memio.h b/client/src/ipu/memio.h new file mode 100644 index 0000000..84e6a7a --- /dev/null +++ b/client/src/ipu/memio.h @@ -0,0 +1,40 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// + +#ifndef MEMIO_H +#define MEMIO_H + +#include + +typedef struct _MEMFILE MEMFILE; + +typedef enum +{ + MEM_SEEK_SET, + MEM_SEEK_CUR, + MEM_SEEK_END, +} mem_rel_t; + +MEMFILE *mem_fopen_read(void *buf, size_t buflen); +size_t mem_fread(void *buf, size_t size, size_t nmemb, MEMFILE *stream); +MEMFILE *mem_fopen_write(void); +size_t mem_fwrite(const void *ptr, size_t size, size_t nmemb, MEMFILE *stream); +void mem_get_buf(MEMFILE *stream, void **buf, size_t *buflen); +void mem_fclose(MEMFILE *stream); +long mem_ftell(MEMFILE *stream); +int mem_fseek(MEMFILE *stream, signed long offset, mem_rel_t whence); + +#endif /* #ifndef MEMIO_H */ + diff --git a/client/src/ipu/midifile.h b/client/src/ipu/midifile.h new file mode 100644 index 0000000..6f8c801 --- /dev/null +++ b/client/src/ipu/midifile.h @@ -0,0 +1,171 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// MIDI file parsing. +// + +#ifndef MIDIFILE_H +#define MIDIFILE_H + +#include "doomtype.h" + +typedef struct midi_file_s midi_file_t; +typedef struct midi_track_iter_s midi_track_iter_t; + +#define MIDI_CHANNELS_PER_TRACK 16 + +typedef enum +{ + MIDI_EVENT_NOTE_OFF = 0x80, + MIDI_EVENT_NOTE_ON = 0x90, + MIDI_EVENT_AFTERTOUCH = 0xa0, + MIDI_EVENT_CONTROLLER = 0xb0, + MIDI_EVENT_PROGRAM_CHANGE = 0xc0, + MIDI_EVENT_CHAN_AFTERTOUCH = 0xd0, + MIDI_EVENT_PITCH_BEND = 0xe0, + + MIDI_EVENT_SYSEX = 0xf0, + MIDI_EVENT_SYSEX_SPLIT = 0xf7, + MIDI_EVENT_META = 0xff, +} midi_event_type_t; + +typedef enum +{ + MIDI_CONTROLLER_BANK_SELECT = 0x0, + MIDI_CONTROLLER_MODULATION = 0x1, + MIDI_CONTROLLER_BREATH_CONTROL = 0x2, + MIDI_CONTROLLER_FOOT_CONTROL = 0x3, + MIDI_CONTROLLER_PORTAMENTO = 0x4, + MIDI_CONTROLLER_DATA_ENTRY = 0x5, + + MIDI_CONTROLLER_MAIN_VOLUME = 0x7, + MIDI_CONTROLLER_PAN = 0xa, + + MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7b, +} midi_controller_t; + +typedef enum +{ + MIDI_META_SEQUENCE_NUMBER = 0x0, + + MIDI_META_TEXT = 0x1, + MIDI_META_COPYRIGHT = 0x2, + MIDI_META_TRACK_NAME = 0x3, + MIDI_META_INSTR_NAME = 0x4, + MIDI_META_LYRICS = 0x5, + MIDI_META_MARKER = 0x6, + MIDI_META_CUE_POINT = 0x7, + + MIDI_META_CHANNEL_PREFIX = 0x20, + MIDI_META_END_OF_TRACK = 0x2f, + + MIDI_META_SET_TEMPO = 0x51, + MIDI_META_SMPTE_OFFSET = 0x54, + MIDI_META_TIME_SIGNATURE = 0x58, + MIDI_META_KEY_SIGNATURE = 0x59, + MIDI_META_SEQUENCER_SPECIFIC = 0x7f, +} midi_meta_event_type_t; + +typedef struct +{ + // Meta event type: + + unsigned int type; + + // Length: + + unsigned int length; + + // Meta event data: + + byte *data; +} midi_meta_event_data_t; + +typedef struct +{ + // Length: + + unsigned int length; + + // Event data: + + byte *data; +} midi_sysex_event_data_t; + +typedef struct +{ + // The channel number to which this applies: + + unsigned int channel; + + // Extra parameters: + + unsigned int param1; + unsigned int param2; +} midi_channel_event_data_t; + +typedef struct +{ + // Time between the previous event and this event. + unsigned int delta_time; + + // Type of event: + midi_event_type_t event_type; + + union + { + midi_channel_event_data_t channel; + midi_meta_event_data_t meta; + midi_sysex_event_data_t sysex; + } data; +} midi_event_t; + +// Load a MIDI file. + +midi_file_t *MIDI_LoadFile(char *filename); + +// Free a MIDI file. + +void MIDI_FreeFile(midi_file_t *file); + +// Get the time division value from the MIDI header. + +unsigned int MIDI_GetFileTimeDivision(midi_file_t *file); + +// Get the number of tracks in a MIDI file. + +unsigned int MIDI_NumTracks(midi_file_t *file); + +// Start iterating over the events in a track. + +midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track_num); + +// Free an iterator. + +void MIDI_FreeIterator(midi_track_iter_t *iter); + +// Get the time until the next MIDI event in a track. + +unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter); + +// Get a pointer to the next MIDI event. + +int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event); + +// Reset an iterator to the beginning of a track. + +void MIDI_RestartIterator(midi_track_iter_t *iter); + +#endif /* #ifndef MIDIFILE_H */ + diff --git a/client/src/ipu/mus2mid.h b/client/src/ipu/mus2mid.h new file mode 100644 index 0000000..324025a --- /dev/null +++ b/client/src/ipu/mus2mid.h @@ -0,0 +1,29 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2006 Ben Ryves 2006 +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// mus2mid.h - Ben Ryves 2006 - http://benryves.com - benryves@benryves.com +// Use to convert a MUS file into a single track, type 0 MIDI file. + +#ifndef MUS2MID_H +#define MUS2MID_H + +#include "doomtype.h" +#include "memio.h" + +boolean mus2mid(MEMFILE *musinput, MEMFILE *midioutput); + +#endif /* #ifndef MUS2MID_H */ + diff --git a/client/src/ipu/net_client.h b/client/src/ipu/net_client.h new file mode 100644 index 0000000..96b8650 --- /dev/null +++ b/client/src/ipu/net_client.h @@ -0,0 +1,50 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// Network client code +// + +#ifndef NET_CLIENT_H +#define NET_CLIENT_H + +#include "doomtype.h" +#include "d_ticcmd.h" +#include "sha1.h" +#include "net_defs.h" + +boolean NET_CL_Connect(net_addr_t *addr, net_connect_data_t *data); +void NET_CL_Disconnect(void); +void NET_CL_Run(void); +void NET_CL_Init(void); +void NET_CL_LaunchGame(void); +void NET_CL_StartGame(net_gamesettings_t *settings); +void NET_CL_SendTiccmd(ticcmd_t *ticcmd, int maketic); +boolean NET_CL_GetSettings(net_gamesettings_t *_settings); +void NET_Init(void); + +void NET_BindVariables(void); + +extern boolean net_client_connected; +extern boolean net_client_received_wait_data; +extern net_waitdata_t net_client_wait_data; +extern boolean net_waiting_for_launch; +extern char *net_player_name; + +extern sha1_digest_t net_server_wad_sha1sum; +extern unsigned int net_server_is_freedoom; +extern sha1_digest_t net_local_wad_sha1sum; +extern unsigned int net_local_is_freedoom; + +extern boolean drone; + +#endif /* #ifndef NET_CLIENT_H */ diff --git a/client/src/ipu/net_common.h b/client/src/ipu/net_common.h new file mode 100644 index 0000000..6151b48 --- /dev/null +++ b/client/src/ipu/net_common.h @@ -0,0 +1,111 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// Common code shared between the client and server +// + +#ifndef NET_COMMON_H +#define NET_COMMON_H + +#include "d_mode.h" +#include "doomtype.h" +#include "net_defs.h" + +typedef enum +{ + // sending syn packets, waiting for an ACK reply + // (client side) + + NET_CONN_STATE_CONNECTING, + + // received a syn, sent an ack, waiting for an ack reply + // (server side) + + NET_CONN_STATE_WAITING_ACK, + + // successfully connected + + NET_CONN_STATE_CONNECTED, + + // sent a DISCONNECT packet, waiting for a DISCONNECT_ACK reply + + NET_CONN_STATE_DISCONNECTING, + + // client successfully disconnected + + NET_CONN_STATE_DISCONNECTED, + + // We are disconnected, but in a sleep state, waiting for several + // seconds. This is in case the DISCONNECT_ACK we sent failed + // to arrive, and we need to send another one. We keep this as + // a valid connection for a few seconds until we are sure that + // the other end has successfully disconnected as well. + + NET_CONN_STATE_DISCONNECTED_SLEEP, + +} net_connstate_t; + +// Reason a connection was terminated + +typedef enum +{ + // As the result of a local disconnect request + + NET_DISCONNECT_LOCAL, + + // As the result of a remote disconnect request + + NET_DISCONNECT_REMOTE, + + // Timeout (no data received in a long time) + + NET_DISCONNECT_TIMEOUT, + +} net_disconnect_reason_t; + +#define MAX_RETRIES 5 + +typedef struct net_reliable_packet_s net_reliable_packet_t; + +typedef struct +{ + net_connstate_t state; + net_disconnect_reason_t disconnect_reason; + net_addr_t *addr; + int last_send_time; + int num_retries; + int keepalive_send_time; + int keepalive_recv_time; + net_reliable_packet_t *reliable_packets; + int reliable_send_seq; + int reliable_recv_seq; +} net_connection_t; + + +void NET_Conn_SendPacket(net_connection_t *conn, net_packet_t *packet); +void NET_Conn_InitClient(net_connection_t *conn, net_addr_t *addr); +void NET_Conn_InitServer(net_connection_t *conn, net_addr_t *addr); +boolean NET_Conn_Packet(net_connection_t *conn, net_packet_t *packet, + unsigned int *packet_type); +void NET_Conn_Disconnect(net_connection_t *conn); +void NET_Conn_Run(net_connection_t *conn); +net_packet_t *NET_Conn_NewReliable(net_connection_t *conn, int packet_type); + +// Other miscellaneous common functions + +unsigned int NET_ExpandTicNum(unsigned int relative, unsigned int b); +boolean NET_ValidGameSettings(GameMode_t mode, GameMission_t mission, + net_gamesettings_t *settings); + +#endif /* #ifndef NET_COMMON_H */ + diff --git a/client/src/ipu/net_dedicated.h b/client/src/ipu/net_dedicated.h new file mode 100644 index 0000000..3d7387b --- /dev/null +++ b/client/src/ipu/net_dedicated.h @@ -0,0 +1,25 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// +// Dedicated server code. +// + +#ifndef NET_DEDICATED_H +#define NET_DEDICATED_H + +void NET_DedicatedServer(void); + +#endif /* #ifndef NET_DEDICATED_H */ + + diff --git a/client/src/ipu/net_defs.h b/client/src/ipu/net_defs.h new file mode 100644 index 0000000..f13db11 --- /dev/null +++ b/client/src/ipu/net_defs.h @@ -0,0 +1,246 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Definitions for use in networking code. +// + +#ifndef NET_DEFS_H +#define NET_DEFS_H + +// #include // LATER + +#include "doomtype.h" +#include "d_ticcmd.h" +#include "sha1.h" + +// Absolute maximum number of "nodes" in the game. This is different to +// NET_MAXPLAYERS, as there may be observers that are not participating +// (eg. left/right monitors) + +#define MAXNETNODES 16 + +// The maximum number of players, multiplayer/networking. +// This is the maximum supported by the networking code; individual games +// have their own values for MAXPLAYERS that can be smaller. + +#define NET_MAXPLAYERS 8 + +// Maximum length of a player's name. + +#define MAXPLAYERNAME 30 + +// Networking and tick handling related. + +#define BACKUPTICS 128 + +typedef struct _net_module_s net_module_t; +typedef struct _net_packet_s net_packet_t; +typedef struct _net_addr_s net_addr_t; +typedef struct _net_context_s net_context_t; + +struct _net_packet_s +{ + byte *data; + size_t len; + size_t alloced; + unsigned int pos; +}; + +struct _net_module_s +{ + // Initialize this module for use as a client + + boolean (*InitClient)(void); + + // Initialize this module for use as a server + + boolean (*InitServer)(void); + + // Send a packet + + void (*SendPacket)(net_addr_t *addr, net_packet_t *packet); + + // Check for new packets to receive + // + // Returns true if packet received + + boolean (*RecvPacket)(net_addr_t **addr, net_packet_t **packet); + + // Converts an address to a string + + void (*AddrToString)(net_addr_t *addr, char *buffer, int buffer_len); + + // Free back an address when no longer in use + + void (*FreeAddress)(net_addr_t *addr); + + // Try to resolve a name to an address + + net_addr_t *(*ResolveAddress)(char *addr); +}; + +// net_addr_t + +struct _net_addr_s +{ + net_module_t *module; + void *handle; +}; + +// magic number sent when connecting to check this is a valid client + +#define NET_MAGIC_NUMBER 3436803284U + +// header field value indicating that the packet is a reliable packet + +#define NET_RELIABLE_PACKET (1 << 15) + +// packet types + +typedef enum +{ + NET_PACKET_TYPE_SYN, + NET_PACKET_TYPE_ACK, + NET_PACKET_TYPE_REJECTED, + NET_PACKET_TYPE_KEEPALIVE, + NET_PACKET_TYPE_WAITING_DATA, + NET_PACKET_TYPE_GAMESTART, + NET_PACKET_TYPE_GAMEDATA, + NET_PACKET_TYPE_GAMEDATA_ACK, + NET_PACKET_TYPE_DISCONNECT, + NET_PACKET_TYPE_DISCONNECT_ACK, + NET_PACKET_TYPE_RELIABLE_ACK, + NET_PACKET_TYPE_GAMEDATA_RESEND, + NET_PACKET_TYPE_CONSOLE_MESSAGE, + NET_PACKET_TYPE_QUERY, + NET_PACKET_TYPE_QUERY_RESPONSE, + NET_PACKET_TYPE_LAUNCH, +} net_packet_type_t; + +typedef enum +{ + NET_MASTER_PACKET_TYPE_ADD, + NET_MASTER_PACKET_TYPE_ADD_RESPONSE, + NET_MASTER_PACKET_TYPE_QUERY, + NET_MASTER_PACKET_TYPE_QUERY_RESPONSE, + NET_MASTER_PACKET_TYPE_GET_METADATA, + NET_MASTER_PACKET_TYPE_GET_METADATA_RESPONSE, + NET_MASTER_PACKET_TYPE_SIGN_START, + NET_MASTER_PACKET_TYPE_SIGN_START_RESPONSE, + NET_MASTER_PACKET_TYPE_SIGN_END, + NET_MASTER_PACKET_TYPE_SIGN_END_RESPONSE, +} net_master_packet_type_t; + +// Settings specified when the client connects to the server. + +typedef struct +{ + int gamemode; + int gamemission; + int lowres_turn; + int drone; + int max_players; + int is_freedoom; + sha1_digest_t wad_sha1sum; + int player_class; +} net_connect_data_t; + +// Game settings sent by client to server when initiating game start, +// and received from the server by clients when the game starts. + +typedef struct +{ + int ticdup; + int extratics; + int deathmatch; + int episode; + int nomonsters; + int fast_monsters; + int respawn_monsters; + int map; + int skill; + int gameversion; + int lowres_turn; + int new_sync; + int timelimit; + int loadgame; + int random; // [Strife only] + + // These fields are only used by the server when sending a game + // start message: + + int num_players; + int consoleplayer; + + // Hexen player classes: + + int player_classes[NET_MAXPLAYERS]; + +} net_gamesettings_t; + +#define NET_TICDIFF_FORWARD (1 << 0) +#define NET_TICDIFF_SIDE (1 << 1) +#define NET_TICDIFF_TURN (1 << 2) +#define NET_TICDIFF_BUTTONS (1 << 3) +#define NET_TICDIFF_CONSISTANCY (1 << 4) +#define NET_TICDIFF_CHATCHAR (1 << 5) +#define NET_TICDIFF_RAVEN (1 << 6) +#define NET_TICDIFF_STRIFE (1 << 7) + +typedef struct +{ + unsigned int diff; + ticcmd_t cmd; +} net_ticdiff_t; + +// Complete set of ticcmds from all players + +typedef struct +{ + signed int latency; + unsigned int seq; + boolean playeringame[NET_MAXPLAYERS]; + net_ticdiff_t cmds[NET_MAXPLAYERS]; +} net_full_ticcmd_t; + +// Data sent in response to server queries + +typedef struct +{ + char *version; + int server_state; + int num_players; + int max_players; + int gamemode; + int gamemission; + char *description; +} net_querydata_t; + +// Data sent by the server while waiting for the game to start. + +typedef struct +{ + int num_players; + int num_drones; + int ready_players; + int max_players; + int is_controller; + int consoleplayer; + char player_names[NET_MAXPLAYERS][MAXPLAYERNAME]; + char player_addrs[NET_MAXPLAYERS][MAXPLAYERNAME]; + sha1_digest_t wad_sha1sum; + int is_freedoom; +} net_waitdata_t; + +#endif /* #ifndef NET_DEFS_H */ diff --git a/client/src/ipu/net_io.h b/client/src/ipu/net_io.h new file mode 100644 index 0000000..ab94a8f --- /dev/null +++ b/client/src/ipu/net_io.h @@ -0,0 +1,37 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Network packet manipulation (net_packet_t) +// + +#ifndef NET_IO_H +#define NET_IO_H + +#include "doomtype.h" +#include "net_defs.h" + +extern net_addr_t net_broadcast_addr; + +net_context_t *NET_NewContext(void); +void NET_AddModule(net_context_t *context, net_module_t *module); +void NET_SendPacket(net_addr_t *addr, net_packet_t *packet); +void NET_SendBroadcast(net_context_t *context, net_packet_t *packet); +boolean NET_RecvPacket(net_context_t *context, net_addr_t **addr, + net_packet_t **packet); +char *NET_AddrToString(net_addr_t *addr); +void NET_FreeAddress(net_addr_t *addr); +net_addr_t *NET_ResolveAddress(net_context_t *context, char *address); + +#endif /* #ifndef NET_IO_H */ + diff --git a/client/src/ipu/net_loop.h b/client/src/ipu/net_loop.h new file mode 100644 index 0000000..5a2e58e --- /dev/null +++ b/client/src/ipu/net_loop.h @@ -0,0 +1,27 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Loopback network module for server compiled into the client +// + +#ifndef NET_LOOP_H +#define NET_LOOP_H + +#include "net_defs.h" + +extern net_module_t net_loop_client_module; +extern net_module_t net_loop_server_module; + +#endif /* #ifndef NET_LOOP_H */ + diff --git a/client/src/ipu/net_packet.h b/client/src/ipu/net_packet.h new file mode 100644 index 0000000..cb170be --- /dev/null +++ b/client/src/ipu/net_packet.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Definitions for use in networking code. +// + +#ifndef NET_PACKET_H +#define NET_PACKET_H + +#include "doomtype.h" +#include "net_defs.h" + +net_packet_t *NET_NewPacket(int initial_size); +net_packet_t *NET_PacketDup(net_packet_t *packet); +void NET_FreePacket(net_packet_t *packet); + +boolean NET_ReadInt8(net_packet_t *packet, unsigned int *data); +boolean NET_ReadInt16(net_packet_t *packet, unsigned int *data); +boolean NET_ReadInt32(net_packet_t *packet, unsigned int *data); + +boolean NET_ReadSInt8(net_packet_t *packet, signed int *data); +boolean NET_ReadSInt16(net_packet_t *packet, signed int *data); +boolean NET_ReadSInt32(net_packet_t *packet, signed int *data); + +char *NET_ReadString(net_packet_t *packet); + +void NET_WriteInt8(net_packet_t *packet, unsigned int i); +void NET_WriteInt16(net_packet_t *packet, unsigned int i); +void NET_WriteInt32(net_packet_t *packet, unsigned int i); + +void NET_WriteString(net_packet_t *packet, char *string); + +#endif /* #ifndef NET_PACKET_H */ + diff --git a/client/src/ipu/net_query.h b/client/src/ipu/net_query.h new file mode 100644 index 0000000..323545f --- /dev/null +++ b/client/src/ipu/net_query.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Querying servers to find their current status. +// + +#ifndef NET_QUERY_H +#define NET_QUERY_H + +#include "doomtype.h" +#include "net_defs.h" + +typedef void (*net_query_callback_t)(net_addr_t *addr, + net_querydata_t *querydata, + unsigned int ping_time, + void *user_data); + +extern int NET_StartLANQuery(void); +extern int NET_StartMasterQuery(void); + +extern void NET_LANQuery(void); +extern void NET_MasterQuery(void); +extern void NET_QueryAddress(char *addr); +extern net_addr_t *NET_FindLANServer(void); + +extern int NET_Query_Poll(net_query_callback_t callback, void *user_data); + +extern net_addr_t *NET_Query_ResolveMaster(net_context_t *context); +extern void NET_Query_AddToMaster(net_addr_t *master_addr); +extern boolean NET_Query_CheckAddedToMaster(boolean *result); +extern void NET_Query_MasterResponse(net_packet_t *packet); + +#endif /* #ifndef NET_QUERY_H */ + diff --git a/client/src/ipu/net_sdl.h b/client/src/ipu/net_sdl.h new file mode 100644 index 0000000..c249de1 --- /dev/null +++ b/client/src/ipu/net_sdl.h @@ -0,0 +1,26 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Networking module which uses SDL_net +// + +#ifndef NET_SDL_H +#define NET_SDL_H + +#include "net_defs.h" + +extern net_module_t net_sdl_module; + +#endif /* #ifndef NET_SDL_H */ + diff --git a/client/src/ipu/net_server.h b/client/src/ipu/net_server.h new file mode 100644 index 0000000..d144107 --- /dev/null +++ b/client/src/ipu/net_server.h @@ -0,0 +1,43 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// Network server code +// + +#ifndef NET_SERVER_H +#define NET_SERVER_H + +#include "net_defs.h" +// initialize server and wait for connections + +void NET_SV_Init(void); + +// run server: check for new packets received etc. + +void NET_SV_Run(void); + +// Shut down the server +// Blocks until all clients disconnect, or until a 5 second timeout + +void NET_SV_Shutdown(void); + +// Add a network module to the context used by the server + +void NET_SV_AddModule(net_module_t *module); + +// Register server with master server. + +void NET_SV_RegisterWithMaster(void); + +#endif /* #ifndef NET_SERVER_H */ + diff --git a/client/src/ipu/net_structrw.h b/client/src/ipu/net_structrw.h new file mode 100644 index 0000000..1d7c7d0 --- /dev/null +++ b/client/src/ipu/net_structrw.h @@ -0,0 +1,52 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// + +#ifndef NET_STRUCTRW_H +#define NET_STRUCTRW_H + +#include "aes_prng.h" +#include "d_ticcmd.h" +#include "doomtype.h" +#include "net_defs.h" +#include "sha1.h" + +void NET_WriteConnectData(net_packet_t *packet, net_connect_data_t *data); +boolean NET_ReadConnectData(net_packet_t *packet, net_connect_data_t *data); + +extern void NET_WriteSettings(net_packet_t *packet, net_gamesettings_t *settings); +extern boolean NET_ReadSettings(net_packet_t *packet, net_gamesettings_t *settings); + +extern void NET_WriteQueryData(net_packet_t *packet, net_querydata_t *querydata); +extern boolean NET_ReadQueryData(net_packet_t *packet, net_querydata_t *querydata); + +extern void NET_WriteTiccmdDiff(net_packet_t *packet, net_ticdiff_t *diff, boolean lowres_turn); +extern boolean NET_ReadTiccmdDiff(net_packet_t *packet, net_ticdiff_t *diff, boolean lowres_turn); +extern void NET_TiccmdDiff(ticcmd_t *tic1, ticcmd_t *tic2, net_ticdiff_t *diff); +extern void NET_TiccmdPatch(ticcmd_t *src, net_ticdiff_t *diff, ticcmd_t *dest); + +boolean NET_ReadFullTiccmd(net_packet_t *packet, net_full_ticcmd_t *cmd, boolean lowres_turn); +void NET_WriteFullTiccmd(net_packet_t *packet, net_full_ticcmd_t *cmd, boolean lowres_turn); + +boolean NET_ReadSHA1Sum(net_packet_t *packet, sha1_digest_t digest); +void NET_WriteSHA1Sum(net_packet_t *packet, sha1_digest_t digest); + +void NET_WriteWaitData(net_packet_t *packet, net_waitdata_t *data); +boolean NET_ReadWaitData(net_packet_t *packet, net_waitdata_t *data); + +void NET_SafePuts(char *msg); + +boolean NET_ReadPRNGSeed(net_packet_t *packet, prng_seed_t seed); +void NET_WritePRNGSeed(net_packet_t *packet, prng_seed_t seed); + +#endif /* #ifndef NET_STRUCTRW_H */ diff --git a/client/src/ipu/p_inter.h b/client/src/ipu/p_inter.h new file mode 100644 index 0000000..c5f7966 --- /dev/null +++ b/client/src/ipu/p_inter.h @@ -0,0 +1,27 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// + +#ifndef __P_INTER__ +#define __P_INTER__ + +#include "d_player.h" +#include "doomtype.h" + +boolean P_GivePower(player_t *, int); + +#endif diff --git a/client/src/ipu/p_local.h b/client/src/ipu/p_local.h new file mode 100644 index 0000000..a808875 --- /dev/null +++ b/client/src/ipu/p_local.h @@ -0,0 +1,244 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Play functions, animation, global header. +// + +#ifndef __P_LOCAL__ +#define __P_LOCAL__ + +#ifndef __R_LOCAL__ +#include "r_local.h" +#endif + +#define FLOATSPEED (FRACUNIT * 4) + +#define MAXHEALTH 100 +#define VIEWHEIGHT (41 * FRACUNIT) + +// mapblocks are used to check movement +// against lines and things +#define MAPBLOCKUNITS 128 +#define MAPBLOCKSIZE (MAPBLOCKUNITS * FRACUNIT) +#define MAPBLOCKSHIFT (FRACBITS + 7) +#define MAPBMASK (MAPBLOCKSIZE - 1) +#define MAPBTOFRAC (MAPBLOCKSHIFT - FRACBITS) + +// player radius for movement checking +#define PLAYERRADIUS 16 * FRACUNIT + +// MAXRADIUS is for precalculated sector block boxes +// the spider demon is larger, +// but we do not have any moving sectors nearby +#define MAXRADIUS 32 * FRACUNIT + +#define GRAVITY FRACUNIT +#define MAXMOVE (30 * FRACUNIT) + +#define USERANGE (64 * FRACUNIT) +#define MELEERANGE (64 * FRACUNIT) +#define MISSILERANGE (32 * 64 * FRACUNIT) + +// follow a player exlusively for 3 seconds +#define BASETHRESHOLD 100 + +// +// P_TICK +// + +// both the head and tail of the thinker list +extern thinker_t thinkercap; // LATER + +void P_InitThinkers(void); +void P_AddThinker(thinker_t *thinker); +void P_RemoveThinker(thinker_t *thinker); + +// +// P_PSPR +// +void P_SetupPsprites(player_t *curplayer); +void P_MovePsprites(player_t *curplayer); +void P_DropWeapon(player_t *player); + +// +// P_USER +// +void P_PlayerThink(player_t *player); + +// +// P_MOBJ +// +#define ONFLOORZ INT_MIN +#define ONCEILINGZ INT_MAX + +// Time interval for item respawning. +#define ITEMQUESIZE 128 + +extern mapthing_t itemrespawnque[ITEMQUESIZE]; +extern int itemrespawntime[ITEMQUESIZE]; +extern int iquehead; +extern int iquetail; + +void P_RespawnSpecials(void); + +mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type); + +void P_RemoveMobj(mobj_t *th); +mobj_t *P_SubstNullMobj(mobj_t *th); +boolean P_SetMobjState(mobj_t *mobj, statenum_t state); +void P_MobjThinker(mobj_t *mobj); + +void P_SpawnPuff(fixed_t x, fixed_t y, fixed_t z); +void P_SpawnBlood(fixed_t x, fixed_t y, fixed_t z, int damage); +mobj_t *P_SpawnMissile(mobj_t *source, mobj_t *dest, mobjtype_t type); +void P_SpawnPlayerMissile(mobj_t *source, mobjtype_t type); + +// +// P_ENEMY +// +void P_NoiseAlert(mobj_t *target, mobj_t *emmiter); + +// +// P_MAPUTL +// +typedef struct { + fixed_t x; + fixed_t y; + fixed_t dx; + fixed_t dy; + +} divline_t; + +typedef struct { + fixed_t frac; // along trace line + boolean isaline; + union { + mobj_t *thing; + line_t *line; + } d; +} intercept_t; + +// Extended MAXINTERCEPTS, to allow for intercepts overrun emulation. + +#define MAXINTERCEPTS_ORIGINAL 128 +#define MAXINTERCEPTS (MAXINTERCEPTS_ORIGINAL + 61) + +extern intercept_t intercepts[MAXINTERCEPTS]; +extern intercept_t *intercept_p; + +typedef boolean (*traverser_t)(intercept_t *in); + +fixed_t P_AproxDistance(fixed_t dx, fixed_t dy); +int P_PointOnLineSide(fixed_t x, fixed_t y, line_t *line); +int P_PointOnDivlineSide(fixed_t x, fixed_t y, divline_t *line); +void P_MakeDivline(line_t *li, divline_t *dl); +fixed_t P_InterceptVector(divline_t *v2, divline_t *v1); +int P_BoxOnLineSide(fixed_t *tmbox, line_t *ld); + +extern fixed_t opentop; +extern fixed_t openbottom; +extern fixed_t openrange; +extern fixed_t lowfloor; + +void P_LineOpening(line_t *linedef); + +boolean P_BlockLinesIterator(int x, int y, boolean (*func)(line_t *)); +boolean P_BlockThingsIterator(int x, int y, boolean (*func)(mobj_t *)); + +#define PT_ADDLINES 1 +#define PT_ADDTHINGS 2 +#define PT_EARLYOUT 4 + +extern divline_t trace; + +boolean P_PathTraverse(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, + int flags, boolean (*trav)(intercept_t *)); + +void P_UnsetThingPosition(mobj_t *thing); +void P_SetThingPosition(mobj_t *thing); + +// +// P_MAP +// + +// If "floatok" true, move would be ok +// if within "tmfloorz - tmceilingz". +extern boolean floatok; +extern fixed_t tmfloorz; +extern fixed_t tmceilingz; + +extern line_t *ceilingline; + +// fraggle: I have increased the size of this buffer. In the original Doom, +// overrunning past this limit caused other bits of memory to be overwritten, +// affecting demo playback. However, in doing so, the limit was still +// exceeded. So we have to support more than 8 specials. +// +// We keep the original limit, to detect what variables in memory were +// overwritten (see SpechitOverrun()) + +#define MAXSPECIALCROSS 20 +#define MAXSPECIALCROSS_ORIGINAL 8 + +extern line_t *spechit[MAXSPECIALCROSS]; +extern int numspechit; + +boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y); +boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y); +boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y); +void P_SlideMove(mobj_t *mo); +boolean P_CheckSight(mobj_t *t1, mobj_t *t2); +void P_UseLines(player_t *player); + +boolean P_ChangeSector(sector_t *sector, boolean crunch); + +extern mobj_t *linetarget; // who got hit (or NULL) + +fixed_t P_AimLineAttack(mobj_t *t1, angle_t angle, fixed_t distance); + +void P_LineAttack(mobj_t *t1, angle_t angle, fixed_t distance, fixed_t slope, + int damage); + +void P_RadiusAttack(mobj_t *spot, mobj_t *source, int damage); + +// +// P_SETUP +// +extern byte *rejectmatrix; // for fast sight rejection +extern short *blockmaplump; // offsets in blockmap are from here +extern short *blockmap; +extern int bmapwidth; +extern int bmapheight; // in mapblocks +extern fixed_t bmaporgx; +extern fixed_t bmaporgy; // origin of block map +extern mobj_t **blocklinks; // for thing chains + +// +// P_INTER +// +extern int maxammo[NUMAMMO]; +extern int clipammo[NUMAMMO]; + +void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher); + +void P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, + int damage); + +// +// P_SPEC +// +#include "p_spec.h" + +#endif // __P_LOCAL__ diff --git a/client/src/ipu/p_mobj.h b/client/src/ipu/p_mobj.h new file mode 100644 index 0000000..37c439f --- /dev/null +++ b/client/src/ipu/p_mobj.h @@ -0,0 +1,273 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Map Objects, MObj, definition and handling. +// + +#ifndef __P_MOBJ__ +#define __P_MOBJ__ + +// Basics. +#include "m_fixed.h" +#include "tables.h" + +// We need the thinker_t stuff. +#include "d_think.h" + +// We need the WAD data structure for Map things, +// from the THINGS lump. +#include "doomdata.h" + +// States are tied to finite states are +// tied to animation frames. +// Needs precompiled tables/data structures. +#include "info.h" + +// +// NOTES: mobj_t +// +// mobj_ts are used to tell the refresh where to draw an image, +// tell the world simulation when objects are contacted, +// and tell the sound driver how to position a sound. +// +// The refresh uses the next and prev links to follow +// lists of things in sectors as they are being drawn. +// The sprite, frame, and angle elements determine which patch_t +// is used to draw the sprite if it is visible. +// The sprite and frame values are allmost allways set +// from state_t structures. +// The statescr.exe utility generates the states.h and states.c +// files that contain the sprite/frame numbers from the +// statescr.txt source file. +// The xyz origin point represents a point at the bottom middle +// of the sprite (between the feet of a biped). +// This is the default origin position for patch_ts grabbed +// with lumpy.exe. +// A walking creature will have its z equal to the floor +// it is standing on. +// +// The sound code uses the x,y, and subsector fields +// to do stereo positioning of any sound effited by the mobj_t. +// +// The play simulation uses the blocklinks, x,y,z, radius, height +// to determine when mobj_ts are touching each other, +// touching lines in the map, or hit by trace lines (gunshots, +// lines of sight, etc). +// The mobj_t->flags element has various bit flags +// used by the simulation. +// +// Every mobj_t is linked into a single sector +// based on its origin coordinates. +// The subsector_t is found with R_PointInSubsector(x,y), +// and the sector_t can be found with subsector->sector. +// The sector links are only used by the rendering code, +// the play simulation does not care about them at all. +// +// Any mobj_t that needs to be acted upon by something else +// in the play world (block movement, be shot, etc) will also +// need to be linked into the blockmap. +// If the thing has the MF_NOBLOCK flag set, it will not use +// the block links. It can still interact with other things, +// but only as the instigator (missiles will run into other +// things, but nothing can run into a missile). +// Each block in the grid is 128*128 units, and knows about +// every line_t that it contains a piece of, and every +// interactable mobj_t that has its origin contained. +// +// A valid mobj_t is a mobj_t that has the proper subsector_t +// filled in for its xy coordinates and is linked into the +// sector from which the subsector was made, or has the +// MF_NOSECTOR flag set (the subsector_t needs to be valid +// even if MF_NOSECTOR is set), and is linked into a blockmap +// block or has the MF_NOBLOCKMAP flag set. +// Links should only be modified by the P_[Un]SetThingPosition() +// functions. +// Do not change the MF_NO? flags while a thing is valid. +// +// Any questions? +// + +// +// Misc. mobj flags +// +typedef enum { + // Call P_SpecialThing when touched. + MF_SPECIAL = 1, + // Blocks. + MF_SOLID = 2, + // Can be hit. + MF_SHOOTABLE = 4, + // Don't use the sector links (invisible but touchable). + MF_NOSECTOR = 8, + // Don't use the blocklinks (inert but displayable) + MF_NOBLOCKMAP = 16, + + // Not to be activated by sound, deaf monster. + MF_AMBUSH = 32, + // Will try to attack right back. + MF_JUSTHIT = 64, + // Will take at least one step before attacking. + MF_JUSTATTACKED = 128, + // On level spawning (initial position), + // hang from ceiling instead of stand on floor. + MF_SPAWNCEILING = 256, + // Don't apply gravity (every tic), + // that is, object will float, keeping current height + // or changing it actively. + MF_NOGRAVITY = 512, + + // Movement flags. + // This allows jumps from high places. + MF_DROPOFF = 0x400, + // For players, will pick up items. + MF_PICKUP = 0x800, + // Player cheat. ??? + MF_NOCLIP = 0x1000, + // Player: keep info about sliding along walls. + MF_SLIDE = 0x2000, + // Allow moves to any height, no gravity. + // For active floaters, e.g. cacodemons, pain elementals. + MF_FLOAT = 0x4000, + // Don't cross lines + // ??? or look at heights on teleport. + MF_TELEPORT = 0x8000, + // Don't hit same species, explode on block. + // Player missiles as well as fireballs of various kinds. + MF_MISSILE = 0x10000, + // Dropped by a demon, not level spawned. + // E.g. ammo clips dropped by dying former humans. + MF_DROPPED = 0x20000, + // Use fuzzy draw (shadow demons or spectres), + // temporary player invisibility powerup. + MF_SHADOW = 0x40000, + // Flag: don't bleed when shot (use puff), + // barrels and shootable furniture shall not bleed. + MF_NOBLOOD = 0x80000, + // Don't stop moving halfway off a step, + // that is, have dead bodies slide down all the way. + MF_CORPSE = 0x100000, + // Floating to a height for a move, ??? + // don't auto float to target's height. + MF_INFLOAT = 0x200000, + + // On kill, count this enemy object + // towards intermission kill total. + // Happy gathering. + MF_COUNTKILL = 0x400000, + + // On picking up, count this item object + // towards intermission item total. + MF_COUNTITEM = 0x800000, + + // Special handling: skull in flight. + // Neither a cacodemon nor a missile. + MF_SKULLFLY = 0x1000000, + + // Don't spawn this object + // in death match mode (e.g. key cards). + MF_NOTDMATCH = 0x2000000, + + // Player sprites in multiplayer modes are modified + // using an internal color lookup table for re-indexing. + // If 0x4 0x8 or 0xc, + // use a translation table for player colormaps + MF_TRANSLATION = 0xc000000, + // Hmm ???. + MF_TRANSSHIFT = 26 + +} mobjflag_t; + +// Map Object definition. +typedef struct mobj_s { + // List: thinker links. + thinker_t thinker; + + // Info for drawing: position. + fixed_t x; + fixed_t y; + fixed_t z; + + // More list: links in sector (if needed) + struct mobj_s *snext; + struct mobj_s *sprev; + + // More drawing info: to determine current sprite. + angle_t angle; // orientation + spritenum_t sprite; // used to find patch_t and flip value + int frame; // might be ORed with FF_FULLBRIGHT + + // Interaction info, by BLOCKMAP. + // Links in blocks (if needed). + struct mobj_s *bnext; + struct mobj_s *bprev; + + struct subsector_s *subsector; + + // The closest interval over all contacted Sectors. + fixed_t floorz; + fixed_t ceilingz; + + // For movement checking. + fixed_t radius; + fixed_t height; + + // Momentums, used to update position. + fixed_t momx; + fixed_t momy; + fixed_t momz; + + // If == validcount, already checked. + int validcount; + + mobjtype_t type; + mobjinfo_t *info; // &mobjinfo[mobj->type] + + int tics; // state tic counter + state_t *state; + int flags; + int health; + + // Movement direction, movement generation (zig-zagging). + int movedir; // 0-7 + int movecount; // when 0, select a new dir + + // Thing being chased/attacked (or NULL), + // also the originator for missiles. + struct mobj_s *target; + + // Reaction time: if non 0, don't attack yet. + // Used by player to freeze a bit after teleporting. + int reactiontime; + + // If >0, the target will be chased + // no matter what (even if shot) + int threshold; + + // Additional info record for player avatars only. + // Only valid if type == MT_PLAYER + struct player_s *player; + + // Player number last looked for. + int lastlook; + + // For nightmare respawn. + mapthing_t spawnpoint; + + // Thing being chased/attacked for tracers. + struct mobj_s *tracer; + +} mobj_t; + +#endif diff --git a/client/src/ipu/p_pspr.h b/client/src/ipu/p_pspr.h new file mode 100644 index 0000000..23f33c6 --- /dev/null +++ b/client/src/ipu/p_pspr.h @@ -0,0 +1,61 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Sprite animation. +// + +#ifndef __P_PSPR__ +#define __P_PSPR__ + +// +// Needs to include the precompiled +// sprite animation tables. +// Header generated by multigen utility. +// This includes all the data for thing animation, +// i.e. the Thing Atrributes table +// and the Frame Sequence table. +#include "info.h" +// Basic data types. +// Needs fixed point, and BAM angles. +#include "m_fixed.h" + +// +// Frame flags: +// handles maximum brightness (torches, muzzle flare, light sources) +// +#define FF_FULLBRIGHT 0x8000 // flag in thing->frame +#define FF_FRAMEMASK 0x7fff + +// +// Overlay psprites are scaled shapes +// drawn directly on the view screen, +// coordinates are given for a 320*200 view screen. +// +typedef enum { + ps_weapon, + ps_flash, + NUMPSPRITES + +} psprnum_t; + +typedef struct { + state_t *state; // a NULL state means not active + int tics; + fixed_t sx; + fixed_t sy; + +} pspdef_t; + +#endif diff --git a/client/src/ipu/p_saveg.h b/client/src/ipu/p_saveg.h new file mode 100644 index 0000000..aa173c2 --- /dev/null +++ b/client/src/ipu/p_saveg.h @@ -0,0 +1,65 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Savegame I/O, archiving, persistence. +// + +#ifndef __P_SAVEG__ +#define __P_SAVEG__ + +#include + +#include "doomtype.h" + +#define SAVEGAME_EOF 0x1d +#define VERSIONSIZE 16 + +// maximum size of a savegame description + +#define SAVESTRINGSIZE 24 + +// temporary filename to use while saving. + +char *P_TempSaveGameFile(void); + +// filename to use for a savegame slot + +char *P_SaveGameFile(int slot); + +// Savegame file header read/write functions + +boolean P_ReadSaveGameHeader(void); +void P_WriteSaveGameHeader(char *description); + +// Savegame end-of-file read/write functions + +boolean P_ReadSaveGameEOF(void); +void P_WriteSaveGameEOF(void); + +// Persistent storage/archiving. +// These are the load / save game routines. +void P_ArchivePlayers(void); +void P_UnArchivePlayers(void); +void P_ArchiveWorld(void); +void P_UnArchiveWorld(void); +void P_ArchiveThinkers(void); +void P_UnArchiveThinkers(void); +void P_ArchiveSpecials(void); +void P_UnArchiveSpecials(void); + +extern FILE *save_stream; +extern boolean savegame_error; + +#endif diff --git a/client/src/ipu/p_setup.c b/client/src/ipu/p_setup.c new file mode 100644 index 0000000..e9ee976 --- /dev/null +++ b/client/src/ipu/p_setup.c @@ -0,0 +1,410 @@ + +#include "d_mode.h" +#include "doomstat.h" +#include "i_swap.h" +#include "m_bbox.h" +#include "r_defs.h" +#include "p_local.h" + +#include "ipu_malloc.h" +#include "ipu_transfer.h" +#include "ipu_print.h" + + +// +// MAP related Lookup tables. +// Store VERTEXES, LINEDEFS, SIDEDEFS, etc. +// +int numvertexes; +vertex_t *vertexes; + +int numsegs; +seg_t *segs; + +int numsectors; +sector_t *sectors; + +int numsubsectors; +subsector_t *subsectors; + +int numnodes; +node_t *nodes; + +int numlines; +line_t *lines; + +int numsides; +side_t *sides; + +static int totallines; + +// BLOCKMAP +// Created from axis aligned bounding box +// of the map, a rectangular array of +// blocks of size ... +// Used to speed up collision detection +// by spatial subdivision in 2D. +// +// Blockmap size. +int bmapwidth; +int bmapheight; // size in mapblocks +short *blockmap; // int for larger maps +// offsets in blockmap are from here +short *blockmaplump; +// origin of block map +fixed_t bmaporgx; +fixed_t bmaporgy; +// for thing chains +mobj_t **blocklinks; + +// REJECT +// For fast sight rejection. +// Speeds up enemy AI by skipping detailed +// LineOf Sight calculation. +// Without special effect, this could be +// used as a PVS lookup as well. +// +byte *rejectmatrix; + +// Maintain single and multi player starting spots. +#define MAX_DEATHMATCH_STARTS 10 + +mapthing_t deathmatchstarts[MAX_DEATHMATCH_STARTS]; +mapthing_t *deathmatch_p; +mapthing_t playerstarts[MAXPLAYERS]; + + +// +// P_LoadVertexes +// +void P_LoadVertexes(const unsigned char *buf) { + byte *data; + int i; + mapvertex_t *ml; + vertex_t *li; + + // Determine number of lumps: + // total lump length / vertex record length. + int lumplen = ((int*)buf)[0]; + numvertexes = lumplen / sizeof(mapvertex_t); + + // Allocate zone memory for buffer. + vertexes = IPU_level_malloc(numvertexes * sizeof(vertex_t)); + + // Load data into cache. + // JOSEF: data = W_CacheLumpNum(lump, PU_STATIC); + + ml = (mapvertex_t *)(&buf[sizeof(int)]); + li = vertexes; + + // Copy and convert vertex coordinates, + // internal representation as fixed. + for (i = 0; i < numvertexes; i++, li++, ml++) { + li->x = ml->x << FRACBITS; + li->y = ml->y << FRACBITS; + } + + // Free buffer memory. + // JOSEF: W_ReleaseLumpNum(lump); + + requestedlumpnum = gamelumpnum + ML_SECTORS; +} + +// +// P_LoadSectors +// +void P_LoadSectors(const unsigned char *buf) { + byte *data; + int i; + mapsector_t *ms; + sector_t *ss; + + int lumplen = ((int*)buf)[0]; + numsectors = lumplen / sizeof(mapsector_t); + sectors = IPU_level_malloc(numsectors * sizeof(sector_t)); + memset(sectors, 0, numsectors * sizeof(sector_t)); + + ms = (mapsector_t *)(&buf[sizeof(int)]); + ss = sectors; + for (i = 0; i < numsectors; i++, ss++, ms++) { + ss->floorheight = SHORT(ms->floorheight) << FRACBITS; + ss->ceilingheight = SHORT(ms->ceilingheight) << FRACBITS; + // ss->floorpic = R_FlatNumForName(ms->floorpic); // LATER + // ss->ceilingpic = R_FlatNumForName(ms->ceilingpic); // LATER + ss->lightlevel = SHORT(ms->lightlevel); + ss->special = SHORT(ms->special); + ss->tag = SHORT(ms->tag); + ss->thinglist = NULL; + } + + requestedlumpnum = gamelumpnum + ML_SIDEDEFS; +} + +// +// P_LoadSideDefs +// +void P_LoadSideDefs(const unsigned char *buf) { + byte *data; + int i; + mapsidedef_t *msd; + side_t *sd; + + int lumplen = ((int*)buf)[0]; + numsides = lumplen / sizeof(mapsidedef_t); + sides = IPU_level_malloc(numsides * sizeof(side_t)); + memset(sides, 0, numsides * sizeof(side_t)); + + msd = (mapsidedef_t *)(&buf[sizeof(int)]); + sd = sides; + for (i = 0; i < numsides; i++, msd++, sd++) { + sd->textureoffset = SHORT(msd->textureoffset) << FRACBITS; + sd->rowoffset = SHORT(msd->rowoffset) << FRACBITS; + /* LATER (or, not needed on tile 0) + sd->toptexture = R_TextureNumForName(msd->toptexture); + sd->bottomtexture = R_TextureNumForName(msd->bottomtexture); + sd->midtexture = R_TextureNumForName(msd->midtexture); + */ + sd->sector = §ors[SHORT(msd->sector)]; + } + + requestedlumpnum = gamelumpnum + ML_LINEDEFS; +} + +// +// P_LoadLineDefs +// Also counts secret lines for intermissions. +// +void P_LoadLineDefs(const unsigned char *buf) { + byte *data; + int i; + maplinedef_t *mld; + line_t *ld; + vertex_t *v1; + vertex_t *v2; + + int lumplen = ((int*)buf)[0]; + numlines = lumplen / sizeof(maplinedef_t); + lines = IPU_level_malloc(numlines * sizeof(line_t)); + memset(lines, 0, numlines * sizeof(line_t)); + + mld = (maplinedef_t *)(&buf[sizeof(int)]); + ld = lines; + for (i = 0; i < numlines; i++, mld++, ld++) { + ld->flags = (mld->flags); + ld->special = (mld->special); + ld->tag = (mld->tag); + v1 = ld->v1 = &vertexes[(mld->v1)]; + v2 = ld->v2 = &vertexes[(mld->v2)]; + ld->dx = v2->x - v1->x; + ld->dy = v2->y - v1->y; + + if (!ld->dx) + ld->slopetype = ST_VERTICAL; + else if (!ld->dy) + ld->slopetype = ST_HORIZONTAL; + else { + if (FixedDiv(ld->dy, ld->dx) > 0) + ld->slopetype = ST_POSITIVE; + else + ld->slopetype = ST_NEGATIVE; + } + + if (v1->x < v2->x) { + ld->bbox[BOXLEFT] = v1->x; + ld->bbox[BOXRIGHT] = v2->x; + } else { + ld->bbox[BOXLEFT] = v2->x; + ld->bbox[BOXRIGHT] = v1->x; + } + + if (v1->y < v2->y) { + ld->bbox[BOXBOTTOM] = v1->y; + ld->bbox[BOXTOP] = v2->y; + } else { + ld->bbox[BOXBOTTOM] = v2->y; + ld->bbox[BOXTOP] = v1->y; + } + + ld->sidenum[0] = SHORT(mld->sidenum[0]); + ld->sidenum[1] = SHORT(mld->sidenum[1]); + + if (ld->sidenum[0] != -1) + ld->frontsector = sides[ld->sidenum[0]].sector; + else + ld->frontsector = 0; + + if (ld->sidenum[1] != -1) + ld->backsector = sides[ld->sidenum[1]].sector; + else + ld->backsector = 0; + } + + // msd = (mapsidedef_t *)(&buf[sizeof(int)]); + ipuprint("numlines: "); + ipuprintnum(numlines); + ipuprint(", sidenum0: "); + ipuprintnum(lines[0].sidenum[1]); + ipuprint(", sidenum-1: "); + ipuprintnum(lines[numlines-1].sidenum[1]); + ipuprint(", dx-1: "); + ipuprintnum(lines[numlines-1].dx); + ipuprint("\n"); + + requestedlumpnum = gamelumpnum + ML_SSECTORS; +} + +// +// P_LoadSubsectors +// +void P_LoadSubsectors(const unsigned char *buf) { + byte *data; + int i; + mapsubsector_t *ms; + subsector_t *ss; + + int lumplen = ((int*)buf)[0]; + numsubsectors = lumplen / sizeof(mapsubsector_t); + subsectors = IPU_level_malloc(numsubsectors * sizeof(subsector_t)); + + ms = (mapsubsector_t *)(&buf[sizeof(int)]); + memset(subsectors, 0, numsubsectors * sizeof(subsector_t)); + ss = subsectors; + + for (i = 0; i < numsubsectors; i++, ss++, ms++) { + ss->numlines = SHORT(ms->numsegs); + ss->firstline = SHORT(ms->firstseg); + } + + requestedlumpnum = gamelumpnum + ML_NODES; +} + +// +// P_SetupLevel +// +void P_SetupLevel_pt0(void) { + int i; + + /* LATER + totalkills = totalitems = totalsecret = wminfo.maxfrags = 0; + wminfo.partime = 180; + for (i = 0; i < MAXPLAYERS; i++) { + players[i].killcount = players[i].secretcount = players[i].itemcount = 0; + } + + // Initial height of PointOfView + // will be set by player think. + players[consoleplayer].viewz = 1; + + // Make sure all sounds are stopped before Z_FreeTags. + S_Start(); + */ + + // JOSEF, replaced; Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1); + IPU_level_free(); // LATER: free other tags, here not just level + + /* LATER + // UNUSED W_Profile (); + P_InitThinkers(); + + // if working with a devlopment map, reload it + // W_Reload(); // JOSEF: disabled + + // find map name + lumpname[0] = 'E'; + lumpname[1] = '0' + gameepisode; + lumpname[2] = 'M'; + lumpname[3] = '0' + gamemap; + lumpname[4] = '\0'; + + lumpnum = W_GetNumForName(lumpname); + + */ + leveltime = 0; + + reset_ipuprint(); + ipuprint("Map starts at lump "); ipuprintnum(gamelumpnum); ipuprint("\n"); + + // JOSEF: Lumpnum for P_LoadBlockMap + requestedlumpnum = gamelumpnum + ML_BLOCKMAP; + return; + + // note: most of this ordering is important + /* LATER + P_LoadBlockMap(lumpnum + ML_BLOCKMAP); + P_LoadVertexes(lumpnum + ML_VERTEXES); + P_LoadSectors(lumpnum + ML_SECTORS); + P_LoadSideDefs(lumpnum + ML_SIDEDEFS); + + P_LoadLineDefs(lumpnum + ML_LINEDEFS); + P_LoadSubsectors(lumpnum + ML_SSECTORS); + P_LoadNodes(lumpnum + ML_NODES); + P_LoadSegs(lumpnum + ML_SEGS); + */ + + /* LATER + P_GroupLines(); + P_LoadReject(lumpnum + ML_REJECT); + + bodyqueslot = 0; + deathmatch_p = deathmatchstarts; + P_LoadThings(lumpnum + ML_THINGS); + + // if deathmatch, randomly spawn the active players + if (deathmatch) { + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) { + players[i].mo = NULL; + G_DeathMatchSpawnPlayer(i); + } + } + + // clear special respawning que + iquehead = iquetail = 0; + + // set up world state + P_SpawnSpecials(); + + // build subsector connect matrix + // UNUSED P_ConnectSubsectors (); + + // preload graphics + if (precache) + R_PrecacheLevel(); + + */ +} + + + +// +// P_LoadBlockMap +// +void P_LoadBlockMap(const unsigned char *buf) { + int i; + int count; + int lumplen; + + lumplen = ((int*)buf)[0]; + blockmaplump = IPU_level_malloc(lumplen); + memcpy(blockmaplump, &buf[4], lumplen); + blockmap = blockmaplump + 4; + + // Swap all short integers to native byte ordering. + // JOSEF: assume endianness of SHORT is fine + + // Read the header + + bmaporgx = blockmaplump[0] << FRACBITS; + bmaporgy = blockmaplump[1] << FRACBITS; + bmapwidth = blockmaplump[2]; + bmapheight = blockmaplump[3]; + + // Clear out mobj chains + + count = sizeof(*blocklinks) * bmapwidth * bmapheight; + blocklinks = IPU_level_malloc(count); + memset(blocklinks, 0, count); + + // JOSEF: next lump to load + requestedlumpnum = gamelumpnum + ML_VERTEXES; +} \ No newline at end of file diff --git a/client/src/ipu/p_setup.h b/client/src/ipu/p_setup.h new file mode 100644 index 0000000..a30febc --- /dev/null +++ b/client/src/ipu/p_setup.h @@ -0,0 +1,29 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Setup a game, startup stuff. +// + +#ifndef __P_SETUP__ +#define __P_SETUP__ + + +// NOT called by W_Ticker. Fixme. +void P_SetupLevel(); + +// Called by startup code. +void P_Init(void); + +#endif diff --git a/client/src/ipu/p_setup_codelets.cpp b/client/src/ipu/p_setup_codelets.cpp new file mode 100644 index 0000000..b09b2c7 --- /dev/null +++ b/client/src/ipu/p_setup_codelets.cpp @@ -0,0 +1,107 @@ +#include + +#include "ipu_transfer.h" + + +extern "C" { + void P_SetupLevel_pt0(void); + void P_LoadBlockMap(const unsigned char *buf); + void P_LoadVertexes(const unsigned char *buf); + void P_LoadSectors(const unsigned char *buf); + void P_LoadSideDefs(const unsigned char *buf); + void P_LoadLineDefs(const unsigned char *buf); + void P_LoadSubsectors(const unsigned char *buf); + void IPU_Setup_UnpackMarkNums(const unsigned char* buf); +}; + + +// --------------- P_Setup ----------------- // + +class P_SetupLevel_pt0_Vertex : public poplar::Vertex { + poplar::Output lumpNum; + public: + bool compute() { + P_SetupLevel_pt0(); + *lumpNum = requestedlumpnum; + return true; + } +}; + + +class P_LoadBlockMap_Vertex : public poplar::Vertex { + poplar::Input> lumpBuf; + poplar::Output lumpNum; + public: + bool compute() { + P_LoadBlockMap(&lumpBuf[0]); + *lumpNum = requestedlumpnum; + return true; + } +}; + +class P_LoadVertexes_Vertex : public poplar::Vertex { + poplar::Input> lumpBuf; + poplar::Output lumpNum; + public: + bool compute() { + P_LoadVertexes(&lumpBuf[0]); + *lumpNum = requestedlumpnum; + return true; + } +}; + +class P_LoadSectors_Vertex : public poplar::Vertex { + poplar::Input> lumpBuf; + poplar::Output lumpNum; + public: + bool compute() { + P_LoadSectors(&lumpBuf[0]); + *lumpNum = requestedlumpnum; + return true; + } +}; + +class P_LoadSideDefs_Vertex : public poplar::Vertex { + poplar::Input> lumpBuf; + poplar::Output lumpNum; + public: + bool compute() { + P_LoadSideDefs(&lumpBuf[0]); + *lumpNum = requestedlumpnum; + return true; + } +}; + +class P_LoadLineDefs_Vertex : public poplar::Vertex { + poplar::Input> lumpBuf; + poplar::Output lumpNum; + public: + bool compute() { + P_LoadLineDefs(&lumpBuf[0]); + *lumpNum = requestedlumpnum; + return true; + } +}; + + +class P_LoadSubsectors_Vertex : public poplar::Vertex { + poplar::Input> lumpBuf; + poplar::Output lumpNum; + public: + bool compute() { + P_LoadSubsectors(&lumpBuf[0]); + *lumpNum = requestedlumpnum; + return true; + } +}; + +// ------------ IPU_Setup ------------ // + +class IPU_Setup_UnpackMarknumSprites_Vertex : public poplar::Vertex { + poplar::Input> buf; + public: + bool compute() { + IPU_Setup_UnpackMarkNums(&buf[0]); + return true; + } +}; \ No newline at end of file diff --git a/client/src/ipu/p_spec.h b/client/src/ipu/p_spec.h new file mode 100644 index 0000000..57c4f2d --- /dev/null +++ b/client/src/ipu/p_spec.h @@ -0,0 +1,492 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: none +// Implements special effects: +// Texture animation, height or lighting changes +// according to adjacent sectors, respective +// utility functions, etc. +// + +#ifndef __P_SPEC__ +#define __P_SPEC__ + +#include "d_player.h" +#include "d_think.h" +#include "doomtype.h" +#include "m_fixed.h" +#include "p_mobj.h" +#include "r_defs.h" + +// +// End-level timer (-TIMER option) +// +extern boolean levelTimer; +extern int levelTimeCount; + +// Define values for map objects +#define MO_TELEPORTMAN 14 + +// at game start +void P_InitPicAnims(void); + +// at map load +void P_SpawnSpecials(void); + +// every tic +void P_UpdateSpecials(void); + +// when needed +boolean P_UseSpecialLine(mobj_t *thing, line_t *line, int side); + +void P_ShootSpecialLine(mobj_t *thing, line_t *line); + +void P_CrossSpecialLine(int linenum, int side, mobj_t *thing); + +void P_PlayerInSpecialSector(player_t *player); + +int twoSided(int sector, int line); + +sector_t *getSector(int currentSector, int line, int side); + +side_t *getSide(int currentSector, int line, int side); + +fixed_t P_FindLowestFloorSurrounding(sector_t *sec); +fixed_t P_FindHighestFloorSurrounding(sector_t *sec); + +fixed_t P_FindNextHighestFloor(sector_t *sec, int currentheight); + +fixed_t P_FindLowestCeilingSurrounding(sector_t *sec); +fixed_t P_FindHighestCeilingSurrounding(sector_t *sec); + +int P_FindSectorFromLineTag(line_t *line, int start); + +int P_FindMinSurroundingLight(sector_t *sector, int max); + +sector_t *getNextSector(line_t *line, sector_t *sec); + +// +// SPECIAL +// +int EV_DoDonut(line_t *line); + +// +// P_LIGHTS +// +typedef struct { + thinker_t thinker; + sector_t *sector; + int count; + int maxlight; + int minlight; + +} fireflicker_t; + +typedef struct { + thinker_t thinker; + sector_t *sector; + int count; + int maxlight; + int minlight; + int maxtime; + int mintime; + +} lightflash_t; + +typedef struct { + thinker_t thinker; + sector_t *sector; + int count; + int minlight; + int maxlight; + int darktime; + int brighttime; + +} strobe_t; + +typedef struct { + thinker_t thinker; + sector_t *sector; + int minlight; + int maxlight; + int direction; + +} glow_t; + +#define GLOWSPEED 8 +#define STROBEBRIGHT 5 +#define FASTDARK 15 +#define SLOWDARK 35 + +void P_SpawnFireFlicker(sector_t *sector); +void T_LightFlash(lightflash_t *flash); +void P_SpawnLightFlash(sector_t *sector); +void T_StrobeFlash(strobe_t *flash); + +void P_SpawnStrobeFlash(sector_t *sector, int fastOrSlow, int inSync); + +void EV_StartLightStrobing(line_t *line); +void EV_TurnTagLightsOff(line_t *line); + +void EV_LightTurnOn(line_t *line, int bright); + +void T_Glow(glow_t *g); +void P_SpawnGlowingLight(sector_t *sector); + +// +// P_SWITCH +// +typedef struct { + char name1[9]; + char name2[9]; + short episode; + +} switchlist_t; + +typedef enum { + top, + middle, + bottom + +} bwhere_e; + +typedef struct { + line_t *line; + bwhere_e where; + int btexture; + int btimer; + degenmobj_t *soundorg; + +} button_t; + +// max # of wall switches in a level +#define MAXSWITCHES 50 + +// 4 players, 4 buttons each at once, max. +#define MAXBUTTONS 16 + +// 1 second, in ticks. +#define BUTTONTIME 35 + +extern button_t buttonlist[MAXBUTTONS]; + +void P_ChangeSwitchTexture(line_t *line, int useAgain); + +void P_InitSwitchList(void); + +// +// P_PLATS +// +typedef enum { + up, + down, + waiting, + in_stasis + +} plat_e; + +typedef enum { + perpetualRaise, + downWaitUpStay, + raiseAndChange, + raiseToNearestAndChange, + blazeDWUS + +} plattype_e; + +typedef struct { + thinker_t thinker; + sector_t *sector; + fixed_t speed; + fixed_t low; + fixed_t high; + int wait; + int count; + plat_e status; + plat_e oldstatus; + boolean crush; + int tag; + plattype_e type; + +} plat_t; + +#define PLATWAIT 3 +#define PLATSPEED FRACUNIT +#define MAXPLATS 30 + +extern plat_t *activeplats[MAXPLATS]; + +void T_PlatRaise(plat_t *plat); + +int EV_DoPlat(line_t *line, plattype_e type, int amount); + +void P_AddActivePlat(plat_t *plat); +void P_RemoveActivePlat(plat_t *plat); +void EV_StopPlat(line_t *line); +void P_ActivateInStasis(int tag); + +// +// P_DOORS +// +typedef enum { + vld_normal, + vld_close30ThenOpen, + vld_close, + vld_open, + vld_raiseIn5Mins, + vld_blazeRaise, + vld_blazeOpen, + vld_blazeClose + +} vldoor_e; + +typedef struct { + thinker_t thinker; + vldoor_e type; + sector_t *sector; + fixed_t topheight; + fixed_t speed; + + // 1 = up, 0 = waiting at top, -1 = down + int direction; + + // tics to wait at the top + int topwait; + // (keep in case a door going down is reset) + // when it reaches 0, start going down + int topcountdown; + +} vldoor_t; + +#define VDOORSPEED FRACUNIT * 2 +#define VDOORWAIT 150 + +void EV_VerticalDoor(line_t *line, mobj_t *thing); + +int EV_DoDoor(line_t *line, vldoor_e type); + +int EV_DoLockedDoor(line_t *line, vldoor_e type, mobj_t *thing); + +void T_VerticalDoor(vldoor_t *door); +void P_SpawnDoorCloseIn30(sector_t *sec); + +void P_SpawnDoorRaiseIn5Mins(sector_t *sec, int secnum); + +#if 0 // UNUSED +// +// Sliding doors... +// +typedef enum +{ + sd_opening, + sd_waiting, + sd_closing + +} sd_e; + + + +typedef enum +{ + sdt_openOnly, + sdt_closeOnly, + sdt_openAndClose + +} sdt_e; + + + + +typedef struct +{ + thinker_t thinker; + sdt_e type; + line_t* line; + int frame; + int whichDoorIndex; + int timer; + sector_t* frontsector; + sector_t* backsector; + sd_e status; + +} slidedoor_t; + + + +typedef struct +{ + char frontFrame1[9]; + char frontFrame2[9]; + char frontFrame3[9]; + char frontFrame4[9]; + char backFrame1[9]; + char backFrame2[9]; + char backFrame3[9]; + char backFrame4[9]; + +} slidename_t; + + + +typedef struct +{ + int frontFrames[4]; + int backFrames[4]; + +} slideframe_t; + + + +// how many frames of animation +#define SNUMFRAMES 4 + +#define SDOORWAIT 35 * 3 +#define SWAITTICS 4 + +// how many diff. types of anims +#define MAXSLIDEDOORS 5 + +void P_InitSlidingDoorFrames(void); + +void +EV_SlidingDoor +( line_t* line, + mobj_t* thing ); +#endif + +// +// P_CEILNG +// +typedef enum { + lowerToFloor, + raiseToHighest, + lowerAndCrush, + crushAndRaise, + fastCrushAndRaise, + silentCrushAndRaise + +} ceiling_e; + +typedef struct { + thinker_t thinker; + ceiling_e type; + sector_t *sector; + fixed_t bottomheight; + fixed_t topheight; + fixed_t speed; + boolean crush; + + // 1 = up, 0 = waiting, -1 = down + int direction; + + // ID + int tag; + int olddirection; + +} ceiling_t; + +#define CEILSPEED FRACUNIT +#define CEILWAIT 150 +#define MAXCEILINGS 30 + +extern ceiling_t *activeceilings[MAXCEILINGS]; + +int EV_DoCeiling(line_t *line, ceiling_e type); + +void T_MoveCeiling(ceiling_t *ceiling); +void P_AddActiveCeiling(ceiling_t *c); +void P_RemoveActiveCeiling(ceiling_t *c); +int EV_CeilingCrushStop(line_t *line); +void P_ActivateInStasisCeiling(line_t *line); + +// +// P_FLOOR +// +typedef enum { + // lower floor to highest surrounding floor + lowerFloor, + + // lower floor to lowest surrounding floor + lowerFloorToLowest, + + // lower floor to highest surrounding floor VERY FAST + turboLower, + + // raise floor to lowest surrounding CEILING + raiseFloor, + + // raise floor to next highest surrounding floor + raiseFloorToNearest, + + // raise floor to shortest height texture around it + raiseToTexture, + + // lower floor to lowest surrounding floor + // and change floorpic + lowerAndChange, + + raiseFloor24, + raiseFloor24AndChange, + raiseFloorCrush, + + // raise to next highest floor, turbo-speed + raiseFloorTurbo, + donutRaise, + raiseFloor512 + +} floor_e; + +typedef enum { + build8, // slowly build by 8 + turbo16 // quickly build by 16 + +} stair_e; + +typedef struct { + thinker_t thinker; + floor_e type; + boolean crush; + sector_t *sector; + int direction; + int newspecial; + short texture; + fixed_t floordestheight; + fixed_t speed; + +} floormove_t; + +#define FLOORSPEED FRACUNIT + +typedef enum { + ok, + crushed, + pastdest + +} result_e; + +result_e T_MovePlane(sector_t *sector, fixed_t speed, fixed_t dest, + boolean crush, int floorOrCeiling, int direction); + +int EV_BuildStairs(line_t *line, stair_e type); + +int EV_DoFloor(line_t *line, floor_e floortype); + +void T_MoveFloor(floormove_t *floor); + +// +// P_TELEPT +// +int EV_Teleport(line_t *line, int side, mobj_t *thing); + +#endif diff --git a/client/src/ipu/p_tick.c b/client/src/ipu/p_tick.c new file mode 100644 index 0000000..18220b3 --- /dev/null +++ b/client/src/ipu/p_tick.c @@ -0,0 +1,131 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Archiving: SaveGame I/O. +// Thinker, Ticker. +// + +#include "d_player.h" +#include "d_think.h" +#include "doomdef.h" +#include "doomstat.h" +#include "doomtype.h" +#include "p_local.h" +#include "p_spec.h" +// TODO JOSEF #include "z_zone.h" + +int leveltime; + +// +// THINKERS +// All thinkers should be allocated by Z_Malloc +// so they can be operated on uniformly. +// The actual structures will vary in size, +// but the first element must be thinker_t. +// + +// Both the head and tail of the thinker list. +thinker_t thinkercap; + +// +// P_InitThinkers +// +void P_InitThinkers(void) { thinkercap.prev = thinkercap.next = &thinkercap; } + +// +// P_AddThinker +// Adds a new thinker at the end of the list. +// +void P_AddThinker(thinker_t *thinker) { + thinkercap.prev->next = thinker; + thinker->next = &thinkercap; + thinker->prev = thinkercap.prev; + thinkercap.prev = thinker; +} + +// +// P_RemoveThinker +// Deallocation is lazy -- it will not actually be freed +// until its thinking turn comes up. +// +void P_RemoveThinker(thinker_t *thinker) { + // FIXME: NOP. + thinker->function.acv = (actionf_v)(-1); +} + +// +// P_AllocateThinker +// Allocates memory and adds a new thinker at the end of the list. +// +void P_AllocateThinker(thinker_t *thinker) {} + +// +// P_RunThinkers +// + +/* JOSEF TODO + +void P_RunThinkers(void) { + thinker_t *currentthinker, *nextthinker; + + currentthinker = thinkercap.next; + while (currentthinker != &thinkercap) { + if (currentthinker->function.acv == (actionf_v)(-1)) { + // time to remove it + nextthinker = currentthinker->next; + currentthinker->next->prev = currentthinker->prev; + currentthinker->prev->next = currentthinker->next; + Z_Free(currentthinker); + } else { + if (currentthinker->function.acp1) + currentthinker->function.acp1(currentthinker); + nextthinker = currentthinker->next; + } + currentthinker = nextthinker; + } +} +*/ + +// +// P_Ticker +// + +/* JOSEF TODO + +void P_Ticker(void) { + int i; + + // run the tic + if (paused) + return; + + // pause if in menu and at least one tic has been run + if (!netgame && menuactive && !demoplayback && + players[consoleplayer].viewz != 1) { + return; + } + + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) + P_PlayerThink(&players[i]); + + P_RunThinkers(); + P_UpdateSpecials(); + P_RespawnSpecials(); + + // for par times + leveltime++; +} +*/ diff --git a/client/src/ipu/p_tick.h b/client/src/ipu/p_tick.h new file mode 100644 index 0000000..804d86e --- /dev/null +++ b/client/src/ipu/p_tick.h @@ -0,0 +1,27 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// ? +// + +#ifndef __P_TICK__ +#define __P_TICK__ + +// Called by C_Ticker, +// can call G_PlayerExited. +// Carries out all thinking of monsters and players. +void P_Ticker(void); + +#endif diff --git a/client/src/ipu/r_bsp.h b/client/src/ipu/r_bsp.h new file mode 100644 index 0000000..0981680 --- /dev/null +++ b/client/src/ipu/r_bsp.h @@ -0,0 +1,56 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh module, BSP traversal and handling. +// + +#ifndef __R_BSP__ +#define __R_BSP__ + +#include "r_defs.h" + +extern seg_t *curline; +extern side_t *sidedef; +extern line_t *linedef; +extern sector_t *frontsector; +extern sector_t *backsector; + +extern int rw_x; +extern int rw_stopx; + +extern boolean segtextured; + +// false if the back side is the same plane +extern boolean markfloor; +extern boolean markceiling; + +extern boolean skymap; + +extern drawseg_t drawsegs[MAXDRAWSEGS]; +extern drawseg_t *ds_p; + +extern lighttable_t **hscalelight; +extern lighttable_t **vscalelight; +extern lighttable_t **dscalelight; + +typedef void (*drawfunc_t)(int start, int stop); + +// BSP? +void R_ClearClipSegs(void); +void R_ClearDrawSegs(void); + +void R_RenderBSPNode(int bspnum); + +#endif diff --git a/client/src/ipu/r_data.h b/client/src/ipu/r_data.h new file mode 100644 index 0000000..cb9305d --- /dev/null +++ b/client/src/ipu/r_data.h @@ -0,0 +1,42 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh module, data I/O, caching, retrieval of graphics +// by name. +// + +#ifndef __R_DATA__ +#define __R_DATA__ + +#include "doomtype.h" + +// Retrieve column data for span blitting. +byte *R_GetColumn(int tex, int col); + +// I/O, setting up the stuff. +void R_InitData(void); +void R_PrecacheLevel(void); + +// Retrieval. +// Floor/ceiling opaque texture tiles, +// lookup by name. For animation? +int R_FlatNumForName(char *name); + +// Called by P_Ticker for switches and animations, +// returns the texture number for the texture name. +int R_TextureNumForName(char *name); +int R_CheckTextureNumForName(char *name); + +#endif diff --git a/client/src/ipu/r_defs.h b/client/src/ipu/r_defs.h new file mode 100644 index 0000000..979d1bb --- /dev/null +++ b/client/src/ipu/r_defs.h @@ -0,0 +1,390 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh/rendering module, shared data struct definitions. +// + +#ifndef __R_DEFS__ +#define __R_DEFS__ + +// Screenwidth. +#include "doomdef.h" + +// Some more or less basic data types +// we depend on. +#include "m_fixed.h" + +// We rely on the thinker data struct +// to handle sound origins in sectors. +#include "d_think.h" +// SECTORS do store MObjs anyway. +#include "p_mobj.h" + +#include "i_video.h" + +#include "v_patch.h" + +// Silhouette, needed for clipping Segs (mainly) +// and sprites representing things. +#define SIL_NONE 0 +#define SIL_BOTTOM 1 +#define SIL_TOP 2 +#define SIL_BOTH 3 + +#define MAXDRAWSEGS 256 + +// +// INTERNAL MAP TYPES +// used by play and refresh +// + +// +// Your plain vanilla vertex. +// Note: transformed values not buffered locally, +// like some DOOM-alikes ("wt", "WebView") did. +// +typedef struct { + fixed_t x; + fixed_t y; + +} vertex_t; + +// Forward of LineDefs, for Sectors. +struct line_s; + +// Each sector has a degenmobj_t in its center +// for sound origin purposes. +// I suppose this does not handle sound from +// moving objects (doppler), because +// position is prolly just buffered, not +// updated. +typedef struct { + thinker_t thinker; // not used for anything + fixed_t x; + fixed_t y; + fixed_t z; + +} degenmobj_t; + +// +// The SECTORS record, at runtime. +// Stores things/mobjs. +// +typedef struct { + fixed_t floorheight; + fixed_t ceilingheight; + short floorpic; + short ceilingpic; + short lightlevel; + short special; + short tag; + + // 0 = untraversed, 1,2 = sndlines -1 + int soundtraversed; + + // thing that made a sound (or null) + mobj_t *soundtarget; + + // mapblock bounding box for height changes + int blockbox[4]; + + // origin for any sounds played by the sector + degenmobj_t soundorg; + + // if == validcount, already checked + int validcount; + + // list of mobjs in sector + mobj_t *thinglist; + + // thinker_t for reversable actions + void *specialdata; + + int linecount; + struct line_s **lines; // [linecount] size + +} sector_t; + +// +// The SideDef. +// + +typedef struct { + // add this to the calculated texture column + fixed_t textureoffset; + + // add this to the calculated texture top + fixed_t rowoffset; + + // Texture indices. + // We do not maintain names here. + short toptexture; + short bottomtexture; + short midtexture; + + // Sector the SideDef is facing. + sector_t *sector; + +} side_t; + +// +// Move clipping aid for LineDefs. +// +typedef enum { + ST_HORIZONTAL, + ST_VERTICAL, + ST_POSITIVE, + ST_NEGATIVE + +} slopetype_t; + +typedef struct line_s { + // Vertices, from v1 to v2. + vertex_t *v1; + vertex_t *v2; + + // Precalculated v2 - v1 for side checking. + fixed_t dx; + fixed_t dy; + + // Animation related. + short flags; + short special; + short tag; + + // Visual appearance: SideDefs. + // sidenum[1] will be -1 if one sided + short sidenum[2]; + + // Neat. Another bounding box, for the extent + // of the LineDef. + fixed_t bbox[4]; + + // To aid move clipping. + slopetype_t slopetype; + + // Front and back sector. + // Note: redundant? Can be retrieved from SideDefs. + sector_t *frontsector; + sector_t *backsector; + + // if == validcount, already checked + int validcount; + + // thinker_t for reversable actions + void *specialdata; +} line_t; + +// +// A SubSector. +// References a Sector. +// Basically, this is a list of LineSegs, +// indicating the visible walls that define +// (all or some) sides of a convex BSP leaf. +// +typedef struct subsector_s { + sector_t *sector; + short numlines; + short firstline; + +} subsector_t; + +// +// The LineSeg. +// +typedef struct { + vertex_t *v1; + vertex_t *v2; + + fixed_t offset; + + angle_t angle; + + side_t *sidedef; + line_t *linedef; + + // Sector references. + // Could be retrieved from linedef, too. + // backsector is NULL for one sided lines + sector_t *frontsector; + sector_t *backsector; + +} seg_t; + +// +// BSP node. +// +typedef struct { + // Partition line. + fixed_t x; + fixed_t y; + fixed_t dx; + fixed_t dy; + + // Bounding box for each child. + fixed_t bbox[2][4]; + + // If NF_SUBSECTOR its a subsector. + unsigned short children[2]; + +} node_t; + +// PC direct to screen pointers +// B UNUSED - keep till detailshift in r_draw.c resolved +// extern byte* destview; +// extern byte* destscreen; + +// +// OTHER TYPES +// + +// This could be wider for >8 bit display. +// Indeed, true color support is posibble +// precalculating 24bpp lightmap/colormap LUT. +// from darkening PLAYPAL to all black. +// Could even us emore than 32 levels. +typedef pixel_t lighttable_t; + +// +// ? +// +typedef struct drawseg_s { + seg_t *curline; + int x1; + int x2; + + fixed_t scale1; + fixed_t scale2; + fixed_t scalestep; + + // 0=none, 1=bottom, 2=top, 3=both + int silhouette; + + // do not clip sprites above this + fixed_t bsilheight; + + // do not clip sprites below this + fixed_t tsilheight; + + // Pointers to lists for sprite clipping, + // all three adjusted so [x1] is first value. + short *sprtopclip; + short *sprbottomclip; + short *maskedtexturecol; + +} drawseg_t; + +// A vissprite_t is a thing +// that will be drawn during a refresh. +// I.e. a sprite object that is partly visible. +typedef struct vissprite_s { + // Doubly linked list. + struct vissprite_s *prev; + struct vissprite_s *next; + + int x1; + int x2; + + // for line side calculation + fixed_t gx; + fixed_t gy; + + // global bottom / top for silhouette clipping + fixed_t gz; + fixed_t gzt; + + // horizontal position of x1 + fixed_t startfrac; + + fixed_t scale; + + // negative if flipped + fixed_t xiscale; + + fixed_t texturemid; + int patch; + + // for color translation and shadow draw, + // maxbright frames as well + lighttable_t *colormap; + + int mobjflags; + +} vissprite_t; + +// +// Sprites are patches with a special naming convention +// so they can be recognized by R_InitSprites. +// The base name is NNNNFx or NNNNFxFx, with +// x indicating the rotation, x = 0, 1-7. +// The sprite and frame specified by a thing_t +// is range checked at run time. +// A sprite is a patch_t that is assumed to represent +// a three dimensional object and may have multiple +// rotations pre drawn. +// Horizontal flipping is used to save space, +// thus NNNNF2F5 defines a mirrored patch. +// Some sprites will only have one picture used +// for all views: NNNNF0 +// +typedef struct { + // If false use 0 for any position. + // Note: as eight entries are available, + // we might as well insert the same name eight times. + boolean rotate; + + // Lump to use for view angles 0-7. + short lump[8]; + + // Flip bit (1 = flip) to use for view angles 0-7. + byte flip[8]; + +} spriteframe_t; + +// +// A sprite definition: +// a number of animation frames. +// +typedef struct { + int numframes; + spriteframe_t *spriteframes; + +} spritedef_t; + +// +// Now what is a visplane, anyway? +// +typedef struct { + fixed_t height; + int picnum; + int lightlevel; + int minx; + int maxx; + + // leave pads for [minx-1]/[maxx+1] + + byte pad1; + // Here lies the rub for all + // dynamic resize/change of resolution. + byte top[SCREENWIDTH]; + byte pad2; + byte pad3; + // See above. + byte bottom[SCREENWIDTH]; + byte pad4; + +} visplane_t; + +#endif diff --git a/client/src/ipu/r_draw.h b/client/src/ipu/r_draw.h new file mode 100644 index 0000000..35358e8 --- /dev/null +++ b/client/src/ipu/r_draw.h @@ -0,0 +1,90 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + +#ifndef __R_DRAW__ +#define __R_DRAW__ + +#include "doomtype.h" +#include "m_fixed.h" +#include "r_defs.h" + +extern lighttable_t *dc_colormap; +extern int dc_x; +extern int dc_yl; +extern int dc_yh; +extern fixed_t dc_iscale; +extern fixed_t dc_texturemid; + +// first pixel in a column +extern byte *dc_source; + +// The span blitting interface. +// Hook in assembler or system specific BLT +// here. +void R_DrawColumn(void); +void R_DrawColumnLow(void); + +// The Spectre/Invisibility effect. +void R_DrawFuzzColumn(void); +void R_DrawFuzzColumnLow(void); + +// Draw with color translation tables, +// for player sprite rendering, +// Green/Red/Blue/Indigo shirts. +void R_DrawTranslatedColumn(void); +void R_DrawTranslatedColumnLow(void); + +void R_VideoErase(unsigned ofs, int count); + +extern int ds_y; +extern int ds_x1; +extern int ds_x2; + +extern lighttable_t *ds_colormap; + +extern fixed_t ds_xfrac; +extern fixed_t ds_yfrac; +extern fixed_t ds_xstep; +extern fixed_t ds_ystep; + +// start of a 64*64 tile image +extern byte *ds_source; + +extern byte *translationtables; +extern byte *dc_translation; + +// Span blitting for rows, floor/ceiling. +// No Sepctre effect needed. +void R_DrawSpan(void); + +// Low resolution mode, 160x200? +void R_DrawSpanLow(void); + +void R_InitBuffer(int width, int height); + +// Initialize color translation tables, +// for player rendering etc. +void R_InitTranslationTables(void); + +// Rendering function. +void R_FillBackScreen(void); + +// If the view size is not full screen, draws a border around it. +void R_DrawViewBorder(void); + +#endif diff --git a/client/src/ipu/r_local.h b/client/src/ipu/r_local.h new file mode 100644 index 0000000..5e43cd1 --- /dev/null +++ b/client/src/ipu/r_local.h @@ -0,0 +1,43 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh (R_*) module, global header. +// All the rendering/drawing stuff is here. +// + +#ifndef __R_LOCAL__ +#define __R_LOCAL__ + +// Binary Angles, sine/cosine/atan lookups. +#include "tables.h" + +// Screen size related parameters. +#include "doomdef.h" + +// Include the refresh/render data structs. +#include "r_data.h" + +// +// Separate header file for each module. +// +#include "r_bsp.h" +#include "r_data.h" +#include "r_draw.h" +#include "r_main.h" +#include "r_plane.h" +#include "r_segs.h" +#include "r_things.h" + +#endif // __R_LOCAL__ diff --git a/client/src/ipu/r_main.h b/client/src/ipu/r_main.h new file mode 100644 index 0000000..a5ab94a --- /dev/null +++ b/client/src/ipu/r_main.h @@ -0,0 +1,122 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + +#ifndef __R_MAIN__ +#define __R_MAIN__ + +#include "d_player.h" +#include "m_fixed.h" +#include "r_defs.h" +#include "tables.h" + +// +// POV related. +// +extern fixed_t viewcos; +extern fixed_t viewsin; + +extern int viewwindowx; +extern int viewwindowy; + +extern int centerx; +extern int centery; + +extern fixed_t centerxfrac; +extern fixed_t centeryfrac; +extern fixed_t projection; + +extern int validcount; + +extern int linecount; +extern int loopcount; + +// +// Lighting LUT. +// Used for z-depth cuing per column/row, +// and other lighting effects (sector ambient, flash). +// + +// Lighting constants. +// Now why not 32 levels here? +#define LIGHTLEVELS 16 +#define LIGHTSEGSHIFT 4 + +#define MAXLIGHTSCALE 48 +#define LIGHTSCALESHIFT 12 +#define MAXLIGHTZ 128 +#define LIGHTZSHIFT 20 + +extern lighttable_t *scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; +extern lighttable_t *scalelightfixed[MAXLIGHTSCALE]; +extern lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ]; + +extern int extralight; +extern lighttable_t *fixedcolormap; + +// Number of diminishing brightness levels. +// There a 0-31, i.e. 32 LUT in the COLORMAP lump. +#define NUMCOLORMAPS 32 + +// Blocky/low detail mode. +// B remove this? +// 0 = high, 1 = low +extern int detailshift; + +// +// Function pointers to switch refresh/drawing functions. +// Used to select shadow mode etc. +// +extern void (*colfunc)(void); +extern void (*transcolfunc)(void); +extern void (*basecolfunc)(void); +extern void (*fuzzcolfunc)(void); +// No shadow effects on floors. +extern void (*spanfunc)(void); + +// +// Utility functions. +int R_PointOnSide(fixed_t x, fixed_t y, node_t *node); + +int R_PointOnSegSide(fixed_t x, fixed_t y, seg_t *line); + +angle_t R_PointToAngle(fixed_t x, fixed_t y); + +angle_t R_PointToAngle2(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2); + +fixed_t R_PointToDist(fixed_t x, fixed_t y); + +fixed_t R_ScaleFromGlobalAngle(angle_t visangle); + +subsector_t *R_PointInSubsector(fixed_t x, fixed_t y); + +void R_AddPointToBox(int x, int y, fixed_t *box); + +// +// REFRESH - the actual rendering functions. +// + +// Called by G_Drawer. +void R_RenderPlayerView(player_t *player); + +// Called by startup code. +void R_Init(void); + +// Called by M_Responder. +void R_SetViewSize(int blocks, int detail); + +#endif diff --git a/client/src/ipu/r_plane.h b/client/src/ipu/r_plane.h new file mode 100644 index 0000000..2f8ff90 --- /dev/null +++ b/client/src/ipu/r_plane.h @@ -0,0 +1,53 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh, visplane stuff (floor, ceilings). +// + +#ifndef __R_PLANE__ +#define __R_PLANE__ + +#include "i_video.h" +#include "m_fixed.h" +#include "r_defs.h" + +// Visplane related. +extern short *lastopening; + +typedef void (*planefunction_t)(int top, int bottom); + +extern planefunction_t floorfunc; +extern planefunction_t ceilingfunc_t; + +extern short floorclip[SCREENWIDTH]; +extern short ceilingclip[SCREENWIDTH]; + +extern fixed_t yslope[SCREENHEIGHT]; +extern fixed_t distscale[SCREENWIDTH]; + +void R_InitPlanes(void); +void R_ClearPlanes(void); + +void R_MapPlane(int y, int x1, int x2); + +void R_MakeSpans(int x, int t1, int b1, int t2, int b2); + +void R_DrawPlanes(void); + +visplane_t *R_FindPlane(fixed_t height, int picnum, int lightlevel); + +visplane_t *R_CheckPlane(visplane_t *pl, int start, int stop); + +#endif diff --git a/client/src/ipu/r_segs.h b/client/src/ipu/r_segs.h new file mode 100644 index 0000000..b22e539 --- /dev/null +++ b/client/src/ipu/r_segs.h @@ -0,0 +1,26 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh module, drawing LineSegs from BSP. +// + +#ifndef __R_SEGS__ +#define __R_SEGS__ + +#include "r_defs.h" + +void R_RenderMaskedSegRange(drawseg_t *ds, int x1, int x2); + +#endif diff --git a/client/src/ipu/r_sky.h b/client/src/ipu/r_sky.h new file mode 100644 index 0000000..0f1ca03 --- /dev/null +++ b/client/src/ipu/r_sky.h @@ -0,0 +1,34 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Sky rendering. +// + +#ifndef __R_SKY__ +#define __R_SKY__ + +// SKY, store the number for name. +#define SKYFLATNAME "F_SKY1" + +// The sky map is 256*128*4 maps. +#define ANGLETOSKYSHIFT 22 + +extern int skytexture; +extern int skytexturemid; + +// Called whenever the view size changes. +void R_InitSkyMap(void); + +#endif diff --git a/client/src/ipu/r_state.h b/client/src/ipu/r_state.h new file mode 100644 index 0000000..4c2e5be --- /dev/null +++ b/client/src/ipu/r_state.h @@ -0,0 +1,113 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh/render internal state variables (global). +// + +#ifndef __R_STATE__ +#define __R_STATE__ + +// Need data structure definitions. +#include "d_player.h" +#include "r_data.h" + +// +// Refresh internal data structures, +// for rendering. +// + +// needed for texture pegging +extern fixed_t *textureheight; + +// needed for pre rendering (fracs) +extern fixed_t *spritewidth; + +extern fixed_t *spriteoffset; +extern fixed_t *spritetopoffset; + +extern lighttable_t *colormaps; + +extern int viewwidth; +extern int scaledviewwidth; +extern int viewheight; + +extern int firstflat; + +// for global animation +extern int *flattranslation; +extern int *texturetranslation; + +// Sprite.... +extern int firstspritelump; +extern int lastspritelump; +extern int numspritelumps; + +// +// Lookup tables for map data. +// +extern int numsprites; +extern spritedef_t *sprites; + +extern int numvertexes; +extern vertex_t *vertexes; + +extern int numsegs; +extern seg_t *segs; + +extern int numsectors; +extern sector_t *sectors; + +extern int numsubsectors; +extern subsector_t *subsectors; + +extern int numnodes; +extern node_t *nodes; + +extern int numlines; +extern line_t *lines; + +extern int numsides; +extern side_t *sides; + +// +// POV data. +// +extern fixed_t viewx; +extern fixed_t viewy; +extern fixed_t viewz; + +extern angle_t viewangle; +extern player_t *viewplayer; + +// ? +extern angle_t clipangle; + +extern int viewangletox[FINEANGLES / 2]; +extern angle_t xtoviewangle[SCREENWIDTH + 1]; +// extern fixed_t finetangent[FINEANGLES/2]; + +extern fixed_t rw_distance; +extern angle_t rw_normalangle; + +// angle to line origin +extern int rw_angle1; + +// Segs count? +extern int sscount; + +extern visplane_t *floorplane; +extern visplane_t *ceilingplane; + +#endif diff --git a/client/src/ipu/r_things.h b/client/src/ipu/r_things.h new file mode 100644 index 0000000..89ad81f --- /dev/null +++ b/client/src/ipu/r_things.h @@ -0,0 +1,60 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Rendering of moving objects, sprites. +// + +#ifndef __R_THINGS__ +#define __R_THINGS__ + +#include "i_video.h" +#include "m_fixed.h" +#include "r_defs.h" +#include "v_patch.h" + +#define MAXVISSPRITES 128 + +extern vissprite_t vissprites[MAXVISSPRITES]; +extern vissprite_t *vissprite_p; +extern vissprite_t vsprsortedhead; + +// Constant arrays used for psprite clipping +// and initializing clipping. +extern short negonearray[SCREENWIDTH]; +extern short screenheightarray[SCREENWIDTH]; + +// vars for R_DrawMaskedColumn +extern short *mfloorclip; +extern short *mceilingclip; +extern fixed_t spryscale; +extern fixed_t sprtopscreen; + +extern fixed_t pspritescale; +extern fixed_t pspriteiscale; + +void R_DrawMaskedColumn(column_t *column); + +void R_SortVisSprites(void); + +void R_AddSprites(sector_t *sec); +void R_AddPSprites(void); +void R_DrawSprites(void); +void R_InitSprites(char **namelist); +void R_ClearSprites(void); +void R_DrawMasked(void); + +void R_ClipVisSprite(vissprite_t *vis, int xl, int xh); + +#endif diff --git a/client/src/ipu/s_sound.h b/client/src/ipu/s_sound.h new file mode 100644 index 0000000..cafd723 --- /dev/null +++ b/client/src/ipu/s_sound.h @@ -0,0 +1,82 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// The not so system specific sound interface. +// + +#ifndef __S_SOUND__ +#define __S_SOUND__ + +#include "doomtype.h" +#include "p_mobj.h" + +// +// Initializes sound stuff, including volume +// Sets channels, SFX and music volume, +// allocates channel buffer, sets S_sfx lookup. +// + +void S_Init(int sfxVolume, int musicVolume); + +// Shut down sound + +void S_Shutdown(void); + +// +// Per level startup code. +// Kills playing sounds at start of level, +// determines music if any, changes music. +// + +void S_Start(void); + +// +// Start sound for thing at +// using from sounds.h +// + +void S_StartSound(void *origin, int sound_id); + +// Stop sound for thing at +void S_StopSound(mobj_t *origin); + +// Start music using from sounds.h +void S_StartMusic(int music_id); + +// Start music using from sounds.h, +// and set whether looping +void S_ChangeMusic(int music_id, int looping); + +// query if music is playing +boolean S_MusicPlaying(void); + +// Stops the music fer sure. +void S_StopMusic(void); + +// Stop and resume music, during game PAUSE. +void S_PauseSound(void); +void S_ResumeSound(void); + +// +// Updates music & sounds +// +void S_UpdateSounds(mobj_t *listener); + +void S_SetMusicVolume(int volume); +void S_SetSfxVolume(int volume); + +extern int snd_channels; + +#endif diff --git a/client/src/ipu/sha1.h b/client/src/ipu/sha1.h new file mode 100644 index 0000000..cb7d4e9 --- /dev/null +++ b/client/src/ipu/sha1.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// SHA-1 digest. +// + +#ifndef __SHA1_H__ +#define __SHA1_H__ + +#include +#include + +#include "doomtype.h" + +struct sha1_context_s; + +typedef struct sha1_context_s sha1_context_t; +typedef byte sha1_digest_t[20]; + +struct sha1_context_s { + uint32_t h0,h1,h2,h3,h4; + uint32_t nblocks; + byte buf[64]; + int count; +}; + +void SHA1_Init(sha1_context_t *context); +void SHA1_Update(sha1_context_t *context, byte *buf, size_t len); +void SHA1_Final(sha1_digest_t digest, sha1_context_t *context); +void SHA1_UpdateInt32(sha1_context_t *context, unsigned int val); +void SHA1_UpdateString(sha1_context_t *context, char *str); + +#endif /* #ifndef __SHA1_H__ */ + diff --git a/client/src/ipu/sounds.h b/client/src/ipu/sounds.h new file mode 100644 index 0000000..9f7d741 --- /dev/null +++ b/client/src/ipu/sounds.h @@ -0,0 +1,224 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Created by the sound utility written by Dave Taylor. +// Kept as a sample, DOOM2 sounds. Frozen. +// + +#ifndef __SOUNDS__ +#define __SOUNDS__ + +#include "i_sound.h" + +// the complete set of sound effects +extern sfxinfo_t S_sfx[]; + +// the complete set of music +extern musicinfo_t S_music[]; + +// +// Identifiers for all music in game. +// + +typedef enum { + mus_None, + mus_e1m1, + mus_e1m2, + mus_e1m3, + mus_e1m4, + mus_e1m5, + mus_e1m6, + mus_e1m7, + mus_e1m8, + mus_e1m9, + mus_e2m1, + mus_e2m2, + mus_e2m3, + mus_e2m4, + mus_e2m5, + mus_e2m6, + mus_e2m7, + mus_e2m8, + mus_e2m9, + mus_e3m1, + mus_e3m2, + mus_e3m3, + mus_e3m4, + mus_e3m5, + mus_e3m6, + mus_e3m7, + mus_e3m8, + mus_e3m9, + mus_inter, + mus_intro, + mus_bunny, + mus_victor, + mus_introa, + mus_runnin, + mus_stalks, + mus_countd, + mus_betwee, + mus_doom, + mus_the_da, + mus_shawn, + mus_ddtblu, + mus_in_cit, + mus_dead, + mus_stlks2, + mus_theda2, + mus_doom2, + mus_ddtbl2, + mus_runni2, + mus_dead2, + mus_stlks3, + mus_romero, + mus_shawn2, + mus_messag, + mus_count2, + mus_ddtbl3, + mus_ampie, + mus_theda3, + mus_adrian, + mus_messg2, + mus_romer2, + mus_tense, + mus_shawn3, + mus_openin, + mus_evil, + mus_ultima, + mus_read_m, + mus_dm2ttl, + mus_dm2int, + NUMMUSIC +} musicenum_t; + +// +// Identifiers for all sfx in game. +// + +typedef enum { + sfx_None, + sfx_pistol, + sfx_shotgn, + sfx_sgcock, + sfx_dshtgn, + sfx_dbopn, + sfx_dbcls, + sfx_dbload, + sfx_plasma, + sfx_bfg, + sfx_sawup, + sfx_sawidl, + sfx_sawful, + sfx_sawhit, + sfx_rlaunc, + sfx_rxplod, + sfx_firsht, + sfx_firxpl, + sfx_pstart, + sfx_pstop, + sfx_doropn, + sfx_dorcls, + sfx_stnmov, + sfx_swtchn, + sfx_swtchx, + sfx_plpain, + sfx_dmpain, + sfx_popain, + sfx_vipain, + sfx_mnpain, + sfx_pepain, + sfx_slop, + sfx_itemup, + sfx_wpnup, + sfx_oof, + sfx_telept, + sfx_posit1, + sfx_posit2, + sfx_posit3, + sfx_bgsit1, + sfx_bgsit2, + sfx_sgtsit, + sfx_cacsit, + sfx_brssit, + sfx_cybsit, + sfx_spisit, + sfx_bspsit, + sfx_kntsit, + sfx_vilsit, + sfx_mansit, + sfx_pesit, + sfx_sklatk, + sfx_sgtatk, + sfx_skepch, + sfx_vilatk, + sfx_claw, + sfx_skeswg, + sfx_pldeth, + sfx_pdiehi, + sfx_podth1, + sfx_podth2, + sfx_podth3, + sfx_bgdth1, + sfx_bgdth2, + sfx_sgtdth, + sfx_cacdth, + sfx_skldth, + sfx_brsdth, + sfx_cybdth, + sfx_spidth, + sfx_bspdth, + sfx_vildth, + sfx_kntdth, + sfx_pedth, + sfx_skedth, + sfx_posact, + sfx_bgact, + sfx_dmact, + sfx_bspact, + sfx_bspwlk, + sfx_vilact, + sfx_noway, + sfx_barexp, + sfx_punch, + sfx_hoof, + sfx_metal, + sfx_chgun, + sfx_tink, + sfx_bdopn, + sfx_bdcls, + sfx_itmbk, + sfx_flame, + sfx_flamst, + sfx_getpow, + sfx_bospit, + sfx_boscub, + sfx_bossit, + sfx_bospn, + sfx_bosdth, + sfx_manatk, + sfx_mandth, + sfx_sssit, + sfx_ssdth, + sfx_keenpn, + sfx_keendt, + sfx_skeact, + sfx_skesit, + sfx_skeatk, + sfx_radio, + NUMSFX +} sfxenum_t; + +#endif diff --git a/client/src/ipu/st_lib.h b/client/src/ipu/st_lib.h new file mode 100644 index 0000000..d5a8625 --- /dev/null +++ b/client/src/ipu/st_lib.h @@ -0,0 +1,151 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// The status bar widget code. +// + +#ifndef __STLIB__ +#define __STLIB__ + +#include "doomtype.h" +// We are referring to patches. +#include "v_patch.h" + +// +// Typedefs of widgets +// + +// Number widget + +typedef struct { + // upper right-hand corner + // of the number (right-justified) + int x; + int y; + + // max # of digits in number + int width; + + // last number value + int oldnum; + + // pointer to current value + int *num; + + // pointer to boolean stating + // whether to update number + boolean *on; + + // list of patches for 0-9 + patch_t **p; + + // user data + int data; + +} st_number_t; + +// Percent widget ("child" of number widget, +// or, more precisely, contains a number widget.) +typedef struct { + // number information + st_number_t n; + + // percent sign graphic + patch_t *p; + +} st_percent_t; + +// Multiple Icon widget +typedef struct { + // center-justified location of icons + int x; + int y; + + // last icon number + int oldinum; + + // pointer to current icon + int *inum; + + // pointer to boolean stating + // whether to update icon + boolean *on; + + // list of icons + patch_t **p; + + // user data + int data; + +} st_multicon_t; + +// Binary Icon widget + +typedef struct { + // center-justified location of icon + int x; + int y; + + // last icon value + boolean oldval; + + // pointer to current icon status + boolean *val; + + // pointer to boolean + // stating whether to update icon + boolean *on; + + patch_t *p; // icon + int data; // user data + +} st_binicon_t; + +// +// Widget creation, access, and update routines +// + +// Initializes widget library. +// More precisely, initialize STMINUS, +// everything else is done somewhere else. +// +void STlib_init(void); + +// Number widget routines +void STlib_initNum(st_number_t *n, int x, int y, patch_t **pl, int *num, + boolean *on, int width); + +void STlib_updateNum(st_number_t *n, boolean refresh); + +// Percent widget routines +void STlib_initPercent(st_percent_t *p, int x, int y, patch_t **pl, int *num, + boolean *on, patch_t *percent); + +void STlib_updatePercent(st_percent_t *per, int refresh); + +// Multiple Icon widget routines +void STlib_initMultIcon(st_multicon_t *mi, int x, int y, patch_t **il, + int *inum, boolean *on); + +void STlib_updateMultIcon(st_multicon_t *mi, boolean refresh); + +// Binary Icon widget routines + +void STlib_initBinIcon(st_binicon_t *b, int x, int y, patch_t *i, boolean *val, + boolean *on); + +void STlib_updateBinIcon(st_binicon_t *bi, boolean refresh); + +#endif diff --git a/client/src/ipu/st_stuff.h b/client/src/ipu/st_stuff.h new file mode 100644 index 0000000..ca4ad84 --- /dev/null +++ b/client/src/ipu/st_stuff.h @@ -0,0 +1,81 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Status bar code. +// Does the face/direction indicator animatin. +// Does palette indicators as well (red pain/berserk, bright pickup) +// + +#ifndef __STSTUFF_H__ +#define __STSTUFF_H__ + +#include "d_event.h" +#include "doomtype.h" +#include "i_video.h" +#include "m_cheat.h" + +// Size of statusbar. +// Now sensitive for scaling. +#define ST_HEIGHT 32 +#define ST_WIDTH SCREENWIDTH +#define ST_Y (SCREENHEIGHT - ST_HEIGHT) + +// +// STATUS BAR +// + +// Called by main loop. +boolean ST_Responder(event_t *ev); + +// Called by main loop. +void ST_Ticker(void); + +// Called by main loop. +void ST_Drawer(boolean fullscreen, boolean refresh); + +// Called when the console player is spawned on each level. +void ST_Start(void); + +// Called by startup code. +void ST_Init(void); + +// States for status bar code. +typedef enum { + AutomapState, + FirstPersonState + +} st_stateenum_t; + +// States for the chat code. +typedef enum { + StartChatState, + WaitDestState, + GetChatState + +} st_chatstateenum_t; + +extern byte *st_backing_screen; +extern cheatseq_t cheat_mus; +extern cheatseq_t cheat_god; +extern cheatseq_t cheat_ammo; +extern cheatseq_t cheat_ammonokey; +extern cheatseq_t cheat_noclip; +extern cheatseq_t cheat_commercial_noclip; +extern cheatseq_t cheat_powerup[7]; +extern cheatseq_t cheat_choppers; +extern cheatseq_t cheat_clev; +extern cheatseq_t cheat_mypos; + +#endif diff --git a/client/src/ipu/statdump.h b/client/src/ipu/statdump.h new file mode 100644 index 0000000..e75719c --- /dev/null +++ b/client/src/ipu/statdump.h @@ -0,0 +1,25 @@ +/* + +Copyright(C) 2005-2014 Simon Howard + +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. + +*/ + +#ifndef DOOM_STATDUMP_H +#define DOOM_STATDUMP_H + +#include "d_player.h" + +void StatCopy(wbstartstruct_t *stats); +void StatDump(void); + +#endif /* #ifndef DOOM_STATDUMP_H */ diff --git a/client/src/ipu/tables.c b/client/src/ipu/tables.c new file mode 100644 index 0000000..698ae98 --- /dev/null +++ b/client/src/ipu/tables.c @@ -0,0 +1,2227 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Lookup tables. +// Do not try to look them up :-). +// In the order of appearance: +// +// int finetangent[4096] - Tangens LUT. +// Should work with BAM fairly well (12 of 16bit, +// effectively, by shifting). +// +// int finesine[10240] - Sine lookup. +// Guess what, serves as cosine, too. +// Remarkable thing is, how to use BAMs with this? +// +// int tantoangle[2049] - ArcTan LUT, +// maps tan(angle) to angle fast. Gotta search. +// +// + +#include "tables.h" + +// to get a global angle from cartesian coordinates, the coordinates are +// flipped until they are in the first octant of the coordinate system, then +// the y (<=x) is scaled and divided by x to get a tangent (slope) value +// which is looked up in the tantoangle[] table. The +1 size is to handle +// the case when x==y without additional checking. + +int SlopeDiv(unsigned int num, unsigned int den) +{ + unsigned ans; + + if (den < 512) + { + return SLOPERANGE; + } + else + { + ans = (num << 3) / (den >> 8); + + if (ans <= SLOPERANGE) + { + return ans; + } + else + { + return SLOPERANGE; + } + } +} + +const fixed_t finetangent[4096] = +{ + -170910304,-56965752,-34178904,-24413316,-18988036,-15535599,-13145455,-11392683, + -10052327,-8994149,-8137527,-7429880,-6835455,-6329090,-5892567,-5512368, + -5178251,-4882318,-4618375,-4381502,-4167737,-3973855,-3797206,-3635590, + -3487165,-3350381,-3223918,-3106651,-2997613,-2895966,-2800983,-2712030, + -2628549,-2550052,-2476104,-2406322,-2340362,-2277919,-2218719,-2162516, + -2109087,-2058233,-2009771,-1963536,-1919378,-1877161,-1836758,-1798063, + -1760956,-1725348,-1691149,-1658278,-1626658,-1596220,-1566898,-1538632, + -1511367,-1485049,-1459630,-1435065,-1411312,-1388330,-1366084,-1344537, + -1323658,-1303416,-1283783,-1264730,-1246234,-1228269,-1210813,-1193846, + -1177345,-1161294,-1145673,-1130465,-1115654,-1101225,-1087164,-1073455, + -1060087,-1047046,-1034322,-1021901,-1009774,-997931,-986361,-975054, + -964003,-953199,-942633,-932298,-922186,-912289,-902602,-893117, + -883829,-874730,-865817,-857081,-848520,-840127,-831898,-823827, + -815910,-808143,-800521,-793041,-785699,-778490,-771411,-764460, + -757631,-750922,-744331,-737853,-731486,-725227,-719074,-713023, + -707072,-701219,-695462,-689797,-684223,-678737,-673338,-668024, + -662792,-657640,-652568,-647572,-642651,-637803,-633028,-628323, + -623686,-619117,-614613,-610174,-605798,-601483,-597229,-593033, + -588896,-584815,-580789,-576818,-572901,-569035,-565221,-561456, + -557741,-554074,-550455,-546881,-543354,-539870,-536431,-533034, + -529680,-526366,-523094,-519861,-516667,-513512,-510394,-507313, + -504269,-501261,-498287,-495348,-492443,-489571,-486732,-483925, + -481150,-478406,-475692,-473009,-470355,-467730,-465133,-462565, + -460024,-457511,-455024,-452564,-450129,-447720,-445337,-442978, + -440643,-438332,-436045,-433781,-431540,-429321,-427125,-424951, + -422798,-420666,-418555,-416465,-414395,-412344,-410314,-408303, + -406311,-404338,-402384,-400448,-398530,-396630,-394747,-392882, + -391034,-389202,-387387,-385589,-383807,-382040,-380290,-378555, + -376835,-375130,-373440,-371765,-370105,-368459,-366826,-365208, + -363604,-362013,-360436,-358872,-357321,-355783,-354257,-352744, + -351244,-349756,-348280,-346816,-345364,-343924,-342495,-341078, + -339671,-338276,-336892,-335519,-334157,-332805,-331464,-330133, + -328812,-327502,-326201,-324910,-323629,-322358,-321097,-319844, + -318601,-317368,-316143,-314928,-313721,-312524,-311335,-310154, + -308983,-307819,-306664,-305517,-304379,-303248,-302126,-301011, + -299904,-298805,-297714,-296630,-295554,-294485,-293423,-292369, + -291322,-290282,-289249,-288223,-287204,-286192,-285186,-284188, + -283195,-282210,-281231,-280258,-279292,-278332,-277378,-276430, + -275489,-274553,-273624,-272700,-271782,-270871,-269965,-269064, + -268169,-267280,-266397,-265519,-264646,-263779,-262917,-262060, + -261209,-260363,-259522,-258686,-257855,-257029,-256208,-255392, + -254581,-253774,-252973,-252176,-251384,-250596,-249813,-249035, + -248261,-247492,-246727,-245966,-245210,-244458,-243711,-242967, + -242228,-241493,-240763,-240036,-239314,-238595,-237881,-237170, + -236463,-235761,-235062,-234367,-233676,-232988,-232304,-231624, + -230948,-230275,-229606,-228941,-228279,-227621,-226966,-226314, + -225666,-225022,-224381,-223743,-223108,-222477,-221849,-221225, + -220603,-219985,-219370,-218758,-218149,-217544,-216941,-216341, + -215745,-215151,-214561,-213973,-213389,-212807,-212228,-211652, + -211079,-210509,-209941,-209376,-208815,-208255,-207699,-207145, + -206594,-206045,-205500,-204956,-204416,-203878,-203342,-202809, + -202279,-201751,-201226,-200703,-200182,-199664,-199149,-198636, + -198125,-197616,-197110,-196606,-196105,-195606,-195109,-194614, + -194122,-193631,-193143,-192658,-192174,-191693,-191213,-190736, + -190261,-189789,-189318,-188849,-188382,-187918,-187455,-186995, + -186536,-186080,-185625,-185173,-184722,-184274,-183827,-183382, + -182939,-182498,-182059,-181622,-181186,-180753,-180321,-179891, + -179463,-179037,-178612,-178190,-177769,-177349,-176932,-176516, + -176102,-175690,-175279,-174870,-174463,-174057,-173653,-173251, + -172850,-172451,-172053,-171657,-171263,-170870,-170479,-170089, + -169701,-169315,-168930,-168546,-168164,-167784,-167405,-167027, + -166651,-166277,-165904,-165532,-165162,-164793,-164426,-164060, + -163695,-163332,-162970,-162610,-162251,-161893,-161537,-161182, + -160828,-160476,-160125,-159775,-159427,-159079,-158734,-158389, + -158046,-157704,-157363,-157024,-156686,-156349,-156013,-155678, + -155345,-155013,-154682,-154352,-154024,-153697,-153370,-153045, + -152722,-152399,-152077,-151757,-151438,-151120,-150803,-150487, + -150172,-149859,-149546,-149235,-148924,-148615,-148307,-148000, + -147693,-147388,-147084,-146782,-146480,-146179,-145879,-145580, + -145282,-144986,-144690,-144395,-144101,-143808,-143517,-143226, + -142936,-142647,-142359,-142072,-141786,-141501,-141217,-140934, + -140651,-140370,-140090,-139810,-139532,-139254,-138977,-138701, + -138426,-138152,-137879,-137607,-137335,-137065,-136795,-136526, + -136258,-135991,-135725,-135459,-135195,-134931,-134668,-134406, + -134145,-133884,-133625,-133366,-133108,-132851,-132594,-132339, + -132084,-131830,-131576,-131324,-131072,-130821,-130571,-130322, + -130073,-129825,-129578,-129332,-129086,-128841,-128597,-128353, + -128111,-127869,-127627,-127387,-127147,-126908,-126669,-126432, + -126195,-125959,-125723,-125488,-125254,-125020,-124787,-124555, + -124324,-124093,-123863,-123633,-123404,-123176,-122949,-122722, + -122496,-122270,-122045,-121821,-121597,-121374,-121152,-120930, + -120709,-120489,-120269,-120050,-119831,-119613,-119396,-119179, + -118963,-118747,-118532,-118318,-118104,-117891,-117678,-117466, + -117254,-117044,-116833,-116623,-116414,-116206,-115998,-115790, + -115583,-115377,-115171,-114966,-114761,-114557,-114354,-114151, + -113948,-113746,-113545,-113344,-113143,-112944,-112744,-112546, + -112347,-112150,-111952,-111756,-111560,-111364,-111169,-110974, + -110780,-110586,-110393,-110200,-110008,-109817,-109626,-109435, + -109245,-109055,-108866,-108677,-108489,-108301,-108114,-107927, + -107741,-107555,-107369,-107184,-107000,-106816,-106632,-106449, + -106266,-106084,-105902,-105721,-105540,-105360,-105180,-105000, + -104821,-104643,-104465,-104287,-104109,-103933,-103756,-103580, + -103404,-103229,-103054,-102880,-102706,-102533,-102360,-102187, + -102015,-101843,-101671,-101500,-101330,-101159,-100990,-100820, + -100651,-100482,-100314,-100146,-99979,-99812,-99645,-99479, + -99313,-99148,-98982,-98818,-98653,-98489,-98326,-98163, + -98000,-97837,-97675,-97513,-97352,-97191,-97030,-96870, + -96710,-96551,-96391,-96233,-96074,-95916,-95758,-95601, + -95444,-95287,-95131,-94975,-94819,-94664,-94509,-94354, + -94200,-94046,-93892,-93739,-93586,-93434,-93281,-93129, + -92978,-92826,-92675,-92525,-92375,-92225,-92075,-91926, + -91777,-91628,-91480,-91332,-91184,-91036,-90889,-90742, + -90596,-90450,-90304,-90158,-90013,-89868,-89724,-89579, + -89435,-89292,-89148,-89005,-88862,-88720,-88577,-88435, + -88294,-88152,-88011,-87871,-87730,-87590,-87450,-87310, + -87171,-87032,-86893,-86755,-86616,-86479,-86341,-86204, + -86066,-85930,-85793,-85657,-85521,-85385,-85250,-85114, + -84980,-84845,-84710,-84576,-84443,-84309,-84176,-84043, + -83910,-83777,-83645,-83513,-83381,-83250,-83118,-82987, + -82857,-82726,-82596,-82466,-82336,-82207,-82078,-81949, + -81820,-81691,-81563,-81435,-81307,-81180,-81053,-80925, + -80799,-80672,-80546,-80420,-80294,-80168,-80043,-79918, + -79793,-79668,-79544,-79420,-79296,-79172,-79048,-78925, + -78802,-78679,-78557,-78434,-78312,-78190,-78068,-77947, + -77826,-77705,-77584,-77463,-77343,-77223,-77103,-76983, + -76864,-76744,-76625,-76506,-76388,-76269,-76151,-76033, + -75915,-75797,-75680,-75563,-75446,-75329,-75213,-75096, + -74980,-74864,-74748,-74633,-74517,-74402,-74287,-74172, + -74058,-73944,-73829,-73715,-73602,-73488,-73375,-73262, + -73149,-73036,-72923,-72811,-72699,-72587,-72475,-72363, + -72252,-72140,-72029,-71918,-71808,-71697,-71587,-71477, + -71367,-71257,-71147,-71038,-70929,-70820,-70711,-70602, + -70494,-70385,-70277,-70169,-70061,-69954,-69846,-69739, + -69632,-69525,-69418,-69312,-69205,-69099,-68993,-68887, + -68781,-68676,-68570,-68465,-68360,-68255,-68151,-68046, + -67942,-67837,-67733,-67629,-67526,-67422,-67319,-67216, + -67113,-67010,-66907,-66804,-66702,-66600,-66498,-66396, + -66294,-66192,-66091,-65989,-65888,-65787,-65686,-65586, + -65485,-65385,-65285,-65185,-65085,-64985,-64885,-64786, + -64687,-64587,-64488,-64389,-64291,-64192,-64094,-63996, + -63897,-63799,-63702,-63604,-63506,-63409,-63312,-63215, + -63118,-63021,-62924,-62828,-62731,-62635,-62539,-62443, + -62347,-62251,-62156,-62060,-61965,-61870,-61775,-61680, + -61585,-61491,-61396,-61302,-61208,-61114,-61020,-60926, + -60833,-60739,-60646,-60552,-60459,-60366,-60273,-60181, + -60088,-59996,-59903,-59811,-59719,-59627,-59535,-59444, + -59352,-59261,-59169,-59078,-58987,-58896,-58805,-58715, + -58624,-58534,-58443,-58353,-58263,-58173,-58083,-57994, + -57904,-57815,-57725,-57636,-57547,-57458,-57369,-57281, + -57192,-57104,-57015,-56927,-56839,-56751,-56663,-56575, + -56487,-56400,-56312,-56225,-56138,-56051,-55964,-55877, + -55790,-55704,-55617,-55531,-55444,-55358,-55272,-55186, + -55100,-55015,-54929,-54843,-54758,-54673,-54587,-54502, + -54417,-54333,-54248,-54163,-54079,-53994,-53910,-53826, + -53741,-53657,-53574,-53490,-53406,-53322,-53239,-53156, + -53072,-52989,-52906,-52823,-52740,-52657,-52575,-52492, + -52410,-52327,-52245,-52163,-52081,-51999,-51917,-51835, + -51754,-51672,-51591,-51509,-51428,-51347,-51266,-51185, + -51104,-51023,-50942,-50862,-50781,-50701,-50621,-50540, + -50460,-50380,-50300,-50221,-50141,-50061,-49982,-49902, + -49823,-49744,-49664,-49585,-49506,-49427,-49349,-49270, + -49191,-49113,-49034,-48956,-48878,-48799,-48721,-48643, + -48565,-48488,-48410,-48332,-48255,-48177,-48100,-48022, + -47945,-47868,-47791,-47714,-47637,-47560,-47484,-47407, + -47331,-47254,-47178,-47102,-47025,-46949,-46873,-46797, + -46721,-46646,-46570,-46494,-46419,-46343,-46268,-46193, + -46118,-46042,-45967,-45892,-45818,-45743,-45668,-45593, + -45519,-45444,-45370,-45296,-45221,-45147,-45073,-44999, + -44925,-44851,-44778,-44704,-44630,-44557,-44483,-44410, + -44337,-44263,-44190,-44117,-44044,-43971,-43898,-43826, + -43753,-43680,-43608,-43535,-43463,-43390,-43318,-43246, + -43174,-43102,-43030,-42958,-42886,-42814,-42743,-42671, + -42600,-42528,-42457,-42385,-42314,-42243,-42172,-42101, + -42030,-41959,-41888,-41817,-41747,-41676,-41605,-41535, + -41465,-41394,-41324,-41254,-41184,-41113,-41043,-40973, + -40904,-40834,-40764,-40694,-40625,-40555,-40486,-40416, + -40347,-40278,-40208,-40139,-40070,-40001,-39932,-39863, + -39794,-39726,-39657,-39588,-39520,-39451,-39383,-39314, + -39246,-39178,-39110,-39042,-38973,-38905,-38837,-38770, + -38702,-38634,-38566,-38499,-38431,-38364,-38296,-38229, + -38161,-38094,-38027,-37960,-37893,-37826,-37759,-37692, + -37625,-37558,-37491,-37425,-37358,-37291,-37225,-37158, + -37092,-37026,-36959,-36893,-36827,-36761,-36695,-36629, + -36563,-36497,-36431,-36365,-36300,-36234,-36168,-36103, + -36037,-35972,-35907,-35841,-35776,-35711,-35646,-35580, + -35515,-35450,-35385,-35321,-35256,-35191,-35126,-35062, + -34997,-34932,-34868,-34803,-34739,-34675,-34610,-34546, + -34482,-34418,-34354,-34289,-34225,-34162,-34098,-34034, + -33970,-33906,-33843,-33779,-33715,-33652,-33588,-33525, + -33461,-33398,-33335,-33272,-33208,-33145,-33082,-33019, + -32956,-32893,-32830,-32767,-32705,-32642,-32579,-32516, + -32454,-32391,-32329,-32266,-32204,-32141,-32079,-32017, + -31955,-31892,-31830,-31768,-31706,-31644,-31582,-31520, + -31458,-31396,-31335,-31273,-31211,-31150,-31088,-31026, + -30965,-30904,-30842,-30781,-30719,-30658,-30597,-30536, + -30474,-30413,-30352,-30291,-30230,-30169,-30108,-30048, + -29987,-29926,-29865,-29805,-29744,-29683,-29623,-29562, + -29502,-29441,-29381,-29321,-29260,-29200,-29140,-29080, + -29020,-28959,-28899,-28839,-28779,-28719,-28660,-28600, + -28540,-28480,-28420,-28361,-28301,-28241,-28182,-28122, + -28063,-28003,-27944,-27884,-27825,-27766,-27707,-27647, + -27588,-27529,-27470,-27411,-27352,-27293,-27234,-27175, + -27116,-27057,-26998,-26940,-26881,-26822,-26763,-26705, + -26646,-26588,-26529,-26471,-26412,-26354,-26295,-26237, + -26179,-26120,-26062,-26004,-25946,-25888,-25830,-25772, + -25714,-25656,-25598,-25540,-25482,-25424,-25366,-25308, + -25251,-25193,-25135,-25078,-25020,-24962,-24905,-24847, + -24790,-24732,-24675,-24618,-24560,-24503,-24446,-24389, + -24331,-24274,-24217,-24160,-24103,-24046,-23989,-23932, + -23875,-23818,-23761,-23704,-23647,-23591,-23534,-23477, + -23420,-23364,-23307,-23250,-23194,-23137,-23081,-23024, + -22968,-22911,-22855,-22799,-22742,-22686,-22630,-22573, + -22517,-22461,-22405,-22349,-22293,-22237,-22181,-22125, + -22069,-22013,-21957,-21901,-21845,-21789,-21733,-21678, + -21622,-21566,-21510,-21455,-21399,-21343,-21288,-21232, + -21177,-21121,-21066,-21010,-20955,-20900,-20844,-20789, + -20734,-20678,-20623,-20568,-20513,-20457,-20402,-20347, + -20292,-20237,-20182,-20127,-20072,-20017,-19962,-19907, + -19852,-19797,-19742,-19688,-19633,-19578,-19523,-19469, + -19414,-19359,-19305,-19250,-19195,-19141,-19086,-19032, + -18977,-18923,-18868,-18814,-18760,-18705,-18651,-18597, + -18542,-18488,-18434,-18380,-18325,-18271,-18217,-18163, + -18109,-18055,-18001,-17946,-17892,-17838,-17784,-17731, + -17677,-17623,-17569,-17515,-17461,-17407,-17353,-17300, + -17246,-17192,-17138,-17085,-17031,-16977,-16924,-16870, + -16817,-16763,-16710,-16656,-16603,-16549,-16496,-16442, + -16389,-16335,-16282,-16229,-16175,-16122,-16069,-16015, + -15962,-15909,-15856,-15802,-15749,-15696,-15643,-15590, + -15537,-15484,-15431,-15378,-15325,-15272,-15219,-15166, + -15113,-15060,-15007,-14954,-14901,-14848,-14795,-14743, + -14690,-14637,-14584,-14531,-14479,-14426,-14373,-14321, + -14268,-14215,-14163,-14110,-14057,-14005,-13952,-13900, + -13847,-13795,-13742,-13690,-13637,-13585,-13533,-13480, + -13428,-13375,-13323,-13271,-13218,-13166,-13114,-13062, + -13009,-12957,-12905,-12853,-12800,-12748,-12696,-12644, + -12592,-12540,-12488,-12436,-12383,-12331,-12279,-12227, + -12175,-12123,-12071,-12019,-11967,-11916,-11864,-11812, + -11760,-11708,-11656,-11604,-11552,-11501,-11449,-11397, + -11345,-11293,-11242,-11190,-11138,-11086,-11035,-10983, + -10931,-10880,-10828,-10777,-10725,-10673,-10622,-10570, + -10519,-10467,-10415,-10364,-10312,-10261,-10209,-10158, + -10106,-10055,-10004,-9952,-9901,-9849,-9798,-9747, + -9695,-9644,-9592,-9541,-9490,-9438,-9387,-9336, + -9285,-9233,-9182,-9131,-9080,-9028,-8977,-8926, + -8875,-8824,-8772,-8721,-8670,-8619,-8568,-8517, + -8466,-8414,-8363,-8312,-8261,-8210,-8159,-8108, + -8057,-8006,-7955,-7904,-7853,-7802,-7751,-7700, + -7649,-7598,-7547,-7496,-7445,-7395,-7344,-7293, + -7242,-7191,-7140,-7089,-7038,-6988,-6937,-6886, + -6835,-6784,-6733,-6683,-6632,-6581,-6530,-6480, + -6429,-6378,-6327,-6277,-6226,-6175,-6124,-6074, + -6023,-5972,-5922,-5871,-5820,-5770,-5719,-5668, + -5618,-5567,-5517,-5466,-5415,-5365,-5314,-5264, + -5213,-5162,-5112,-5061,-5011,-4960,-4910,-4859, + -4808,-4758,-4707,-4657,-4606,-4556,-4505,-4455, + -4404,-4354,-4303,-4253,-4202,-4152,-4101,-4051, + -4001,-3950,-3900,-3849,-3799,-3748,-3698,-3648, + -3597,-3547,-3496,-3446,-3395,-3345,-3295,-3244, + -3194,-3144,-3093,-3043,-2992,-2942,-2892,-2841, + -2791,-2741,-2690,-2640,-2590,-2539,-2489,-2439, + -2388,-2338,-2288,-2237,-2187,-2137,-2086,-2036, + -1986,-1935,-1885,-1835,-1784,-1734,-1684,-1633, + -1583,-1533,-1483,-1432,-1382,-1332,-1281,-1231, + -1181,-1131,-1080,-1030,-980,-929,-879,-829, + -779,-728,-678,-628,-578,-527,-477,-427, + -376,-326,-276,-226,-175,-125,-75,-25, + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1131,1181, + 1231,1281,1332,1382,1432,1483,1533,1583, + 1633,1684,1734,1784,1835,1885,1935,1986, + 2036,2086,2137,2187,2237,2288,2338,2388, + 2439,2489,2539,2590,2640,2690,2741,2791, + 2841,2892,2942,2992,3043,3093,3144,3194, + 3244,3295,3345,3395,3446,3496,3547,3597, + 3648,3698,3748,3799,3849,3900,3950,4001, + 4051,4101,4152,4202,4253,4303,4354,4404, + 4455,4505,4556,4606,4657,4707,4758,4808, + 4859,4910,4960,5011,5061,5112,5162,5213, + 5264,5314,5365,5415,5466,5517,5567,5618, + 5668,5719,5770,5820,5871,5922,5972,6023, + 6074,6124,6175,6226,6277,6327,6378,6429, + 6480,6530,6581,6632,6683,6733,6784,6835, + 6886,6937,6988,7038,7089,7140,7191,7242, + 7293,7344,7395,7445,7496,7547,7598,7649, + 7700,7751,7802,7853,7904,7955,8006,8057, + 8108,8159,8210,8261,8312,8363,8414,8466, + 8517,8568,8619,8670,8721,8772,8824,8875, + 8926,8977,9028,9080,9131,9182,9233,9285, + 9336,9387,9438,9490,9541,9592,9644,9695, + 9747,9798,9849,9901,9952,10004,10055,10106, + 10158,10209,10261,10312,10364,10415,10467,10519, + 10570,10622,10673,10725,10777,10828,10880,10931, + 10983,11035,11086,11138,11190,11242,11293,11345, + 11397,11449,11501,11552,11604,11656,11708,11760, + 11812,11864,11916,11967,12019,12071,12123,12175, + 12227,12279,12331,12383,12436,12488,12540,12592, + 12644,12696,12748,12800,12853,12905,12957,13009, + 13062,13114,13166,13218,13271,13323,13375,13428, + 13480,13533,13585,13637,13690,13742,13795,13847, + 13900,13952,14005,14057,14110,14163,14215,14268, + 14321,14373,14426,14479,14531,14584,14637,14690, + 14743,14795,14848,14901,14954,15007,15060,15113, + 15166,15219,15272,15325,15378,15431,15484,15537, + 15590,15643,15696,15749,15802,15856,15909,15962, + 16015,16069,16122,16175,16229,16282,16335,16389, + 16442,16496,16549,16603,16656,16710,16763,16817, + 16870,16924,16977,17031,17085,17138,17192,17246, + 17300,17353,17407,17461,17515,17569,17623,17677, + 17731,17784,17838,17892,17946,18001,18055,18109, + 18163,18217,18271,18325,18380,18434,18488,18542, + 18597,18651,18705,18760,18814,18868,18923,18977, + 19032,19086,19141,19195,19250,19305,19359,19414, + 19469,19523,19578,19633,19688,19742,19797,19852, + 19907,19962,20017,20072,20127,20182,20237,20292, + 20347,20402,20457,20513,20568,20623,20678,20734, + 20789,20844,20900,20955,21010,21066,21121,21177, + 21232,21288,21343,21399,21455,21510,21566,21622, + 21678,21733,21789,21845,21901,21957,22013,22069, + 22125,22181,22237,22293,22349,22405,22461,22517, + 22573,22630,22686,22742,22799,22855,22911,22968, + 23024,23081,23137,23194,23250,23307,23364,23420, + 23477,23534,23591,23647,23704,23761,23818,23875, + 23932,23989,24046,24103,24160,24217,24274,24331, + 24389,24446,24503,24560,24618,24675,24732,24790, + 24847,24905,24962,25020,25078,25135,25193,25251, + 25308,25366,25424,25482,25540,25598,25656,25714, + 25772,25830,25888,25946,26004,26062,26120,26179, + 26237,26295,26354,26412,26471,26529,26588,26646, + 26705,26763,26822,26881,26940,26998,27057,27116, + 27175,27234,27293,27352,27411,27470,27529,27588, + 27647,27707,27766,27825,27884,27944,28003,28063, + 28122,28182,28241,28301,28361,28420,28480,28540, + 28600,28660,28719,28779,28839,28899,28959,29020, + 29080,29140,29200,29260,29321,29381,29441,29502, + 29562,29623,29683,29744,29805,29865,29926,29987, + 30048,30108,30169,30230,30291,30352,30413,30474, + 30536,30597,30658,30719,30781,30842,30904,30965, + 31026,31088,31150,31211,31273,31335,31396,31458, + 31520,31582,31644,31706,31768,31830,31892,31955, + 32017,32079,32141,32204,32266,32329,32391,32454, + 32516,32579,32642,32705,32767,32830,32893,32956, + 33019,33082,33145,33208,33272,33335,33398,33461, + 33525,33588,33652,33715,33779,33843,33906,33970, + 34034,34098,34162,34225,34289,34354,34418,34482, + 34546,34610,34675,34739,34803,34868,34932,34997, + 35062,35126,35191,35256,35321,35385,35450,35515, + 35580,35646,35711,35776,35841,35907,35972,36037, + 36103,36168,36234,36300,36365,36431,36497,36563, + 36629,36695,36761,36827,36893,36959,37026,37092, + 37158,37225,37291,37358,37425,37491,37558,37625, + 37692,37759,37826,37893,37960,38027,38094,38161, + 38229,38296,38364,38431,38499,38566,38634,38702, + 38770,38837,38905,38973,39042,39110,39178,39246, + 39314,39383,39451,39520,39588,39657,39726,39794, + 39863,39932,40001,40070,40139,40208,40278,40347, + 40416,40486,40555,40625,40694,40764,40834,40904, + 40973,41043,41113,41184,41254,41324,41394,41465, + 41535,41605,41676,41747,41817,41888,41959,42030, + 42101,42172,42243,42314,42385,42457,42528,42600, + 42671,42743,42814,42886,42958,43030,43102,43174, + 43246,43318,43390,43463,43535,43608,43680,43753, + 43826,43898,43971,44044,44117,44190,44263,44337, + 44410,44483,44557,44630,44704,44778,44851,44925, + 44999,45073,45147,45221,45296,45370,45444,45519, + 45593,45668,45743,45818,45892,45967,46042,46118, + 46193,46268,46343,46419,46494,46570,46646,46721, + 46797,46873,46949,47025,47102,47178,47254,47331, + 47407,47484,47560,47637,47714,47791,47868,47945, + 48022,48100,48177,48255,48332,48410,48488,48565, + 48643,48721,48799,48878,48956,49034,49113,49191, + 49270,49349,49427,49506,49585,49664,49744,49823, + 49902,49982,50061,50141,50221,50300,50380,50460, + 50540,50621,50701,50781,50862,50942,51023,51104, + 51185,51266,51347,51428,51509,51591,51672,51754, + 51835,51917,51999,52081,52163,52245,52327,52410, + 52492,52575,52657,52740,52823,52906,52989,53072, + 53156,53239,53322,53406,53490,53574,53657,53741, + 53826,53910,53994,54079,54163,54248,54333,54417, + 54502,54587,54673,54758,54843,54929,55015,55100, + 55186,55272,55358,55444,55531,55617,55704,55790, + 55877,55964,56051,56138,56225,56312,56400,56487, + 56575,56663,56751,56839,56927,57015,57104,57192, + 57281,57369,57458,57547,57636,57725,57815,57904, + 57994,58083,58173,58263,58353,58443,58534,58624, + 58715,58805,58896,58987,59078,59169,59261,59352, + 59444,59535,59627,59719,59811,59903,59996,60088, + 60181,60273,60366,60459,60552,60646,60739,60833, + 60926,61020,61114,61208,61302,61396,61491,61585, + 61680,61775,61870,61965,62060,62156,62251,62347, + 62443,62539,62635,62731,62828,62924,63021,63118, + 63215,63312,63409,63506,63604,63702,63799,63897, + 63996,64094,64192,64291,64389,64488,64587,64687, + 64786,64885,64985,65085,65185,65285,65385,65485, + 65586,65686,65787,65888,65989,66091,66192,66294, + 66396,66498,66600,66702,66804,66907,67010,67113, + 67216,67319,67422,67526,67629,67733,67837,67942, + 68046,68151,68255,68360,68465,68570,68676,68781, + 68887,68993,69099,69205,69312,69418,69525,69632, + 69739,69846,69954,70061,70169,70277,70385,70494, + 70602,70711,70820,70929,71038,71147,71257,71367, + 71477,71587,71697,71808,71918,72029,72140,72252, + 72363,72475,72587,72699,72811,72923,73036,73149, + 73262,73375,73488,73602,73715,73829,73944,74058, + 74172,74287,74402,74517,74633,74748,74864,74980, + 75096,75213,75329,75446,75563,75680,75797,75915, + 76033,76151,76269,76388,76506,76625,76744,76864, + 76983,77103,77223,77343,77463,77584,77705,77826, + 77947,78068,78190,78312,78434,78557,78679,78802, + 78925,79048,79172,79296,79420,79544,79668,79793, + 79918,80043,80168,80294,80420,80546,80672,80799, + 80925,81053,81180,81307,81435,81563,81691,81820, + 81949,82078,82207,82336,82466,82596,82726,82857, + 82987,83118,83250,83381,83513,83645,83777,83910, + 84043,84176,84309,84443,84576,84710,84845,84980, + 85114,85250,85385,85521,85657,85793,85930,86066, + 86204,86341,86479,86616,86755,86893,87032,87171, + 87310,87450,87590,87730,87871,88011,88152,88294, + 88435,88577,88720,88862,89005,89148,89292,89435, + 89579,89724,89868,90013,90158,90304,90450,90596, + 90742,90889,91036,91184,91332,91480,91628,91777, + 91926,92075,92225,92375,92525,92675,92826,92978, + 93129,93281,93434,93586,93739,93892,94046,94200, + 94354,94509,94664,94819,94975,95131,95287,95444, + 95601,95758,95916,96074,96233,96391,96551,96710, + 96870,97030,97191,97352,97513,97675,97837,98000, + 98163,98326,98489,98653,98818,98982,99148,99313, + 99479,99645,99812,99979,100146,100314,100482,100651, + 100820,100990,101159,101330,101500,101671,101843,102015, + 102187,102360,102533,102706,102880,103054,103229,103404, + 103580,103756,103933,104109,104287,104465,104643,104821, + 105000,105180,105360,105540,105721,105902,106084,106266, + 106449,106632,106816,107000,107184,107369,107555,107741, + 107927,108114,108301,108489,108677,108866,109055,109245, + 109435,109626,109817,110008,110200,110393,110586,110780, + 110974,111169,111364,111560,111756,111952,112150,112347, + 112546,112744,112944,113143,113344,113545,113746,113948, + 114151,114354,114557,114761,114966,115171,115377,115583, + 115790,115998,116206,116414,116623,116833,117044,117254, + 117466,117678,117891,118104,118318,118532,118747,118963, + 119179,119396,119613,119831,120050,120269,120489,120709, + 120930,121152,121374,121597,121821,122045,122270,122496, + 122722,122949,123176,123404,123633,123863,124093,124324, + 124555,124787,125020,125254,125488,125723,125959,126195, + 126432,126669,126908,127147,127387,127627,127869,128111, + 128353,128597,128841,129086,129332,129578,129825,130073, + 130322,130571,130821,131072,131324,131576,131830,132084, + 132339,132594,132851,133108,133366,133625,133884,134145, + 134406,134668,134931,135195,135459,135725,135991,136258, + 136526,136795,137065,137335,137607,137879,138152,138426, + 138701,138977,139254,139532,139810,140090,140370,140651, + 140934,141217,141501,141786,142072,142359,142647,142936, + 143226,143517,143808,144101,144395,144690,144986,145282, + 145580,145879,146179,146480,146782,147084,147388,147693, + 148000,148307,148615,148924,149235,149546,149859,150172, + 150487,150803,151120,151438,151757,152077,152399,152722, + 153045,153370,153697,154024,154352,154682,155013,155345, + 155678,156013,156349,156686,157024,157363,157704,158046, + 158389,158734,159079,159427,159775,160125,160476,160828, + 161182,161537,161893,162251,162610,162970,163332,163695, + 164060,164426,164793,165162,165532,165904,166277,166651, + 167027,167405,167784,168164,168546,168930,169315,169701, + 170089,170479,170870,171263,171657,172053,172451,172850, + 173251,173653,174057,174463,174870,175279,175690,176102, + 176516,176932,177349,177769,178190,178612,179037,179463, + 179891,180321,180753,181186,181622,182059,182498,182939, + 183382,183827,184274,184722,185173,185625,186080,186536, + 186995,187455,187918,188382,188849,189318,189789,190261, + 190736,191213,191693,192174,192658,193143,193631,194122, + 194614,195109,195606,196105,196606,197110,197616,198125, + 198636,199149,199664,200182,200703,201226,201751,202279, + 202809,203342,203878,204416,204956,205500,206045,206594, + 207145,207699,208255,208815,209376,209941,210509,211079, + 211652,212228,212807,213389,213973,214561,215151,215745, + 216341,216941,217544,218149,218758,219370,219985,220603, + 221225,221849,222477,223108,223743,224381,225022,225666, + 226314,226966,227621,228279,228941,229606,230275,230948, + 231624,232304,232988,233676,234367,235062,235761,236463, + 237170,237881,238595,239314,240036,240763,241493,242228, + 242967,243711,244458,245210,245966,246727,247492,248261, + 249035,249813,250596,251384,252176,252973,253774,254581, + 255392,256208,257029,257855,258686,259522,260363,261209, + 262060,262917,263779,264646,265519,266397,267280,268169, + 269064,269965,270871,271782,272700,273624,274553,275489, + 276430,277378,278332,279292,280258,281231,282210,283195, + 284188,285186,286192,287204,288223,289249,290282,291322, + 292369,293423,294485,295554,296630,297714,298805,299904, + 301011,302126,303248,304379,305517,306664,307819,308983, + 310154,311335,312524,313721,314928,316143,317368,318601, + 319844,321097,322358,323629,324910,326201,327502,328812, + 330133,331464,332805,334157,335519,336892,338276,339671, + 341078,342495,343924,345364,346816,348280,349756,351244, + 352744,354257,355783,357321,358872,360436,362013,363604, + 365208,366826,368459,370105,371765,373440,375130,376835, + 378555,380290,382040,383807,385589,387387,389202,391034, + 392882,394747,396630,398530,400448,402384,404338,406311, + 408303,410314,412344,414395,416465,418555,420666,422798, + 424951,427125,429321,431540,433781,436045,438332,440643, + 442978,445337,447720,450129,452564,455024,457511,460024, + 462565,465133,467730,470355,473009,475692,478406,481150, + 483925,486732,489571,492443,495348,498287,501261,504269, + 507313,510394,513512,516667,519861,523094,526366,529680, + 533034,536431,539870,543354,546881,550455,554074,557741, + 561456,565221,569035,572901,576818,580789,584815,588896, + 593033,597229,601483,605798,610174,614613,619117,623686, + 628323,633028,637803,642651,647572,652568,657640,662792, + 668024,673338,678737,684223,689797,695462,701219,707072, + 713023,719074,725227,731486,737853,744331,750922,757631, + 764460,771411,778490,785699,793041,800521,808143,815910, + 823827,831898,840127,848520,857081,865817,874730,883829, + 893117,902602,912289,922186,932298,942633,953199,964003, + 975054,986361,997931,1009774,1021901,1034322,1047046,1060087, + 1073455,1087164,1101225,1115654,1130465,1145673,1161294,1177345, + 1193846,1210813,1228269,1246234,1264730,1283783,1303416,1323658, + 1344537,1366084,1388330,1411312,1435065,1459630,1485049,1511367, + 1538632,1566898,1596220,1626658,1658278,1691149,1725348,1760956, + 1798063,1836758,1877161,1919378,1963536,2009771,2058233,2109087, + 2162516,2218719,2277919,2340362,2406322,2476104,2550052,2628549, + 2712030,2800983,2895966,2997613,3106651,3223918,3350381,3487165, + 3635590,3797206,3973855,4167737,4381502,4618375,4882318,5178251, + 5512368,5892567,6329090,6835455,7429880,8137527,8994149,10052327, + 11392683,13145455,15535599,18988036,24413316,34178904,56965752,170910304 +}; + + +const fixed_t finesine[10240] = +{ + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1130,1181, + 1231,1281,1331,1382,1432,1482,1532,1583, + 1633,1683,1733,1784,1834,1884,1934,1985, + 2035,2085,2135,2186,2236,2286,2336,2387, + 2437,2487,2537,2587,2638,2688,2738,2788, + 2839,2889,2939,2989,3039,3090,3140,3190, + 3240,3291,3341,3391,3441,3491,3541,3592, + 3642,3692,3742,3792,3843,3893,3943,3993, + 4043,4093,4144,4194,4244,4294,4344,4394, + 4445,4495,4545,4595,4645,4695,4745,4796, + 4846,4896,4946,4996,5046,5096,5146,5197, + 5247,5297,5347,5397,5447,5497,5547,5597, + 5647,5697,5748,5798,5848,5898,5948,5998, + 6048,6098,6148,6198,6248,6298,6348,6398, + 6448,6498,6548,6598,6648,6698,6748,6798, + 6848,6898,6948,6998,7048,7098,7148,7198, + 7248,7298,7348,7398,7448,7498,7548,7598, + 7648,7697,7747,7797,7847,7897,7947,7997, + 8047,8097,8147,8196,8246,8296,8346,8396, + 8446,8496,8545,8595,8645,8695,8745,8794, + 8844,8894,8944,8994,9043,9093,9143,9193, + 9243,9292,9342,9392,9442,9491,9541,9591, + 9640,9690,9740,9790,9839,9889,9939,9988, + 10038,10088,10137,10187,10237,10286,10336,10386, + 10435,10485,10534,10584,10634,10683,10733,10782, + 10832,10882,10931,10981,11030,11080,11129,11179, + 11228,11278,11327,11377,11426,11476,11525,11575, + 11624,11674,11723,11773,11822,11872,11921,11970, + 12020,12069,12119,12168,12218,12267,12316,12366, + 12415,12464,12514,12563,12612,12662,12711,12760, + 12810,12859,12908,12957,13007,13056,13105,13154, + 13204,13253,13302,13351,13401,13450,13499,13548, + 13597,13647,13696,13745,13794,13843,13892,13941, + 13990,14040,14089,14138,14187,14236,14285,14334, + 14383,14432,14481,14530,14579,14628,14677,14726, + 14775,14824,14873,14922,14971,15020,15069,15118, + 15167,15215,15264,15313,15362,15411,15460,15509, + 15557,15606,15655,15704,15753,15802,15850,15899, + 15948,15997,16045,16094,16143,16191,16240,16289, + 16338,16386,16435,16484,16532,16581,16629,16678, + 16727,16775,16824,16872,16921,16970,17018,17067, + 17115,17164,17212,17261,17309,17358,17406,17455, + 17503,17551,17600,17648,17697,17745,17793,17842, + 17890,17939,17987,18035,18084,18132,18180,18228, + 18277,18325,18373,18421,18470,18518,18566,18614, + 18663,18711,18759,18807,18855,18903,18951,19000, + 19048,19096,19144,19192,19240,19288,19336,19384, + 19432,19480,19528,19576,19624,19672,19720,19768, + 19816,19864,19912,19959,20007,20055,20103,20151, + 20199,20246,20294,20342,20390,20438,20485,20533, + 20581,20629,20676,20724,20772,20819,20867,20915, + 20962,21010,21057,21105,21153,21200,21248,21295, + 21343,21390,21438,21485,21533,21580,21628,21675, + 21723,21770,21817,21865,21912,21960,22007,22054, + 22102,22149,22196,22243,22291,22338,22385,22433, + 22480,22527,22574,22621,22668,22716,22763,22810, + 22857,22904,22951,22998,23045,23092,23139,23186, + 23233,23280,23327,23374,23421,23468,23515,23562, + 23609,23656,23703,23750,23796,23843,23890,23937, + 23984,24030,24077,24124,24171,24217,24264,24311, + 24357,24404,24451,24497,24544,24591,24637,24684, + 24730,24777,24823,24870,24916,24963,25009,25056, + 25102,25149,25195,25241,25288,25334,25381,25427, + 25473,25520,25566,25612,25658,25705,25751,25797, + 25843,25889,25936,25982,26028,26074,26120,26166, + 26212,26258,26304,26350,26396,26442,26488,26534, + 26580,26626,26672,26718,26764,26810,26856,26902, + 26947,26993,27039,27085,27131,27176,27222,27268, + 27313,27359,27405,27450,27496,27542,27587,27633, + 27678,27724,27770,27815,27861,27906,27952,27997, + 28042,28088,28133,28179,28224,28269,28315,28360, + 28405,28451,28496,28541,28586,28632,28677,28722, + 28767,28812,28858,28903,28948,28993,29038,29083, + 29128,29173,29218,29263,29308,29353,29398,29443, + 29488,29533,29577,29622,29667,29712,29757,29801, + 29846,29891,29936,29980,30025,30070,30114,30159, + 30204,30248,30293,30337,30382,30426,30471,30515, + 30560,30604,30649,30693,30738,30782,30826,30871, + 30915,30959,31004,31048,31092,31136,31181,31225, + 31269,31313,31357,31402,31446,31490,31534,31578, + 31622,31666,31710,31754,31798,31842,31886,31930, + 31974,32017,32061,32105,32149,32193,32236,32280, + 32324,32368,32411,32455,32499,32542,32586,32630, + 32673,32717,32760,32804,32847,32891,32934,32978, + 33021,33065,33108,33151,33195,33238,33281,33325, + 33368,33411,33454,33498,33541,33584,33627,33670, + 33713,33756,33799,33843,33886,33929,33972,34015, + 34057,34100,34143,34186,34229,34272,34315,34358, + 34400,34443,34486,34529,34571,34614,34657,34699, + 34742,34785,34827,34870,34912,34955,34997,35040, + 35082,35125,35167,35210,35252,35294,35337,35379, + 35421,35464,35506,35548,35590,35633,35675,35717, + 35759,35801,35843,35885,35927,35969,36011,36053, + 36095,36137,36179,36221,36263,36305,36347,36388, + 36430,36472,36514,36555,36597,36639,36681,36722, + 36764,36805,36847,36889,36930,36972,37013,37055, + 37096,37137,37179,37220,37262,37303,37344,37386, + 37427,37468,37509,37551,37592,37633,37674,37715, + 37756,37797,37838,37879,37920,37961,38002,38043, + 38084,38125,38166,38207,38248,38288,38329,38370, + 38411,38451,38492,38533,38573,38614,38655,38695, + 38736,38776,38817,38857,38898,38938,38979,39019, + 39059,39100,39140,39180,39221,39261,39301,39341, + 39382,39422,39462,39502,39542,39582,39622,39662, + 39702,39742,39782,39822,39862,39902,39942,39982, + 40021,40061,40101,40141,40180,40220,40260,40300, + 40339,40379,40418,40458,40497,40537,40576,40616, + 40655,40695,40734,40773,40813,40852,40891,40931, + 40970,41009,41048,41087,41127,41166,41205,41244, + 41283,41322,41361,41400,41439,41478,41517,41556, + 41595,41633,41672,41711,41750,41788,41827,41866, + 41904,41943,41982,42020,42059,42097,42136,42174, + 42213,42251,42290,42328,42366,42405,42443,42481, + 42520,42558,42596,42634,42672,42711,42749,42787, + 42825,42863,42901,42939,42977,43015,43053,43091, + 43128,43166,43204,43242,43280,43317,43355,43393, + 43430,43468,43506,43543,43581,43618,43656,43693, + 43731,43768,43806,43843,43880,43918,43955,43992, + 44029,44067,44104,44141,44178,44215,44252,44289, + 44326,44363,44400,44437,44474,44511,44548,44585, + 44622,44659,44695,44732,44769,44806,44842,44879, + 44915,44952,44989,45025,45062,45098,45135,45171, + 45207,45244,45280,45316,45353,45389,45425,45462, + 45498,45534,45570,45606,45642,45678,45714,45750, + 45786,45822,45858,45894,45930,45966,46002,46037, + 46073,46109,46145,46180,46216,46252,46287,46323, + 46358,46394,46429,46465,46500,46536,46571,46606, + 46642,46677,46712,46747,46783,46818,46853,46888, + 46923,46958,46993,47028,47063,47098,47133,47168, + 47203,47238,47273,47308,47342,47377,47412,47446, + 47481,47516,47550,47585,47619,47654,47688,47723, + 47757,47792,47826,47860,47895,47929,47963,47998, + 48032,48066,48100,48134,48168,48202,48237,48271, + 48305,48338,48372,48406,48440,48474,48508,48542, + 48575,48609,48643,48676,48710,48744,48777,48811, + 48844,48878,48911,48945,48978,49012,49045,49078, + 49112,49145,49178,49211,49244,49278,49311,49344, + 49377,49410,49443,49476,49509,49542,49575,49608, + 49640,49673,49706,49739,49771,49804,49837,49869, + 49902,49935,49967,50000,50032,50065,50097,50129, + 50162,50194,50226,50259,50291,50323,50355,50387, + 50420,50452,50484,50516,50548,50580,50612,50644, + 50675,50707,50739,50771,50803,50834,50866,50898, + 50929,50961,50993,51024,51056,51087,51119,51150, + 51182,51213,51244,51276,51307,51338,51369,51401, + 51432,51463,51494,51525,51556,51587,51618,51649, + 51680,51711,51742,51773,51803,51834,51865,51896, + 51926,51957,51988,52018,52049,52079,52110,52140, + 52171,52201,52231,52262,52292,52322,52353,52383, + 52413,52443,52473,52503,52534,52564,52594,52624, + 52653,52683,52713,52743,52773,52803,52832,52862, + 52892,52922,52951,52981,53010,53040,53069,53099, + 53128,53158,53187,53216,53246,53275,53304,53334, + 53363,53392,53421,53450,53479,53508,53537,53566, + 53595,53624,53653,53682,53711,53739,53768,53797, + 53826,53854,53883,53911,53940,53969,53997,54026, + 54054,54082,54111,54139,54167,54196,54224,54252, + 54280,54308,54337,54365,54393,54421,54449,54477, + 54505,54533,54560,54588,54616,54644,54672,54699, + 54727,54755,54782,54810,54837,54865,54892,54920, + 54947,54974,55002,55029,55056,55084,55111,55138, + 55165,55192,55219,55246,55274,55300,55327,55354, + 55381,55408,55435,55462,55489,55515,55542,55569, + 55595,55622,55648,55675,55701,55728,55754,55781, + 55807,55833,55860,55886,55912,55938,55965,55991, + 56017,56043,56069,56095,56121,56147,56173,56199, + 56225,56250,56276,56302,56328,56353,56379,56404, + 56430,56456,56481,56507,56532,56557,56583,56608, + 56633,56659,56684,56709,56734,56760,56785,56810, + 56835,56860,56885,56910,56935,56959,56984,57009, + 57034,57059,57083,57108,57133,57157,57182,57206, + 57231,57255,57280,57304,57329,57353,57377,57402, + 57426,57450,57474,57498,57522,57546,57570,57594, + 57618,57642,57666,57690,57714,57738,57762,57785, + 57809,57833,57856,57880,57903,57927,57950,57974, + 57997,58021,58044,58067,58091,58114,58137,58160, + 58183,58207,58230,58253,58276,58299,58322,58345, + 58367,58390,58413,58436,58459,58481,58504,58527, + 58549,58572,58594,58617,58639,58662,58684,58706, + 58729,58751,58773,58795,58818,58840,58862,58884, + 58906,58928,58950,58972,58994,59016,59038,59059, + 59081,59103,59125,59146,59168,59190,59211,59233, + 59254,59276,59297,59318,59340,59361,59382,59404, + 59425,59446,59467,59488,59509,59530,59551,59572, + 59593,59614,59635,59656,59677,59697,59718,59739, + 59759,59780,59801,59821,59842,59862,59883,59903, + 59923,59944,59964,59984,60004,60025,60045,60065, + 60085,60105,60125,60145,60165,60185,60205,60225, + 60244,60264,60284,60304,60323,60343,60363,60382, + 60402,60421,60441,60460,60479,60499,60518,60537, + 60556,60576,60595,60614,60633,60652,60671,60690, + 60709,60728,60747,60766,60785,60803,60822,60841, + 60859,60878,60897,60915,60934,60952,60971,60989, + 61007,61026,61044,61062,61081,61099,61117,61135, + 61153,61171,61189,61207,61225,61243,61261,61279, + 61297,61314,61332,61350,61367,61385,61403,61420, + 61438,61455,61473,61490,61507,61525,61542,61559, + 61577,61594,61611,61628,61645,61662,61679,61696, + 61713,61730,61747,61764,61780,61797,61814,61831, + 61847,61864,61880,61897,61913,61930,61946,61963, + 61979,61995,62012,62028,62044,62060,62076,62092, + 62108,62125,62141,62156,62172,62188,62204,62220, + 62236,62251,62267,62283,62298,62314,62329,62345, + 62360,62376,62391,62407,62422,62437,62453,62468, + 62483,62498,62513,62528,62543,62558,62573,62588, + 62603,62618,62633,62648,62662,62677,62692,62706, + 62721,62735,62750,62764,62779,62793,62808,62822, + 62836,62850,62865,62879,62893,62907,62921,62935, + 62949,62963,62977,62991,63005,63019,63032,63046, + 63060,63074,63087,63101,63114,63128,63141,63155, + 63168,63182,63195,63208,63221,63235,63248,63261, + 63274,63287,63300,63313,63326,63339,63352,63365, + 63378,63390,63403,63416,63429,63441,63454,63466, + 63479,63491,63504,63516,63528,63541,63553,63565, + 63578,63590,63602,63614,63626,63638,63650,63662, + 63674,63686,63698,63709,63721,63733,63745,63756, + 63768,63779,63791,63803,63814,63825,63837,63848, + 63859,63871,63882,63893,63904,63915,63927,63938, + 63949,63960,63971,63981,63992,64003,64014,64025, + 64035,64046,64057,64067,64078,64088,64099,64109, + 64120,64130,64140,64151,64161,64171,64181,64192, + 64202,64212,64222,64232,64242,64252,64261,64271, + 64281,64291,64301,64310,64320,64330,64339,64349, + 64358,64368,64377,64387,64396,64405,64414,64424, + 64433,64442,64451,64460,64469,64478,64487,64496, + 64505,64514,64523,64532,64540,64549,64558,64566, + 64575,64584,64592,64601,64609,64617,64626,64634, + 64642,64651,64659,64667,64675,64683,64691,64699, + 64707,64715,64723,64731,64739,64747,64754,64762, + 64770,64777,64785,64793,64800,64808,64815,64822, + 64830,64837,64844,64852,64859,64866,64873,64880, + 64887,64895,64902,64908,64915,64922,64929,64936, + 64943,64949,64956,64963,64969,64976,64982,64989, + 64995,65002,65008,65015,65021,65027,65033,65040, + 65046,65052,65058,65064,65070,65076,65082,65088, + 65094,65099,65105,65111,65117,65122,65128,65133, + 65139,65144,65150,65155,65161,65166,65171,65177, + 65182,65187,65192,65197,65202,65207,65212,65217, + 65222,65227,65232,65237,65242,65246,65251,65256, + 65260,65265,65270,65274,65279,65283,65287,65292, + 65296,65300,65305,65309,65313,65317,65321,65325, + 65329,65333,65337,65341,65345,65349,65352,65356, + 65360,65363,65367,65371,65374,65378,65381,65385, + 65388,65391,65395,65398,65401,65404,65408,65411, + 65414,65417,65420,65423,65426,65429,65431,65434, + 65437,65440,65442,65445,65448,65450,65453,65455, + 65458,65460,65463,65465,65467,65470,65472,65474, + 65476,65478,65480,65482,65484,65486,65488,65490, + 65492,65494,65496,65497,65499,65501,65502,65504, + 65505,65507,65508,65510,65511,65513,65514,65515, + 65516,65518,65519,65520,65521,65522,65523,65524, + 65525,65526,65527,65527,65528,65529,65530,65530, + 65531,65531,65532,65532,65533,65533,65534,65534, + 65534,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65534, + 65534,65534,65533,65533,65532,65532,65531,65531, + 65530,65530,65529,65528,65527,65527,65526,65525, + 65524,65523,65522,65521,65520,65519,65518,65516, + 65515,65514,65513,65511,65510,65508,65507,65505, + 65504,65502,65501,65499,65497,65496,65494,65492, + 65490,65488,65486,65484,65482,65480,65478,65476, + 65474,65472,65470,65467,65465,65463,65460,65458, + 65455,65453,65450,65448,65445,65442,65440,65437, + 65434,65431,65429,65426,65423,65420,65417,65414, + 65411,65408,65404,65401,65398,65395,65391,65388, + 65385,65381,65378,65374,65371,65367,65363,65360, + 65356,65352,65349,65345,65341,65337,65333,65329, + 65325,65321,65317,65313,65309,65305,65300,65296, + 65292,65287,65283,65279,65274,65270,65265,65260, + 65256,65251,65246,65242,65237,65232,65227,65222, + 65217,65212,65207,65202,65197,65192,65187,65182, + 65177,65171,65166,65161,65155,65150,65144,65139, + 65133,65128,65122,65117,65111,65105,65099,65094, + 65088,65082,65076,65070,65064,65058,65052,65046, + 65040,65033,65027,65021,65015,65008,65002,64995, + 64989,64982,64976,64969,64963,64956,64949,64943, + 64936,64929,64922,64915,64908,64902,64895,64887, + 64880,64873,64866,64859,64852,64844,64837,64830, + 64822,64815,64808,64800,64793,64785,64777,64770, + 64762,64754,64747,64739,64731,64723,64715,64707, + 64699,64691,64683,64675,64667,64659,64651,64642, + 64634,64626,64617,64609,64600,64592,64584,64575, + 64566,64558,64549,64540,64532,64523,64514,64505, + 64496,64487,64478,64469,64460,64451,64442,64433, + 64424,64414,64405,64396,64387,64377,64368,64358, + 64349,64339,64330,64320,64310,64301,64291,64281, + 64271,64261,64252,64242,64232,64222,64212,64202, + 64192,64181,64171,64161,64151,64140,64130,64120, + 64109,64099,64088,64078,64067,64057,64046,64035, + 64025,64014,64003,63992,63981,63971,63960,63949, + 63938,63927,63915,63904,63893,63882,63871,63859, + 63848,63837,63825,63814,63803,63791,63779,63768, + 63756,63745,63733,63721,63709,63698,63686,63674, + 63662,63650,63638,63626,63614,63602,63590,63578, + 63565,63553,63541,63528,63516,63504,63491,63479, + 63466,63454,63441,63429,63416,63403,63390,63378, + 63365,63352,63339,63326,63313,63300,63287,63274, + 63261,63248,63235,63221,63208,63195,63182,63168, + 63155,63141,63128,63114,63101,63087,63074,63060, + 63046,63032,63019,63005,62991,62977,62963,62949, + 62935,62921,62907,62893,62879,62865,62850,62836, + 62822,62808,62793,62779,62764,62750,62735,62721, + 62706,62692,62677,62662,62648,62633,62618,62603, + 62588,62573,62558,62543,62528,62513,62498,62483, + 62468,62453,62437,62422,62407,62391,62376,62360, + 62345,62329,62314,62298,62283,62267,62251,62236, + 62220,62204,62188,62172,62156,62141,62125,62108, + 62092,62076,62060,62044,62028,62012,61995,61979, + 61963,61946,61930,61913,61897,61880,61864,61847, + 61831,61814,61797,61780,61764,61747,61730,61713, + 61696,61679,61662,61645,61628,61611,61594,61577, + 61559,61542,61525,61507,61490,61473,61455,61438, + 61420,61403,61385,61367,61350,61332,61314,61297, + 61279,61261,61243,61225,61207,61189,61171,61153, + 61135,61117,61099,61081,61062,61044,61026,61007, + 60989,60971,60952,60934,60915,60897,60878,60859, + 60841,60822,60803,60785,60766,60747,60728,60709, + 60690,60671,60652,60633,60614,60595,60576,60556, + 60537,60518,60499,60479,60460,60441,60421,60402, + 60382,60363,60343,60323,60304,60284,60264,60244, + 60225,60205,60185,60165,60145,60125,60105,60085, + 60065,60045,60025,60004,59984,59964,59944,59923, + 59903,59883,59862,59842,59821,59801,59780,59759, + 59739,59718,59697,59677,59656,59635,59614,59593, + 59572,59551,59530,59509,59488,59467,59446,59425, + 59404,59382,59361,59340,59318,59297,59276,59254, + 59233,59211,59190,59168,59146,59125,59103,59081, + 59059,59038,59016,58994,58972,58950,58928,58906, + 58884,58862,58840,58818,58795,58773,58751,58729, + 58706,58684,58662,58639,58617,58594,58572,58549, + 58527,58504,58481,58459,58436,58413,58390,58367, + 58345,58322,58299,58276,58253,58230,58207,58183, + 58160,58137,58114,58091,58067,58044,58021,57997, + 57974,57950,57927,57903,57880,57856,57833,57809, + 57785,57762,57738,57714,57690,57666,57642,57618, + 57594,57570,57546,57522,57498,57474,57450,57426, + 57402,57377,57353,57329,57304,57280,57255,57231, + 57206,57182,57157,57133,57108,57083,57059,57034, + 57009,56984,56959,56935,56910,56885,56860,56835, + 56810,56785,56760,56734,56709,56684,56659,56633, + 56608,56583,56557,56532,56507,56481,56456,56430, + 56404,56379,56353,56328,56302,56276,56250,56225, + 56199,56173,56147,56121,56095,56069,56043,56017, + 55991,55965,55938,55912,55886,55860,55833,55807, + 55781,55754,55728,55701,55675,55648,55622,55595, + 55569,55542,55515,55489,55462,55435,55408,55381, + 55354,55327,55300,55274,55246,55219,55192,55165, + 55138,55111,55084,55056,55029,55002,54974,54947, + 54920,54892,54865,54837,54810,54782,54755,54727, + 54699,54672,54644,54616,54588,54560,54533,54505, + 54477,54449,54421,54393,54365,54337,54308,54280, + 54252,54224,54196,54167,54139,54111,54082,54054, + 54026,53997,53969,53940,53911,53883,53854,53826, + 53797,53768,53739,53711,53682,53653,53624,53595, + 53566,53537,53508,53479,53450,53421,53392,53363, + 53334,53304,53275,53246,53216,53187,53158,53128, + 53099,53069,53040,53010,52981,52951,52922,52892, + 52862,52832,52803,52773,52743,52713,52683,52653, + 52624,52594,52564,52534,52503,52473,52443,52413, + 52383,52353,52322,52292,52262,52231,52201,52171, + 52140,52110,52079,52049,52018,51988,51957,51926, + 51896,51865,51834,51803,51773,51742,51711,51680, + 51649,51618,51587,51556,51525,51494,51463,51432, + 51401,51369,51338,51307,51276,51244,51213,51182, + 51150,51119,51087,51056,51024,50993,50961,50929, + 50898,50866,50834,50803,50771,50739,50707,50675, + 50644,50612,50580,50548,50516,50484,50452,50420, + 50387,50355,50323,50291,50259,50226,50194,50162, + 50129,50097,50065,50032,50000,49967,49935,49902, + 49869,49837,49804,49771,49739,49706,49673,49640, + 49608,49575,49542,49509,49476,49443,49410,49377, + 49344,49311,49278,49244,49211,49178,49145,49112, + 49078,49045,49012,48978,48945,48911,48878,48844, + 48811,48777,48744,48710,48676,48643,48609,48575, + 48542,48508,48474,48440,48406,48372,48338,48304, + 48271,48237,48202,48168,48134,48100,48066,48032, + 47998,47963,47929,47895,47860,47826,47792,47757, + 47723,47688,47654,47619,47585,47550,47516,47481, + 47446,47412,47377,47342,47308,47273,47238,47203, + 47168,47133,47098,47063,47028,46993,46958,46923, + 46888,46853,46818,46783,46747,46712,46677,46642, + 46606,46571,46536,46500,46465,46429,46394,46358, + 46323,46287,46252,46216,46180,46145,46109,46073, + 46037,46002,45966,45930,45894,45858,45822,45786, + 45750,45714,45678,45642,45606,45570,45534,45498, + 45462,45425,45389,45353,45316,45280,45244,45207, + 45171,45135,45098,45062,45025,44989,44952,44915, + 44879,44842,44806,44769,44732,44695,44659,44622, + 44585,44548,44511,44474,44437,44400,44363,44326, + 44289,44252,44215,44178,44141,44104,44067,44029, + 43992,43955,43918,43880,43843,43806,43768,43731, + 43693,43656,43618,43581,43543,43506,43468,43430, + 43393,43355,43317,43280,43242,43204,43166,43128, + 43091,43053,43015,42977,42939,42901,42863,42825, + 42787,42749,42711,42672,42634,42596,42558,42520, + 42481,42443,42405,42366,42328,42290,42251,42213, + 42174,42136,42097,42059,42020,41982,41943,41904, + 41866,41827,41788,41750,41711,41672,41633,41595, + 41556,41517,41478,41439,41400,41361,41322,41283, + 41244,41205,41166,41127,41088,41048,41009,40970, + 40931,40891,40852,40813,40773,40734,40695,40655, + 40616,40576,40537,40497,40458,40418,40379,40339, + 40300,40260,40220,40180,40141,40101,40061,40021, + 39982,39942,39902,39862,39822,39782,39742,39702, + 39662,39622,39582,39542,39502,39462,39422,39382, + 39341,39301,39261,39221,39180,39140,39100,39059, + 39019,38979,38938,38898,38857,38817,38776,38736, + 38695,38655,38614,38573,38533,38492,38451,38411, + 38370,38329,38288,38248,38207,38166,38125,38084, + 38043,38002,37961,37920,37879,37838,37797,37756, + 37715,37674,37633,37592,37551,37509,37468,37427, + 37386,37344,37303,37262,37220,37179,37137,37096, + 37055,37013,36972,36930,36889,36847,36805,36764, + 36722,36681,36639,36597,36556,36514,36472,36430, + 36388,36347,36305,36263,36221,36179,36137,36095, + 36053,36011,35969,35927,35885,35843,35801,35759, + 35717,35675,35633,35590,35548,35506,35464,35421, + 35379,35337,35294,35252,35210,35167,35125,35082, + 35040,34997,34955,34912,34870,34827,34785,34742, + 34699,34657,34614,34571,34529,34486,34443,34400, + 34358,34315,34272,34229,34186,34143,34100,34057, + 34015,33972,33929,33886,33843,33799,33756,33713, + 33670,33627,33584,33541,33498,33454,33411,33368, + 33325,33281,33238,33195,33151,33108,33065,33021, + 32978,32934,32891,32847,32804,32760,32717,32673, + 32630,32586,32542,32499,32455,32411,32368,32324, + 32280,32236,32193,32149,32105,32061,32017,31974, + 31930,31886,31842,31798,31754,31710,31666,31622, + 31578,31534,31490,31446,31402,31357,31313,31269, + 31225,31181,31136,31092,31048,31004,30959,30915, + 30871,30826,30782,30738,30693,30649,30604,30560, + 30515,30471,30426,30382,30337,30293,30248,30204, + 30159,30114,30070,30025,29980,29936,29891,29846, + 29801,29757,29712,29667,29622,29577,29533,29488, + 29443,29398,29353,29308,29263,29218,29173,29128, + 29083,29038,28993,28948,28903,28858,28812,28767, + 28722,28677,28632,28586,28541,28496,28451,28405, + 28360,28315,28269,28224,28179,28133,28088,28042, + 27997,27952,27906,27861,27815,27770,27724,27678, + 27633,27587,27542,27496,27450,27405,27359,27313, + 27268,27222,27176,27131,27085,27039,26993,26947, + 26902,26856,26810,26764,26718,26672,26626,26580, + 26534,26488,26442,26396,26350,26304,26258,26212, + 26166,26120,26074,26028,25982,25936,25889,25843, + 25797,25751,25705,25658,25612,25566,25520,25473, + 25427,25381,25334,25288,25241,25195,25149,25102, + 25056,25009,24963,24916,24870,24823,24777,24730, + 24684,24637,24591,24544,24497,24451,24404,24357, + 24311,24264,24217,24171,24124,24077,24030,23984, + 23937,23890,23843,23796,23750,23703,23656,23609, + 23562,23515,23468,23421,23374,23327,23280,23233, + 23186,23139,23092,23045,22998,22951,22904,22857, + 22810,22763,22716,22668,22621,22574,22527,22480, + 22433,22385,22338,22291,22243,22196,22149,22102, + 22054,22007,21960,21912,21865,21817,21770,21723, + 21675,21628,21580,21533,21485,21438,21390,21343, + 21295,21248,21200,21153,21105,21057,21010,20962, + 20915,20867,20819,20772,20724,20676,20629,20581, + 20533,20485,20438,20390,20342,20294,20246,20199, + 20151,20103,20055,20007,19959,19912,19864,19816, + 19768,19720,19672,19624,19576,19528,19480,19432, + 19384,19336,19288,19240,19192,19144,19096,19048, + 19000,18951,18903,18855,18807,18759,18711,18663, + 18614,18566,18518,18470,18421,18373,18325,18277, + 18228,18180,18132,18084,18035,17987,17939,17890, + 17842,17793,17745,17697,17648,17600,17551,17503, + 17455,17406,17358,17309,17261,17212,17164,17115, + 17067,17018,16970,16921,16872,16824,16775,16727, + 16678,16629,16581,16532,16484,16435,16386,16338, + 16289,16240,16191,16143,16094,16045,15997,15948, + 15899,15850,15802,15753,15704,15655,15606,15557, + 15509,15460,15411,15362,15313,15264,15215,15167, + 15118,15069,15020,14971,14922,14873,14824,14775, + 14726,14677,14628,14579,14530,14481,14432,14383, + 14334,14285,14236,14187,14138,14089,14040,13990, + 13941,13892,13843,13794,13745,13696,13646,13597, + 13548,13499,13450,13401,13351,13302,13253,13204, + 13154,13105,13056,13007,12957,12908,12859,12810, + 12760,12711,12662,12612,12563,12514,12464,12415, + 12366,12316,12267,12218,12168,12119,12069,12020, + 11970,11921,11872,11822,11773,11723,11674,11624, + 11575,11525,11476,11426,11377,11327,11278,11228, + 11179,11129,11080,11030,10981,10931,10882,10832, + 10782,10733,10683,10634,10584,10534,10485,10435, + 10386,10336,10286,10237,10187,10137,10088,10038, + 9988,9939,9889,9839,9790,9740,9690,9640, + 9591,9541,9491,9442,9392,9342,9292,9243, + 9193,9143,9093,9043,8994,8944,8894,8844, + 8794,8745,8695,8645,8595,8545,8496,8446, + 8396,8346,8296,8246,8196,8147,8097,8047, + 7997,7947,7897,7847,7797,7747,7697,7648, + 7598,7548,7498,7448,7398,7348,7298,7248, + 7198,7148,7098,7048,6998,6948,6898,6848, + 6798,6748,6698,6648,6598,6548,6498,6448, + 6398,6348,6298,6248,6198,6148,6098,6048, + 5998,5948,5898,5848,5798,5748,5697,5647, + 5597,5547,5497,5447,5397,5347,5297,5247, + 5197,5146,5096,5046,4996,4946,4896,4846, + 4796,4745,4695,4645,4595,4545,4495,4445, + 4394,4344,4294,4244,4194,4144,4093,4043, + 3993,3943,3893,3843,3792,3742,3692,3642, + 3592,3541,3491,3441,3391,3341,3291,3240, + 3190,3140,3090,3039,2989,2939,2889,2839, + 2788,2738,2688,2638,2587,2537,2487,2437, + 2387,2336,2286,2236,2186,2135,2085,2035, + 1985,1934,1884,1834,1784,1733,1683,1633, + 1583,1532,1482,1432,1382,1331,1281,1231, + 1181,1130,1080,1030,980,929,879,829, + 779,728,678,628,578,527,477,427, + 376,326,276,226,175,125,75,25, + -25,-75,-125,-175,-226,-276,-326,-376, + -427,-477,-527,-578,-628,-678,-728,-779, + -829,-879,-929,-980,-1030,-1080,-1130,-1181, + -1231,-1281,-1331,-1382,-1432,-1482,-1532,-1583, + -1633,-1683,-1733,-1784,-1834,-1884,-1934,-1985, + -2035,-2085,-2135,-2186,-2236,-2286,-2336,-2387, + -2437,-2487,-2537,-2588,-2638,-2688,-2738,-2788, + -2839,-2889,-2939,-2989,-3039,-3090,-3140,-3190, + -3240,-3291,-3341,-3391,-3441,-3491,-3541,-3592, + -3642,-3692,-3742,-3792,-3843,-3893,-3943,-3993, + -4043,-4093,-4144,-4194,-4244,-4294,-4344,-4394, + -4445,-4495,-4545,-4595,-4645,-4695,-4745,-4796, + -4846,-4896,-4946,-4996,-5046,-5096,-5146,-5197, + -5247,-5297,-5347,-5397,-5447,-5497,-5547,-5597, + -5647,-5697,-5748,-5798,-5848,-5898,-5948,-5998, + -6048,-6098,-6148,-6198,-6248,-6298,-6348,-6398, + -6448,-6498,-6548,-6598,-6648,-6698,-6748,-6798, + -6848,-6898,-6948,-6998,-7048,-7098,-7148,-7198, + -7248,-7298,-7348,-7398,-7448,-7498,-7548,-7598, + -7648,-7697,-7747,-7797,-7847,-7897,-7947,-7997, + -8047,-8097,-8147,-8196,-8246,-8296,-8346,-8396, + -8446,-8496,-8545,-8595,-8645,-8695,-8745,-8794, + -8844,-8894,-8944,-8994,-9043,-9093,-9143,-9193, + -9243,-9292,-9342,-9392,-9442,-9491,-9541,-9591, + -9640,-9690,-9740,-9790,-9839,-9889,-9939,-9988, + -10038,-10088,-10137,-10187,-10237,-10286,-10336,-10386, + -10435,-10485,-10534,-10584,-10634,-10683,-10733,-10782, + -10832,-10882,-10931,-10981,-11030,-11080,-11129,-11179, + -11228,-11278,-11327,-11377,-11426,-11476,-11525,-11575, + -11624,-11674,-11723,-11773,-11822,-11872,-11921,-11970, + -12020,-12069,-12119,-12168,-12218,-12267,-12316,-12366, + -12415,-12464,-12514,-12563,-12612,-12662,-12711,-12760, + -12810,-12859,-12908,-12957,-13007,-13056,-13105,-13154, + -13204,-13253,-13302,-13351,-13401,-13450,-13499,-13548, + -13597,-13647,-13696,-13745,-13794,-13843,-13892,-13941, + -13990,-14040,-14089,-14138,-14187,-14236,-14285,-14334, + -14383,-14432,-14481,-14530,-14579,-14628,-14677,-14726, + -14775,-14824,-14873,-14922,-14971,-15020,-15069,-15118, + -15167,-15215,-15264,-15313,-15362,-15411,-15460,-15509, + -15557,-15606,-15655,-15704,-15753,-15802,-15850,-15899, + -15948,-15997,-16045,-16094,-16143,-16191,-16240,-16289, + -16338,-16386,-16435,-16484,-16532,-16581,-16629,-16678, + -16727,-16775,-16824,-16872,-16921,-16970,-17018,-17067, + -17115,-17164,-17212,-17261,-17309,-17358,-17406,-17455, + -17503,-17551,-17600,-17648,-17697,-17745,-17793,-17842, + -17890,-17939,-17987,-18035,-18084,-18132,-18180,-18228, + -18277,-18325,-18373,-18421,-18470,-18518,-18566,-18614, + -18663,-18711,-18759,-18807,-18855,-18903,-18951,-19000, + -19048,-19096,-19144,-19192,-19240,-19288,-19336,-19384, + -19432,-19480,-19528,-19576,-19624,-19672,-19720,-19768, + -19816,-19864,-19912,-19959,-20007,-20055,-20103,-20151, + -20199,-20246,-20294,-20342,-20390,-20438,-20485,-20533, + -20581,-20629,-20676,-20724,-20772,-20819,-20867,-20915, + -20962,-21010,-21057,-21105,-21153,-21200,-21248,-21295, + -21343,-21390,-21438,-21485,-21533,-21580,-21628,-21675, + -21723,-21770,-21817,-21865,-21912,-21960,-22007,-22054, + -22102,-22149,-22196,-22243,-22291,-22338,-22385,-22433, + -22480,-22527,-22574,-22621,-22668,-22716,-22763,-22810, + -22857,-22904,-22951,-22998,-23045,-23092,-23139,-23186, + -23233,-23280,-23327,-23374,-23421,-23468,-23515,-23562, + -23609,-23656,-23703,-23750,-23796,-23843,-23890,-23937, + -23984,-24030,-24077,-24124,-24171,-24217,-24264,-24311, + -24357,-24404,-24451,-24497,-24544,-24591,-24637,-24684, + -24730,-24777,-24823,-24870,-24916,-24963,-25009,-25056, + -25102,-25149,-25195,-25241,-25288,-25334,-25381,-25427, + -25473,-25520,-25566,-25612,-25658,-25705,-25751,-25797, + -25843,-25889,-25936,-25982,-26028,-26074,-26120,-26166, + -26212,-26258,-26304,-26350,-26396,-26442,-26488,-26534, + -26580,-26626,-26672,-26718,-26764,-26810,-26856,-26902, + -26947,-26993,-27039,-27085,-27131,-27176,-27222,-27268, + -27313,-27359,-27405,-27450,-27496,-27542,-27587,-27633, + -27678,-27724,-27770,-27815,-27861,-27906,-27952,-27997, + -28042,-28088,-28133,-28179,-28224,-28269,-28315,-28360, + -28405,-28451,-28496,-28541,-28586,-28632,-28677,-28722, + -28767,-28812,-28858,-28903,-28948,-28993,-29038,-29083, + -29128,-29173,-29218,-29263,-29308,-29353,-29398,-29443, + -29488,-29533,-29577,-29622,-29667,-29712,-29757,-29801, + -29846,-29891,-29936,-29980,-30025,-30070,-30114,-30159, + -30204,-30248,-30293,-30337,-30382,-30426,-30471,-30515, + -30560,-30604,-30649,-30693,-30738,-30782,-30826,-30871, + -30915,-30959,-31004,-31048,-31092,-31136,-31181,-31225, + -31269,-31313,-31357,-31402,-31446,-31490,-31534,-31578, + -31622,-31666,-31710,-31754,-31798,-31842,-31886,-31930, + -31974,-32017,-32061,-32105,-32149,-32193,-32236,-32280, + -32324,-32368,-32411,-32455,-32499,-32542,-32586,-32630, + -32673,-32717,-32760,-32804,-32847,-32891,-32934,-32978, + -33021,-33065,-33108,-33151,-33195,-33238,-33281,-33325, + -33368,-33411,-33454,-33498,-33541,-33584,-33627,-33670, + -33713,-33756,-33799,-33843,-33886,-33929,-33972,-34015, + -34057,-34100,-34143,-34186,-34229,-34272,-34315,-34358, + -34400,-34443,-34486,-34529,-34571,-34614,-34657,-34699, + -34742,-34785,-34827,-34870,-34912,-34955,-34997,-35040, + -35082,-35125,-35167,-35210,-35252,-35294,-35337,-35379, + -35421,-35464,-35506,-35548,-35590,-35633,-35675,-35717, + -35759,-35801,-35843,-35885,-35927,-35969,-36011,-36053, + -36095,-36137,-36179,-36221,-36263,-36305,-36347,-36388, + -36430,-36472,-36514,-36555,-36597,-36639,-36681,-36722, + -36764,-36805,-36847,-36889,-36930,-36972,-37013,-37055, + -37096,-37137,-37179,-37220,-37262,-37303,-37344,-37386, + -37427,-37468,-37509,-37551,-37592,-37633,-37674,-37715, + -37756,-37797,-37838,-37879,-37920,-37961,-38002,-38043, + -38084,-38125,-38166,-38207,-38248,-38288,-38329,-38370, + -38411,-38451,-38492,-38533,-38573,-38614,-38655,-38695, + -38736,-38776,-38817,-38857,-38898,-38938,-38979,-39019, + -39059,-39100,-39140,-39180,-39221,-39261,-39301,-39341, + -39382,-39422,-39462,-39502,-39542,-39582,-39622,-39662, + -39702,-39742,-39782,-39822,-39862,-39902,-39942,-39982, + -40021,-40061,-40101,-40141,-40180,-40220,-40260,-40299, + -40339,-40379,-40418,-40458,-40497,-40537,-40576,-40616, + -40655,-40695,-40734,-40773,-40813,-40852,-40891,-40931, + -40970,-41009,-41048,-41087,-41127,-41166,-41205,-41244, + -41283,-41322,-41361,-41400,-41439,-41478,-41517,-41556, + -41595,-41633,-41672,-41711,-41750,-41788,-41827,-41866, + -41904,-41943,-41982,-42020,-42059,-42097,-42136,-42174, + -42213,-42251,-42290,-42328,-42366,-42405,-42443,-42481, + -42520,-42558,-42596,-42634,-42672,-42711,-42749,-42787, + -42825,-42863,-42901,-42939,-42977,-43015,-43053,-43091, + -43128,-43166,-43204,-43242,-43280,-43317,-43355,-43393, + -43430,-43468,-43506,-43543,-43581,-43618,-43656,-43693, + -43731,-43768,-43806,-43843,-43880,-43918,-43955,-43992, + -44029,-44067,-44104,-44141,-44178,-44215,-44252,-44289, + -44326,-44363,-44400,-44437,-44474,-44511,-44548,-44585, + -44622,-44659,-44695,-44732,-44769,-44806,-44842,-44879, + -44915,-44952,-44989,-45025,-45062,-45098,-45135,-45171, + -45207,-45244,-45280,-45316,-45353,-45389,-45425,-45462, + -45498,-45534,-45570,-45606,-45642,-45678,-45714,-45750, + -45786,-45822,-45858,-45894,-45930,-45966,-46002,-46037, + -46073,-46109,-46145,-46180,-46216,-46252,-46287,-46323, + -46358,-46394,-46429,-46465,-46500,-46536,-46571,-46606, + -46642,-46677,-46712,-46747,-46783,-46818,-46853,-46888, + -46923,-46958,-46993,-47028,-47063,-47098,-47133,-47168, + -47203,-47238,-47273,-47308,-47342,-47377,-47412,-47446, + -47481,-47516,-47550,-47585,-47619,-47654,-47688,-47723, + -47757,-47792,-47826,-47860,-47895,-47929,-47963,-47998, + -48032,-48066,-48100,-48134,-48168,-48202,-48236,-48271, + -48304,-48338,-48372,-48406,-48440,-48474,-48508,-48542, + -48575,-48609,-48643,-48676,-48710,-48744,-48777,-48811, + -48844,-48878,-48911,-48945,-48978,-49012,-49045,-49078, + -49112,-49145,-49178,-49211,-49244,-49278,-49311,-49344, + -49377,-49410,-49443,-49476,-49509,-49542,-49575,-49608, + -49640,-49673,-49706,-49739,-49771,-49804,-49837,-49869, + -49902,-49935,-49967,-50000,-50032,-50065,-50097,-50129, + -50162,-50194,-50226,-50259,-50291,-50323,-50355,-50387, + -50420,-50452,-50484,-50516,-50548,-50580,-50612,-50644, + -50675,-50707,-50739,-50771,-50803,-50834,-50866,-50898, + -50929,-50961,-50993,-51024,-51056,-51087,-51119,-51150, + -51182,-51213,-51244,-51276,-51307,-51338,-51369,-51401, + -51432,-51463,-51494,-51525,-51556,-51587,-51618,-51649, + -51680,-51711,-51742,-51773,-51803,-51834,-51865,-51896, + -51926,-51957,-51988,-52018,-52049,-52079,-52110,-52140, + -52171,-52201,-52231,-52262,-52292,-52322,-52353,-52383, + -52413,-52443,-52473,-52503,-52534,-52564,-52594,-52624, + -52653,-52683,-52713,-52743,-52773,-52803,-52832,-52862, + -52892,-52922,-52951,-52981,-53010,-53040,-53069,-53099, + -53128,-53158,-53187,-53216,-53246,-53275,-53304,-53334, + -53363,-53392,-53421,-53450,-53479,-53508,-53537,-53566, + -53595,-53624,-53653,-53682,-53711,-53739,-53768,-53797, + -53826,-53854,-53883,-53911,-53940,-53969,-53997,-54026, + -54054,-54082,-54111,-54139,-54167,-54196,-54224,-54252, + -54280,-54308,-54337,-54365,-54393,-54421,-54449,-54477, + -54505,-54533,-54560,-54588,-54616,-54644,-54672,-54699, + -54727,-54755,-54782,-54810,-54837,-54865,-54892,-54920, + -54947,-54974,-55002,-55029,-55056,-55084,-55111,-55138, + -55165,-55192,-55219,-55246,-55274,-55300,-55327,-55354, + -55381,-55408,-55435,-55462,-55489,-55515,-55542,-55569, + -55595,-55622,-55648,-55675,-55701,-55728,-55754,-55781, + -55807,-55833,-55860,-55886,-55912,-55938,-55965,-55991, + -56017,-56043,-56069,-56095,-56121,-56147,-56173,-56199, + -56225,-56250,-56276,-56302,-56328,-56353,-56379,-56404, + -56430,-56456,-56481,-56507,-56532,-56557,-56583,-56608, + -56633,-56659,-56684,-56709,-56734,-56760,-56785,-56810, + -56835,-56860,-56885,-56910,-56935,-56959,-56984,-57009, + -57034,-57059,-57083,-57108,-57133,-57157,-57182,-57206, + -57231,-57255,-57280,-57304,-57329,-57353,-57377,-57402, + -57426,-57450,-57474,-57498,-57522,-57546,-57570,-57594, + -57618,-57642,-57666,-57690,-57714,-57738,-57762,-57785, + -57809,-57833,-57856,-57880,-57903,-57927,-57950,-57974, + -57997,-58021,-58044,-58067,-58091,-58114,-58137,-58160, + -58183,-58207,-58230,-58253,-58276,-58299,-58322,-58345, + -58367,-58390,-58413,-58436,-58459,-58481,-58504,-58527, + -58549,-58572,-58594,-58617,-58639,-58662,-58684,-58706, + -58729,-58751,-58773,-58795,-58818,-58840,-58862,-58884, + -58906,-58928,-58950,-58972,-58994,-59016,-59038,-59059, + -59081,-59103,-59125,-59146,-59168,-59190,-59211,-59233, + -59254,-59276,-59297,-59318,-59340,-59361,-59382,-59404, + -59425,-59446,-59467,-59488,-59509,-59530,-59551,-59572, + -59593,-59614,-59635,-59656,-59677,-59697,-59718,-59739, + -59759,-59780,-59801,-59821,-59842,-59862,-59883,-59903, + -59923,-59944,-59964,-59984,-60004,-60025,-60045,-60065, + -60085,-60105,-60125,-60145,-60165,-60185,-60205,-60225, + -60244,-60264,-60284,-60304,-60323,-60343,-60363,-60382, + -60402,-60421,-60441,-60460,-60479,-60499,-60518,-60537, + -60556,-60576,-60595,-60614,-60633,-60652,-60671,-60690, + -60709,-60728,-60747,-60766,-60785,-60803,-60822,-60841, + -60859,-60878,-60897,-60915,-60934,-60952,-60971,-60989, + -61007,-61026,-61044,-61062,-61081,-61099,-61117,-61135, + -61153,-61171,-61189,-61207,-61225,-61243,-61261,-61279, + -61297,-61314,-61332,-61350,-61367,-61385,-61403,-61420, + -61438,-61455,-61473,-61490,-61507,-61525,-61542,-61559, + -61577,-61594,-61611,-61628,-61645,-61662,-61679,-61696, + -61713,-61730,-61747,-61764,-61780,-61797,-61814,-61831, + -61847,-61864,-61880,-61897,-61913,-61930,-61946,-61963, + -61979,-61995,-62012,-62028,-62044,-62060,-62076,-62092, + -62108,-62125,-62141,-62156,-62172,-62188,-62204,-62220, + -62236,-62251,-62267,-62283,-62298,-62314,-62329,-62345, + -62360,-62376,-62391,-62407,-62422,-62437,-62453,-62468, + -62483,-62498,-62513,-62528,-62543,-62558,-62573,-62588, + -62603,-62618,-62633,-62648,-62662,-62677,-62692,-62706, + -62721,-62735,-62750,-62764,-62779,-62793,-62808,-62822, + -62836,-62850,-62865,-62879,-62893,-62907,-62921,-62935, + -62949,-62963,-62977,-62991,-63005,-63019,-63032,-63046, + -63060,-63074,-63087,-63101,-63114,-63128,-63141,-63155, + -63168,-63182,-63195,-63208,-63221,-63235,-63248,-63261, + -63274,-63287,-63300,-63313,-63326,-63339,-63352,-63365, + -63378,-63390,-63403,-63416,-63429,-63441,-63454,-63466, + -63479,-63491,-63504,-63516,-63528,-63541,-63553,-63565, + -63578,-63590,-63602,-63614,-63626,-63638,-63650,-63662, + -63674,-63686,-63698,-63709,-63721,-63733,-63745,-63756, + -63768,-63779,-63791,-63803,-63814,-63825,-63837,-63848, + -63859,-63871,-63882,-63893,-63904,-63915,-63927,-63938, + -63949,-63960,-63971,-63981,-63992,-64003,-64014,-64025, + -64035,-64046,-64057,-64067,-64078,-64088,-64099,-64109, + -64120,-64130,-64140,-64151,-64161,-64171,-64181,-64192, + -64202,-64212,-64222,-64232,-64242,-64252,-64261,-64271, + -64281,-64291,-64301,-64310,-64320,-64330,-64339,-64349, + -64358,-64368,-64377,-64387,-64396,-64405,-64414,-64424, + -64433,-64442,-64451,-64460,-64469,-64478,-64487,-64496, + -64505,-64514,-64523,-64532,-64540,-64549,-64558,-64566, + -64575,-64584,-64592,-64601,-64609,-64617,-64626,-64634, + -64642,-64651,-64659,-64667,-64675,-64683,-64691,-64699, + -64707,-64715,-64723,-64731,-64739,-64747,-64754,-64762, + -64770,-64777,-64785,-64793,-64800,-64808,-64815,-64822, + -64830,-64837,-64844,-64852,-64859,-64866,-64873,-64880, + -64887,-64895,-64902,-64908,-64915,-64922,-64929,-64936, + -64943,-64949,-64956,-64963,-64969,-64976,-64982,-64989, + -64995,-65002,-65008,-65015,-65021,-65027,-65033,-65040, + -65046,-65052,-65058,-65064,-65070,-65076,-65082,-65088, + -65094,-65099,-65105,-65111,-65117,-65122,-65128,-65133, + -65139,-65144,-65150,-65155,-65161,-65166,-65171,-65177, + -65182,-65187,-65192,-65197,-65202,-65207,-65212,-65217, + -65222,-65227,-65232,-65237,-65242,-65246,-65251,-65256, + -65260,-65265,-65270,-65274,-65279,-65283,-65287,-65292, + -65296,-65300,-65305,-65309,-65313,-65317,-65321,-65325, + -65329,-65333,-65337,-65341,-65345,-65349,-65352,-65356, + -65360,-65363,-65367,-65371,-65374,-65378,-65381,-65385, + -65388,-65391,-65395,-65398,-65401,-65404,-65408,-65411, + -65414,-65417,-65420,-65423,-65426,-65429,-65431,-65434, + -65437,-65440,-65442,-65445,-65448,-65450,-65453,-65455, + -65458,-65460,-65463,-65465,-65467,-65470,-65472,-65474, + -65476,-65478,-65480,-65482,-65484,-65486,-65488,-65490, + -65492,-65494,-65496,-65497,-65499,-65501,-65502,-65504, + -65505,-65507,-65508,-65510,-65511,-65513,-65514,-65515, + -65516,-65518,-65519,-65520,-65521,-65522,-65523,-65524, + -65525,-65526,-65527,-65527,-65528,-65529,-65530,-65530, + -65531,-65531,-65532,-65532,-65533,-65533,-65534,-65534, + -65534,-65535,-65535,-65535,-65535,-65535,-65535,-65535, + -65535,-65535,-65535,-65535,-65535,-65535,-65535,-65534, + -65534,-65534,-65533,-65533,-65532,-65532,-65531,-65531, + -65530,-65530,-65529,-65528,-65527,-65527,-65526,-65525, + -65524,-65523,-65522,-65521,-65520,-65519,-65518,-65516, + -65515,-65514,-65513,-65511,-65510,-65508,-65507,-65505, + -65504,-65502,-65501,-65499,-65497,-65496,-65494,-65492, + -65490,-65488,-65486,-65484,-65482,-65480,-65478,-65476, + -65474,-65472,-65470,-65467,-65465,-65463,-65460,-65458, + -65455,-65453,-65450,-65448,-65445,-65442,-65440,-65437, + -65434,-65431,-65429,-65426,-65423,-65420,-65417,-65414, + -65411,-65408,-65404,-65401,-65398,-65395,-65391,-65388, + -65385,-65381,-65378,-65374,-65371,-65367,-65363,-65360, + -65356,-65352,-65349,-65345,-65341,-65337,-65333,-65329, + -65325,-65321,-65317,-65313,-65309,-65305,-65300,-65296, + -65292,-65287,-65283,-65279,-65274,-65270,-65265,-65260, + -65256,-65251,-65246,-65242,-65237,-65232,-65227,-65222, + -65217,-65212,-65207,-65202,-65197,-65192,-65187,-65182, + -65177,-65171,-65166,-65161,-65155,-65150,-65144,-65139, + -65133,-65128,-65122,-65117,-65111,-65105,-65099,-65094, + -65088,-65082,-65076,-65070,-65064,-65058,-65052,-65046, + -65040,-65033,-65027,-65021,-65015,-65008,-65002,-64995, + -64989,-64982,-64976,-64969,-64963,-64956,-64949,-64943, + -64936,-64929,-64922,-64915,-64908,-64902,-64895,-64887, + -64880,-64873,-64866,-64859,-64852,-64844,-64837,-64830, + -64822,-64815,-64808,-64800,-64793,-64785,-64777,-64770, + -64762,-64754,-64747,-64739,-64731,-64723,-64715,-64707, + -64699,-64691,-64683,-64675,-64667,-64659,-64651,-64642, + -64634,-64626,-64617,-64609,-64601,-64592,-64584,-64575, + -64566,-64558,-64549,-64540,-64532,-64523,-64514,-64505, + -64496,-64487,-64478,-64469,-64460,-64451,-64442,-64433, + -64424,-64414,-64405,-64396,-64387,-64377,-64368,-64358, + -64349,-64339,-64330,-64320,-64310,-64301,-64291,-64281, + -64271,-64261,-64252,-64242,-64232,-64222,-64212,-64202, + -64192,-64181,-64171,-64161,-64151,-64140,-64130,-64120, + -64109,-64099,-64088,-64078,-64067,-64057,-64046,-64035, + -64025,-64014,-64003,-63992,-63981,-63971,-63960,-63949, + -63938,-63927,-63915,-63904,-63893,-63882,-63871,-63859, + -63848,-63837,-63825,-63814,-63803,-63791,-63779,-63768, + -63756,-63745,-63733,-63721,-63709,-63698,-63686,-63674, + -63662,-63650,-63638,-63626,-63614,-63602,-63590,-63578, + -63565,-63553,-63541,-63528,-63516,-63504,-63491,-63479, + -63466,-63454,-63441,-63429,-63416,-63403,-63390,-63378, + -63365,-63352,-63339,-63326,-63313,-63300,-63287,-63274, + -63261,-63248,-63235,-63221,-63208,-63195,-63182,-63168, + -63155,-63141,-63128,-63114,-63101,-63087,-63074,-63060, + -63046,-63032,-63019,-63005,-62991,-62977,-62963,-62949, + -62935,-62921,-62907,-62893,-62879,-62865,-62850,-62836, + -62822,-62808,-62793,-62779,-62764,-62750,-62735,-62721, + -62706,-62692,-62677,-62662,-62648,-62633,-62618,-62603, + -62588,-62573,-62558,-62543,-62528,-62513,-62498,-62483, + -62468,-62453,-62437,-62422,-62407,-62391,-62376,-62360, + -62345,-62329,-62314,-62298,-62283,-62267,-62251,-62236, + -62220,-62204,-62188,-62172,-62156,-62141,-62125,-62108, + -62092,-62076,-62060,-62044,-62028,-62012,-61995,-61979, + -61963,-61946,-61930,-61913,-61897,-61880,-61864,-61847, + -61831,-61814,-61797,-61780,-61764,-61747,-61730,-61713, + -61696,-61679,-61662,-61645,-61628,-61611,-61594,-61577, + -61559,-61542,-61525,-61507,-61490,-61473,-61455,-61438, + -61420,-61403,-61385,-61367,-61350,-61332,-61314,-61297, + -61279,-61261,-61243,-61225,-61207,-61189,-61171,-61153, + -61135,-61117,-61099,-61081,-61062,-61044,-61026,-61007, + -60989,-60971,-60952,-60934,-60915,-60897,-60878,-60859, + -60841,-60822,-60803,-60785,-60766,-60747,-60728,-60709, + -60690,-60671,-60652,-60633,-60614,-60595,-60576,-60556, + -60537,-60518,-60499,-60479,-60460,-60441,-60421,-60402, + -60382,-60363,-60343,-60323,-60304,-60284,-60264,-60244, + -60225,-60205,-60185,-60165,-60145,-60125,-60105,-60085, + -60065,-60045,-60025,-60004,-59984,-59964,-59944,-59923, + -59903,-59883,-59862,-59842,-59821,-59801,-59780,-59759, + -59739,-59718,-59697,-59677,-59656,-59635,-59614,-59593, + -59572,-59551,-59530,-59509,-59488,-59467,-59446,-59425, + -59404,-59382,-59361,-59340,-59318,-59297,-59276,-59254, + -59233,-59211,-59189,-59168,-59146,-59125,-59103,-59081, + -59059,-59038,-59016,-58994,-58972,-58950,-58928,-58906, + -58884,-58862,-58840,-58818,-58795,-58773,-58751,-58729, + -58706,-58684,-58662,-58639,-58617,-58594,-58572,-58549, + -58527,-58504,-58481,-58459,-58436,-58413,-58390,-58367, + -58345,-58322,-58299,-58276,-58253,-58230,-58207,-58183, + -58160,-58137,-58114,-58091,-58067,-58044,-58021,-57997, + -57974,-57950,-57927,-57903,-57880,-57856,-57833,-57809, + -57785,-57762,-57738,-57714,-57690,-57666,-57642,-57618, + -57594,-57570,-57546,-57522,-57498,-57474,-57450,-57426, + -57402,-57377,-57353,-57329,-57304,-57280,-57255,-57231, + -57206,-57182,-57157,-57133,-57108,-57083,-57059,-57034, + -57009,-56984,-56959,-56935,-56910,-56885,-56860,-56835, + -56810,-56785,-56760,-56734,-56709,-56684,-56659,-56633, + -56608,-56583,-56557,-56532,-56507,-56481,-56456,-56430, + -56404,-56379,-56353,-56328,-56302,-56276,-56250,-56225, + -56199,-56173,-56147,-56121,-56095,-56069,-56043,-56017, + -55991,-55965,-55938,-55912,-55886,-55860,-55833,-55807, + -55781,-55754,-55728,-55701,-55675,-55648,-55622,-55595, + -55569,-55542,-55515,-55489,-55462,-55435,-55408,-55381, + -55354,-55327,-55300,-55274,-55246,-55219,-55192,-55165, + -55138,-55111,-55084,-55056,-55029,-55002,-54974,-54947, + -54920,-54892,-54865,-54837,-54810,-54782,-54755,-54727, + -54699,-54672,-54644,-54616,-54588,-54560,-54533,-54505, + -54477,-54449,-54421,-54393,-54365,-54337,-54308,-54280, + -54252,-54224,-54196,-54167,-54139,-54111,-54082,-54054, + -54026,-53997,-53969,-53940,-53911,-53883,-53854,-53826, + -53797,-53768,-53739,-53711,-53682,-53653,-53624,-53595, + -53566,-53537,-53508,-53479,-53450,-53421,-53392,-53363, + -53334,-53304,-53275,-53246,-53216,-53187,-53158,-53128, + -53099,-53069,-53040,-53010,-52981,-52951,-52922,-52892, + -52862,-52832,-52803,-52773,-52743,-52713,-52683,-52653, + -52624,-52594,-52564,-52534,-52503,-52473,-52443,-52413, + -52383,-52353,-52322,-52292,-52262,-52231,-52201,-52171, + -52140,-52110,-52079,-52049,-52018,-51988,-51957,-51926, + -51896,-51865,-51834,-51803,-51773,-51742,-51711,-51680, + -51649,-51618,-51587,-51556,-51525,-51494,-51463,-51432, + -51401,-51369,-51338,-51307,-51276,-51244,-51213,-51182, + -51150,-51119,-51087,-51056,-51024,-50993,-50961,-50929, + -50898,-50866,-50834,-50803,-50771,-50739,-50707,-50675, + -50644,-50612,-50580,-50548,-50516,-50484,-50452,-50420, + -50387,-50355,-50323,-50291,-50259,-50226,-50194,-50162, + -50129,-50097,-50065,-50032,-50000,-49967,-49935,-49902, + -49869,-49837,-49804,-49771,-49739,-49706,-49673,-49640, + -49608,-49575,-49542,-49509,-49476,-49443,-49410,-49377, + -49344,-49311,-49278,-49244,-49211,-49178,-49145,-49112, + -49078,-49045,-49012,-48978,-48945,-48911,-48878,-48844, + -48811,-48777,-48744,-48710,-48676,-48643,-48609,-48575, + -48542,-48508,-48474,-48440,-48406,-48372,-48338,-48305, + -48271,-48237,-48202,-48168,-48134,-48100,-48066,-48032, + -47998,-47963,-47929,-47895,-47860,-47826,-47792,-47757, + -47723,-47688,-47654,-47619,-47585,-47550,-47516,-47481, + -47446,-47412,-47377,-47342,-47307,-47273,-47238,-47203, + -47168,-47133,-47098,-47063,-47028,-46993,-46958,-46923, + -46888,-46853,-46818,-46783,-46747,-46712,-46677,-46642, + -46606,-46571,-46536,-46500,-46465,-46429,-46394,-46358, + -46323,-46287,-46251,-46216,-46180,-46145,-46109,-46073, + -46037,-46002,-45966,-45930,-45894,-45858,-45822,-45786, + -45750,-45714,-45678,-45642,-45606,-45570,-45534,-45498, + -45462,-45425,-45389,-45353,-45316,-45280,-45244,-45207, + -45171,-45135,-45098,-45062,-45025,-44989,-44952,-44915, + -44879,-44842,-44806,-44769,-44732,-44695,-44659,-44622, + -44585,-44548,-44511,-44474,-44437,-44400,-44363,-44326, + -44289,-44252,-44215,-44178,-44141,-44104,-44067,-44029, + -43992,-43955,-43918,-43880,-43843,-43806,-43768,-43731, + -43693,-43656,-43618,-43581,-43543,-43506,-43468,-43430, + -43393,-43355,-43317,-43280,-43242,-43204,-43166,-43128, + -43091,-43053,-43015,-42977,-42939,-42901,-42863,-42825, + -42787,-42749,-42711,-42672,-42634,-42596,-42558,-42520, + -42481,-42443,-42405,-42366,-42328,-42290,-42251,-42213, + -42174,-42136,-42097,-42059,-42020,-41982,-41943,-41904, + -41866,-41827,-41788,-41750,-41711,-41672,-41633,-41595, + -41556,-41517,-41478,-41439,-41400,-41361,-41322,-41283, + -41244,-41205,-41166,-41127,-41087,-41048,-41009,-40970, + -40931,-40891,-40852,-40813,-40773,-40734,-40695,-40655, + -40616,-40576,-40537,-40497,-40458,-40418,-40379,-40339, + -40299,-40260,-40220,-40180,-40141,-40101,-40061,-40021, + -39982,-39942,-39902,-39862,-39822,-39782,-39742,-39702, + -39662,-39622,-39582,-39542,-39502,-39462,-39422,-39382, + -39341,-39301,-39261,-39221,-39180,-39140,-39100,-39059, + -39019,-38979,-38938,-38898,-38857,-38817,-38776,-38736, + -38695,-38655,-38614,-38573,-38533,-38492,-38451,-38411, + -38370,-38329,-38288,-38248,-38207,-38166,-38125,-38084, + -38043,-38002,-37961,-37920,-37879,-37838,-37797,-37756, + -37715,-37674,-37633,-37592,-37550,-37509,-37468,-37427, + -37386,-37344,-37303,-37262,-37220,-37179,-37137,-37096, + -37055,-37013,-36972,-36930,-36889,-36847,-36805,-36764, + -36722,-36681,-36639,-36597,-36556,-36514,-36472,-36430, + -36388,-36347,-36305,-36263,-36221,-36179,-36137,-36095, + -36053,-36011,-35969,-35927,-35885,-35843,-35801,-35759, + -35717,-35675,-35633,-35590,-35548,-35506,-35464,-35421, + -35379,-35337,-35294,-35252,-35210,-35167,-35125,-35082, + -35040,-34997,-34955,-34912,-34870,-34827,-34785,-34742, + -34699,-34657,-34614,-34571,-34529,-34486,-34443,-34400, + -34358,-34315,-34272,-34229,-34186,-34143,-34100,-34057, + -34015,-33972,-33929,-33886,-33843,-33799,-33756,-33713, + -33670,-33627,-33584,-33541,-33498,-33454,-33411,-33368, + -33325,-33281,-33238,-33195,-33151,-33108,-33065,-33021, + -32978,-32934,-32891,-32847,-32804,-32760,-32717,-32673, + -32630,-32586,-32542,-32499,-32455,-32411,-32368,-32324, + -32280,-32236,-32193,-32149,-32105,-32061,-32017,-31974, + -31930,-31886,-31842,-31798,-31754,-31710,-31666,-31622, + -31578,-31534,-31490,-31446,-31402,-31357,-31313,-31269, + -31225,-31181,-31136,-31092,-31048,-31004,-30959,-30915, + -30871,-30826,-30782,-30738,-30693,-30649,-30604,-30560, + -30515,-30471,-30426,-30382,-30337,-30293,-30248,-30204, + -30159,-30114,-30070,-30025,-29980,-29936,-29891,-29846, + -29801,-29757,-29712,-29667,-29622,-29577,-29533,-29488, + -29443,-29398,-29353,-29308,-29263,-29218,-29173,-29128, + -29083,-29038,-28993,-28948,-28903,-28858,-28812,-28767, + -28722,-28677,-28632,-28586,-28541,-28496,-28451,-28405, + -28360,-28315,-28269,-28224,-28179,-28133,-28088,-28042, + -27997,-27952,-27906,-27861,-27815,-27770,-27724,-27678, + -27633,-27587,-27542,-27496,-27450,-27405,-27359,-27313, + -27268,-27222,-27176,-27131,-27085,-27039,-26993,-26947, + -26902,-26856,-26810,-26764,-26718,-26672,-26626,-26580, + -26534,-26488,-26442,-26396,-26350,-26304,-26258,-26212, + -26166,-26120,-26074,-26028,-25982,-25936,-25889,-25843, + -25797,-25751,-25705,-25658,-25612,-25566,-25520,-25473, + -25427,-25381,-25334,-25288,-25241,-25195,-25149,-25102, + -25056,-25009,-24963,-24916,-24870,-24823,-24777,-24730, + -24684,-24637,-24591,-24544,-24497,-24451,-24404,-24357, + -24311,-24264,-24217,-24171,-24124,-24077,-24030,-23984, + -23937,-23890,-23843,-23796,-23750,-23703,-23656,-23609, + -23562,-23515,-23468,-23421,-23374,-23327,-23280,-23233, + -23186,-23139,-23092,-23045,-22998,-22951,-22904,-22857, + -22810,-22763,-22716,-22668,-22621,-22574,-22527,-22480, + -22432,-22385,-22338,-22291,-22243,-22196,-22149,-22102, + -22054,-22007,-21960,-21912,-21865,-21817,-21770,-21723, + -21675,-21628,-21580,-21533,-21485,-21438,-21390,-21343, + -21295,-21248,-21200,-21153,-21105,-21057,-21010,-20962, + -20915,-20867,-20819,-20772,-20724,-20676,-20629,-20581, + -20533,-20485,-20438,-20390,-20342,-20294,-20246,-20199, + -20151,-20103,-20055,-20007,-19959,-19912,-19864,-19816, + -19768,-19720,-19672,-19624,-19576,-19528,-19480,-19432, + -19384,-19336,-19288,-19240,-19192,-19144,-19096,-19048, + -19000,-18951,-18903,-18855,-18807,-18759,-18711,-18663, + -18614,-18566,-18518,-18470,-18421,-18373,-18325,-18277, + -18228,-18180,-18132,-18084,-18035,-17987,-17939,-17890, + -17842,-17793,-17745,-17697,-17648,-17600,-17551,-17503, + -17455,-17406,-17358,-17309,-17261,-17212,-17164,-17115, + -17067,-17018,-16970,-16921,-16872,-16824,-16775,-16727, + -16678,-16629,-16581,-16532,-16484,-16435,-16386,-16338, + -16289,-16240,-16191,-16143,-16094,-16045,-15997,-15948, + -15899,-15850,-15802,-15753,-15704,-15655,-15606,-15557, + -15509,-15460,-15411,-15362,-15313,-15264,-15215,-15167, + -15118,-15069,-15020,-14971,-14922,-14873,-14824,-14775, + -14726,-14677,-14628,-14579,-14530,-14481,-14432,-14383, + -14334,-14285,-14236,-14187,-14138,-14089,-14040,-13990, + -13941,-13892,-13843,-13794,-13745,-13696,-13647,-13597, + -13548,-13499,-13450,-13401,-13351,-13302,-13253,-13204, + -13154,-13105,-13056,-13007,-12957,-12908,-12859,-12810, + -12760,-12711,-12662,-12612,-12563,-12514,-12464,-12415, + -12366,-12316,-12267,-12217,-12168,-12119,-12069,-12020, + -11970,-11921,-11872,-11822,-11773,-11723,-11674,-11624, + -11575,-11525,-11476,-11426,-11377,-11327,-11278,-11228, + -11179,-11129,-11080,-11030,-10981,-10931,-10882,-10832, + -10782,-10733,-10683,-10634,-10584,-10534,-10485,-10435, + -10386,-10336,-10286,-10237,-10187,-10137,-10088,-10038, + -9988,-9939,-9889,-9839,-9790,-9740,-9690,-9640, + -9591,-9541,-9491,-9442,-9392,-9342,-9292,-9243, + -9193,-9143,-9093,-9043,-8994,-8944,-8894,-8844, + -8794,-8745,-8695,-8645,-8595,-8545,-8496,-8446, + -8396,-8346,-8296,-8246,-8196,-8147,-8097,-8047, + -7997,-7947,-7897,-7847,-7797,-7747,-7697,-7648, + -7598,-7548,-7498,-7448,-7398,-7348,-7298,-7248, + -7198,-7148,-7098,-7048,-6998,-6948,-6898,-6848, + -6798,-6748,-6698,-6648,-6598,-6548,-6498,-6448, + -6398,-6348,-6298,-6248,-6198,-6148,-6098,-6048, + -5998,-5948,-5898,-5848,-5798,-5747,-5697,-5647, + -5597,-5547,-5497,-5447,-5397,-5347,-5297,-5247, + -5197,-5146,-5096,-5046,-4996,-4946,-4896,-4846, + -4796,-4745,-4695,-4645,-4595,-4545,-4495,-4445, + -4394,-4344,-4294,-4244,-4194,-4144,-4093,-4043, + -3993,-3943,-3893,-3843,-3792,-3742,-3692,-3642, + -3592,-3541,-3491,-3441,-3391,-3341,-3291,-3240, + -3190,-3140,-3090,-3039,-2989,-2939,-2889,-2839, + -2788,-2738,-2688,-2638,-2588,-2537,-2487,-2437, + -2387,-2336,-2286,-2236,-2186,-2135,-2085,-2035, + -1985,-1934,-1884,-1834,-1784,-1733,-1683,-1633, + -1583,-1532,-1482,-1432,-1382,-1331,-1281,-1231, + -1181,-1130,-1080,-1030,-980,-929,-879,-829, + -779,-728,-678,-628,-578,-527,-477,-427, + -376,-326,-276,-226,-175,-125,-75,-25, + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1130,1181, + 1231,1281,1331,1382,1432,1482,1532,1583, + 1633,1683,1733,1784,1834,1884,1934,1985, + 2035,2085,2135,2186,2236,2286,2336,2387, + 2437,2487,2537,2587,2638,2688,2738,2788, + 2839,2889,2939,2989,3039,3090,3140,3190, + 3240,3291,3341,3391,3441,3491,3542,3592, + 3642,3692,3742,3792,3843,3893,3943,3993, + 4043,4093,4144,4194,4244,4294,4344,4394, + 4445,4495,4545,4595,4645,4695,4745,4796, + 4846,4896,4946,4996,5046,5096,5146,5197, + 5247,5297,5347,5397,5447,5497,5547,5597, + 5647,5697,5747,5798,5848,5898,5948,5998, + 6048,6098,6148,6198,6248,6298,6348,6398, + 6448,6498,6548,6598,6648,6698,6748,6798, + 6848,6898,6948,6998,7048,7098,7148,7198, + 7248,7298,7348,7398,7448,7498,7548,7598, + 7648,7697,7747,7797,7847,7897,7947,7997, + 8047,8097,8147,8196,8246,8296,8346,8396, + 8446,8496,8545,8595,8645,8695,8745,8794, + 8844,8894,8944,8994,9043,9093,9143,9193, + 9243,9292,9342,9392,9442,9491,9541,9591, + 9640,9690,9740,9790,9839,9889,9939,9988, + 10038,10088,10137,10187,10237,10286,10336,10386, + 10435,10485,10534,10584,10634,10683,10733,10782, + 10832,10882,10931,10981,11030,11080,11129,11179, + 11228,11278,11327,11377,11426,11476,11525,11575, + 11624,11674,11723,11773,11822,11872,11921,11970, + 12020,12069,12119,12168,12218,12267,12316,12366, + 12415,12464,12514,12563,12612,12662,12711,12760, + 12810,12859,12908,12957,13007,13056,13105,13154, + 13204,13253,13302,13351,13401,13450,13499,13548, + 13597,13647,13696,13745,13794,13843,13892,13941, + 13990,14040,14089,14138,14187,14236,14285,14334, + 14383,14432,14481,14530,14579,14628,14677,14726, + 14775,14824,14873,14922,14971,15020,15069,15118, + 15167,15215,15264,15313,15362,15411,15460,15509, + 15557,15606,15655,15704,15753,15802,15850,15899, + 15948,15997,16045,16094,16143,16191,16240,16289, + 16338,16386,16435,16484,16532,16581,16629,16678, + 16727,16775,16824,16872,16921,16970,17018,17067, + 17115,17164,17212,17261,17309,17358,17406,17455, + 17503,17551,17600,17648,17697,17745,17793,17842, + 17890,17939,17987,18035,18084,18132,18180,18228, + 18277,18325,18373,18421,18470,18518,18566,18614, + 18663,18711,18759,18807,18855,18903,18951,19000, + 19048,19096,19144,19192,19240,19288,19336,19384, + 19432,19480,19528,19576,19624,19672,19720,19768, + 19816,19864,19912,19959,20007,20055,20103,20151, + 20199,20246,20294,20342,20390,20438,20485,20533, + 20581,20629,20676,20724,20772,20819,20867,20915, + 20962,21010,21057,21105,21153,21200,21248,21295, + 21343,21390,21438,21485,21533,21580,21628,21675, + 21723,21770,21817,21865,21912,21960,22007,22054, + 22102,22149,22196,22243,22291,22338,22385,22432, + 22480,22527,22574,22621,22668,22716,22763,22810, + 22857,22904,22951,22998,23045,23092,23139,23186, + 23233,23280,23327,23374,23421,23468,23515,23562, + 23609,23656,23703,23750,23796,23843,23890,23937, + 23984,24030,24077,24124,24171,24217,24264,24311, + 24357,24404,24451,24497,24544,24591,24637,24684, + 24730,24777,24823,24870,24916,24963,25009,25056, + 25102,25149,25195,25241,25288,25334,25381,25427, + 25473,25520,25566,25612,25658,25705,25751,25797, + 25843,25889,25936,25982,26028,26074,26120,26166, + 26212,26258,26304,26350,26396,26442,26488,26534, + 26580,26626,26672,26718,26764,26810,26856,26902, + 26947,26993,27039,27085,27131,27176,27222,27268, + 27313,27359,27405,27450,27496,27542,27587,27633, + 27678,27724,27770,27815,27861,27906,27952,27997, + 28042,28088,28133,28179,28224,28269,28315,28360, + 28405,28451,28496,28541,28586,28632,28677,28722, + 28767,28812,28858,28903,28948,28993,29038,29083, + 29128,29173,29218,29263,29308,29353,29398,29443, + 29488,29533,29577,29622,29667,29712,29757,29801, + 29846,29891,29936,29980,30025,30070,30114,30159, + 30204,30248,30293,30337,30382,30427,30471,30516, + 30560,30604,30649,30693,30738,30782,30826,30871, + 30915,30959,31004,31048,31092,31136,31181,31225, + 31269,31313,31357,31402,31446,31490,31534,31578, + 31622,31666,31710,31754,31798,31842,31886,31930, + 31974,32017,32061,32105,32149,32193,32236,32280, + 32324,32368,32411,32455,32499,32542,32586,32630, + 32673,32717,32760,32804,32847,32891,32934,32978, + 33021,33065,33108,33151,33195,33238,33281,33325, + 33368,33411,33454,33498,33541,33584,33627,33670, + 33713,33756,33799,33843,33886,33929,33972,34015, + 34057,34100,34143,34186,34229,34272,34315,34358, + 34400,34443,34486,34529,34571,34614,34657,34699, + 34742,34785,34827,34870,34912,34955,34997,35040, + 35082,35125,35167,35210,35252,35294,35337,35379, + 35421,35464,35506,35548,35590,35633,35675,35717, + 35759,35801,35843,35885,35927,35969,36011,36053, + 36095,36137,36179,36221,36263,36305,36347,36388, + 36430,36472,36514,36556,36597,36639,36681,36722, + 36764,36805,36847,36889,36930,36972,37013,37055, + 37096,37137,37179,37220,37262,37303,37344,37386, + 37427,37468,37509,37551,37592,37633,37674,37715, + 37756,37797,37838,37879,37920,37961,38002,38043, + 38084,38125,38166,38207,38248,38288,38329,38370, + 38411,38451,38492,38533,38573,38614,38655,38695, + 38736,38776,38817,38857,38898,38938,38979,39019, + 39059,39100,39140,39180,39221,39261,39301,39341, + 39382,39422,39462,39502,39542,39582,39622,39662, + 39702,39742,39782,39822,39862,39902,39942,39982, + 40021,40061,40101,40141,40180,40220,40260,40299, + 40339,40379,40418,40458,40497,40537,40576,40616, + 40655,40695,40734,40773,40813,40852,40891,40931, + 40970,41009,41048,41087,41127,41166,41205,41244, + 41283,41322,41361,41400,41439,41478,41517,41556, + 41595,41633,41672,41711,41750,41788,41827,41866, + 41904,41943,41982,42020,42059,42097,42136,42174, + 42213,42251,42290,42328,42366,42405,42443,42481, + 42520,42558,42596,42634,42672,42711,42749,42787, + 42825,42863,42901,42939,42977,43015,43053,43091, + 43128,43166,43204,43242,43280,43317,43355,43393, + 43430,43468,43506,43543,43581,43618,43656,43693, + 43731,43768,43806,43843,43880,43918,43955,43992, + 44029,44067,44104,44141,44178,44215,44252,44289, + 44326,44363,44400,44437,44474,44511,44548,44585, + 44622,44659,44695,44732,44769,44806,44842,44879, + 44915,44952,44989,45025,45062,45098,45135,45171, + 45207,45244,45280,45316,45353,45389,45425,45462, + 45498,45534,45570,45606,45642,45678,45714,45750, + 45786,45822,45858,45894,45930,45966,46002,46037, + 46073,46109,46145,46180,46216,46252,46287,46323, + 46358,46394,46429,46465,46500,46536,46571,46606, + 46642,46677,46712,46747,46783,46818,46853,46888, + 46923,46958,46993,47028,47063,47098,47133,47168, + 47203,47238,47273,47308,47342,47377,47412,47446, + 47481,47516,47550,47585,47619,47654,47688,47723, + 47757,47792,47826,47861,47895,47929,47963,47998, + 48032,48066,48100,48134,48168,48202,48237,48271, + 48305,48338,48372,48406,48440,48474,48508,48542, + 48575,48609,48643,48676,48710,48744,48777,48811, + 48844,48878,48911,48945,48978,49012,49045,49078, + 49112,49145,49178,49211,49244,49278,49311,49344, + 49377,49410,49443,49476,49509,49542,49575,49608, + 49640,49673,49706,49739,49771,49804,49837,49869, + 49902,49935,49967,50000,50032,50064,50097,50129, + 50162,50194,50226,50259,50291,50323,50355,50387, + 50420,50452,50484,50516,50548,50580,50612,50644, + 50675,50707,50739,50771,50803,50834,50866,50898, + 50929,50961,50993,51024,51056,51087,51119,51150, + 51182,51213,51244,51276,51307,51338,51369,51401, + 51432,51463,51494,51525,51556,51587,51618,51649, + 51680,51711,51742,51773,51803,51834,51865,51896, + 51926,51957,51988,52018,52049,52079,52110,52140, + 52171,52201,52231,52262,52292,52322,52353,52383, + 52413,52443,52473,52503,52534,52564,52594,52624, + 52653,52683,52713,52743,52773,52803,52832,52862, + 52892,52922,52951,52981,53010,53040,53069,53099, + 53128,53158,53187,53216,53246,53275,53304,53334, + 53363,53392,53421,53450,53479,53508,53537,53566, + 53595,53624,53653,53682,53711,53739,53768,53797, + 53826,53854,53883,53912,53940,53969,53997,54026, + 54054,54082,54111,54139,54167,54196,54224,54252, + 54280,54309,54337,54365,54393,54421,54449,54477, + 54505,54533,54560,54588,54616,54644,54672,54699, + 54727,54755,54782,54810,54837,54865,54892,54920, + 54947,54974,55002,55029,55056,55084,55111,55138, + 55165,55192,55219,55246,55274,55300,55327,55354, + 55381,55408,55435,55462,55489,55515,55542,55569, + 55595,55622,55648,55675,55701,55728,55754,55781, + 55807,55833,55860,55886,55912,55938,55965,55991, + 56017,56043,56069,56095,56121,56147,56173,56199, + 56225,56250,56276,56302,56328,56353,56379,56404, + 56430,56456,56481,56507,56532,56557,56583,56608, + 56633,56659,56684,56709,56734,56760,56785,56810, + 56835,56860,56885,56910,56935,56959,56984,57009, + 57034,57059,57083,57108,57133,57157,57182,57206, + 57231,57255,57280,57304,57329,57353,57377,57402, + 57426,57450,57474,57498,57522,57546,57570,57594, + 57618,57642,57666,57690,57714,57738,57762,57785, + 57809,57833,57856,57880,57903,57927,57950,57974, + 57997,58021,58044,58067,58091,58114,58137,58160, + 58183,58207,58230,58253,58276,58299,58322,58345, + 58367,58390,58413,58436,58459,58481,58504,58527, + 58549,58572,58594,58617,58639,58662,58684,58706, + 58729,58751,58773,58795,58818,58840,58862,58884, + 58906,58928,58950,58972,58994,59016,59038,59059, + 59081,59103,59125,59146,59168,59190,59211,59233, + 59254,59276,59297,59318,59340,59361,59382,59404, + 59425,59446,59467,59488,59509,59530,59551,59572, + 59593,59614,59635,59656,59677,59697,59718,59739, + 59759,59780,59801,59821,59842,59862,59883,59903, + 59923,59944,59964,59984,60004,60025,60045,60065, + 60085,60105,60125,60145,60165,60185,60205,60225, + 60244,60264,60284,60304,60323,60343,60363,60382, + 60402,60421,60441,60460,60479,60499,60518,60537, + 60556,60576,60595,60614,60633,60652,60671,60690, + 60709,60728,60747,60766,60785,60803,60822,60841, + 60859,60878,60897,60915,60934,60952,60971,60989, + 61007,61026,61044,61062,61081,61099,61117,61135, + 61153,61171,61189,61207,61225,61243,61261,61279, + 61297,61314,61332,61350,61367,61385,61403,61420, + 61438,61455,61473,61490,61507,61525,61542,61559, + 61577,61594,61611,61628,61645,61662,61679,61696, + 61713,61730,61747,61764,61780,61797,61814,61831, + 61847,61864,61880,61897,61913,61930,61946,61963, + 61979,61995,62012,62028,62044,62060,62076,62092, + 62108,62125,62141,62156,62172,62188,62204,62220, + 62236,62251,62267,62283,62298,62314,62329,62345, + 62360,62376,62391,62407,62422,62437,62453,62468, + 62483,62498,62513,62528,62543,62558,62573,62588, + 62603,62618,62633,62648,62662,62677,62692,62706, + 62721,62735,62750,62764,62779,62793,62808,62822, + 62836,62850,62865,62879,62893,62907,62921,62935, + 62949,62963,62977,62991,63005,63019,63032,63046, + 63060,63074,63087,63101,63114,63128,63141,63155, + 63168,63182,63195,63208,63221,63235,63248,63261, + 63274,63287,63300,63313,63326,63339,63352,63365, + 63378,63390,63403,63416,63429,63441,63454,63466, + 63479,63491,63504,63516,63528,63541,63553,63565, + 63578,63590,63602,63614,63626,63638,63650,63662, + 63674,63686,63698,63709,63721,63733,63745,63756, + 63768,63779,63791,63803,63814,63825,63837,63848, + 63859,63871,63882,63893,63904,63915,63927,63938, + 63949,63960,63971,63981,63992,64003,64014,64025, + 64035,64046,64057,64067,64078,64088,64099,64109, + 64120,64130,64140,64151,64161,64171,64181,64192, + 64202,64212,64222,64232,64242,64252,64261,64271, + 64281,64291,64301,64310,64320,64330,64339,64349, + 64358,64368,64377,64387,64396,64405,64414,64424, + 64433,64442,64451,64460,64469,64478,64487,64496, + 64505,64514,64523,64532,64540,64549,64558,64566, + 64575,64584,64592,64600,64609,64617,64626,64634, + 64642,64651,64659,64667,64675,64683,64691,64699, + 64707,64715,64723,64731,64739,64747,64754,64762, + 64770,64777,64785,64793,64800,64808,64815,64822, + 64830,64837,64844,64852,64859,64866,64873,64880, + 64887,64895,64902,64908,64915,64922,64929,64936, + 64943,64949,64956,64963,64969,64976,64982,64989, + 64995,65002,65008,65015,65021,65027,65033,65040, + 65046,65052,65058,65064,65070,65076,65082,65088, + 65094,65099,65105,65111,65117,65122,65128,65133, + 65139,65144,65150,65155,65161,65166,65171,65177, + 65182,65187,65192,65197,65202,65207,65212,65217, + 65222,65227,65232,65237,65242,65246,65251,65256, + 65260,65265,65270,65274,65279,65283,65287,65292, + 65296,65300,65305,65309,65313,65317,65321,65325, + 65329,65333,65337,65341,65345,65349,65352,65356, + 65360,65363,65367,65371,65374,65378,65381,65385, + 65388,65391,65395,65398,65401,65404,65408,65411, + 65414,65417,65420,65423,65426,65429,65431,65434, + 65437,65440,65442,65445,65448,65450,65453,65455, + 65458,65460,65463,65465,65467,65470,65472,65474, + 65476,65478,65480,65482,65484,65486,65488,65490, + 65492,65494,65496,65497,65499,65501,65502,65504, + 65505,65507,65508,65510,65511,65513,65514,65515, + 65516,65518,65519,65520,65521,65522,65523,65524, + 65525,65526,65527,65527,65528,65529,65530,65530, + 65531,65531,65532,65532,65533,65533,65534,65534, + 65534,65535,65535,65535,65535,65535,65535,65535 +}; + +const fixed_t *finecosine = &finesine[FINEANGLES/4]; + +const angle_t tantoangle[2049] = +{ + 0,333772,667544,1001315,1335086,1668857,2002626,2336395, + 2670163,3003929,3337694,3671457,4005219,4338979,4672736,5006492, + 5340245,5673995,6007743,6341488,6675230,7008968,7342704,7676435, + 8010164,8343888,8677609,9011325,9345037,9678744,10012447,10346145, + 10679838,11013526,11347209,11680887,12014558,12348225,12681885,13015539, + 13349187,13682829,14016464,14350092,14683714,15017328,15350936,15684536, + 16018129,16351714,16685291,17018860,17352422,17685974,18019518,18353054, + 18686582,19020100,19353610,19687110,20020600,20354080,20687552,21021014, + 21354466,21687906,22021338,22354758,22688168,23021568,23354956,23688332, + 24021698,24355052,24688396,25021726,25355046,25688352,26021648,26354930, + 26688200,27021456,27354702,27687932,28021150,28354356,28687548,29020724, + 29353888,29687038,30020174,30353296,30686404,31019496,31352574,31685636, + 32018684,32351718,32684734,33017736,33350722,33683692,34016648,34349584, + 34682508,35015412,35348300,35681172,36014028,36346868,36679688,37012492, + 37345276,37678044,38010792,38343524,38676240,39008936,39341612,39674272, + 40006912,40339532,40672132,41004716,41337276,41669820,42002344,42334848, + 42667332,42999796,43332236,43664660,43997060,44329444,44661800,44994140, + 45326456,45658752,45991028,46323280,46655512,46987720,47319908,47652072, + 47984212,48316332,48648428,48980500,49312548,49644576,49976580,50308556, + 50640512,50972444,51304352,51636236,51968096,52299928,52631740,52963524, + 53295284,53627020,53958728,54290412,54622068,54953704,55285308,55616888, + 55948444,56279972,56611472,56942948,57274396,57605816,57937212,58268576, + 58599916,58931228,59262512,59593768,59924992,60256192,60587364,60918508, + 61249620,61580704,61911760,62242788,62573788,62904756,63235692,63566604, + 63897480,64228332,64559148,64889940,65220696,65551424,65882120,66212788, + 66543420,66874024,67204600,67535136,67865648,68196120,68526568,68856984, + 69187360,69517712,69848024,70178304,70508560,70838776,71168960,71499112, + 71829224,72159312,72489360,72819376,73149360,73479304,73809216,74139096, + 74468936,74798744,75128520,75458264,75787968,76117632,76447264,76776864, + 77106424,77435952,77765440,78094888,78424304,78753688,79083032,79412336, + 79741608,80070840,80400032,80729192,81058312,81387392,81716432,82045440, + 82374408,82703336,83032224,83361080,83689896,84018664,84347400,84676096, + 85004760,85333376,85661952,85990488,86318984,86647448,86975864,87304240, + 87632576,87960872,88289128,88617344,88945520,89273648,89601736,89929792, + 90257792,90585760,90913688,91241568,91569408,91897200,92224960,92552672, + 92880336,93207968,93535552,93863088,94190584,94518040,94845448,95172816, + 95500136,95827416,96154648,96481832,96808976,97136080,97463136,97790144, + 98117112,98444032,98770904,99097736,99424520,99751256,100077944,100404592, + 100731192,101057744,101384248,101710712,102037128,102363488,102689808,103016080, + 103342312,103668488,103994616,104320696,104646736,104972720,105298656,105624552, + 105950392,106276184,106601928,106927624,107253272,107578872,107904416,108229920, + 108555368,108880768,109206120,109531416,109856664,110181872,110507016,110832120, + 111157168,111482168,111807112,112132008,112456856,112781648,113106392,113431080, + 113755720,114080312,114404848,114729328,115053760,115378136,115702464,116026744, + 116350960,116675128,116999248,117323312,117647320,117971272,118295176,118619024, + 118942816,119266560,119590248,119913880,120237456,120560984,120884456,121207864, + 121531224,121854528,122177784,122500976,122824112,123147200,123470224,123793200, + 124116120,124438976,124761784,125084528,125407224,125729856,126052432,126374960, + 126697424,127019832,127342184,127664472,127986712,128308888,128631008,128953072, + 129275080,129597024,129918912,130240744,130562520,130884232,131205888,131527480, + 131849016,132170496,132491912,132813272,133134576,133455816,133776992,134098120, + 134419184,134740176,135061120,135382000,135702816,136023584,136344272,136664912, + 136985488,137306016,137626464,137946864,138267184,138587456,138907664,139227808, + 139547904,139867920,140187888,140507776,140827616,141147392,141467104,141786752, + 142106336,142425856,142745312,143064720,143384048,143703312,144022512,144341664, + 144660736,144979744,145298704,145617584,145936400,146255168,146573856,146892480, + 147211040,147529536,147847968,148166336,148484640,148802880,149121056,149439152, + 149757200,150075168,150393072,150710912,151028688,151346400,151664048,151981616, + 152299136,152616576,152933952,153251264,153568496,153885680,154202784,154519824, + 154836784,155153696,155470528,155787296,156104000,156420624,156737200,157053696, + 157370112,157686480,158002768,158318976,158635136,158951216,159267232,159583168, + 159899040,160214848,160530592,160846256,161161840,161477376,161792832,162108208, + 162423520,162738768,163053952,163369040,163684080,163999040,164313936,164628752, + 164943504,165258176,165572784,165887312,166201776,166516160,166830480,167144736, + 167458912,167773008,168087040,168400992,168714880,169028688,169342432,169656096, + 169969696,170283216,170596672,170910032,171223344,171536576,171849728,172162800, + 172475808,172788736,173101600,173414384,173727104,174039728,174352288,174664784, + 174977200,175289536,175601792,175913984,176226096,176538144,176850096,177161984, + 177473792,177785536,178097200,178408784,178720288,179031728,179343088,179654368, + 179965568,180276704,180587744,180898720,181209616,181520448,181831184,182141856, + 182452448,182762960,183073408,183383760,183694048,184004240,184314368,184624416, + 184934400,185244288,185554096,185863840,186173504,186483072,186792576,187102000, + 187411344,187720608,188029808,188338912,188647936,188956896,189265760,189574560, + 189883264,190191904,190500448,190808928,191117312,191425632,191733872,192042016, + 192350096,192658096,192966000,193273840,193581584,193889264,194196848,194504352, + 194811792,195119136,195426400,195733584,196040688,196347712,196654656,196961520, + 197268304,197574992,197881616,198188144,198494592,198800960,199107248,199413456, + 199719584,200025616,200331584,200637456,200943248,201248960,201554576,201860128, + 202165584,202470960,202776256,203081456,203386592,203691632,203996592,204301472, + 204606256,204910976,205215600,205520144,205824592,206128960,206433248,206737456, + 207041584,207345616,207649568,207953424,208257216,208560912,208864512,209168048, + 209471488,209774832,210078112,210381296,210684384,210987408,211290336,211593184, + 211895936,212198608,212501184,212803680,213106096,213408432,213710672,214012816, + 214314880,214616864,214918768,215220576,215522288,215823920,216125472,216426928, + 216728304,217029584,217330784,217631904,217932928,218233856,218534704,218835472, + 219136144,219436720,219737216,220037632,220337952,220638192,220938336,221238384, + 221538352,221838240,222138032,222437728,222737344,223036880,223336304,223635664, + 223934912,224234096,224533168,224832160,225131072,225429872,225728608,226027232, + 226325776,226624240,226922608,227220880,227519056,227817152,228115168,228413088, + 228710912,229008640,229306288,229603840,229901312,230198688,230495968,230793152, + 231090256,231387280,231684192,231981024,232277760,232574416,232870960,233167440, + 233463808,233760096,234056288,234352384,234648384,234944304,235240128,235535872, + 235831504,236127056,236422512,236717888,237013152,237308336,237603424,237898416, + 238193328,238488144,238782864,239077488,239372016,239666464,239960816,240255072, + 240549232,240843312,241137280,241431168,241724960,242018656,242312256,242605776, + 242899200,243192512,243485744,243778896,244071936,244364880,244657744,244950496, + 245243168,245535744,245828224,246120608,246412912,246705104,246997216,247289216, + 247581136,247872960,248164688,248456320,248747856,249039296,249330640,249621904, + 249913056,250204128,250495088,250785968,251076736,251367424,251658016,251948512, + 252238912,252529200,252819408,253109520,253399536,253689456,253979280,254269008, + 254558640,254848176,255137632,255426976,255716224,256005376,256294432,256583392, + 256872256,257161024,257449696,257738272,258026752,258315136,258603424,258891600, + 259179696,259467696,259755600,260043392,260331104,260618704,260906224,261193632, + 261480960,261768176,262055296,262342320,262629248,262916080,263202816,263489456, + 263776000,264062432,264348784,264635024,264921168,265207216,265493168,265779024, + 266064784,266350448,266636000,266921472,267206832,267492096,267777264,268062336, + 268347312,268632192,268916960,269201632,269486208,269770688,270055072,270339360, + 270623552,270907616,271191616,271475488,271759296,272042976,272326560,272610048, + 272893440,273176736,273459936,273743040,274026048,274308928,274591744,274874432, + 275157024,275439520,275721920,276004224,276286432,276568512,276850528,277132416, + 277414240,277695936,277977536,278259040,278540448,278821728,279102944,279384032, + 279665056,279945952,280226752,280507456,280788064,281068544,281348960,281629248, + 281909472,282189568,282469568,282749440,283029248,283308960,283588544,283868032, + 284147424,284426720,284705920,284985024,285264000,285542912,285821696,286100384, + 286378976,286657440,286935840,287214112,287492320,287770400,288048384,288326240, + 288604032,288881696,289159264,289436768,289714112,289991392,290268576,290545632, + 290822592,291099456,291376224,291652896,291929440,292205888,292482272,292758528, + 293034656,293310720,293586656,293862496,294138240,294413888,294689440,294964864, + 295240192,295515424,295790560,296065600,296340512,296615360,296890080,297164704, + 297439200,297713632,297987936,298262144,298536256,298810240,299084160,299357952, + 299631648,299905248,300178720,300452128,300725408,300998592,301271680,301544640, + 301817536,302090304,302362976,302635520,302908000,303180352,303452608,303724768, + 303996800,304268768,304540608,304812320,305083968,305355520,305626944,305898272, + 306169472,306440608,306711616,306982528,307253344,307524064,307794656,308065152, + 308335552,308605856,308876032,309146112,309416096,309685984,309955744,310225408, + 310494976,310764448,311033824,311303072,311572224,311841280,312110208,312379040, + 312647776,312916416,313184960,313453376,313721696,313989920,314258016,314526016, + 314793920,315061728,315329408,315597024,315864512,316131872,316399168,316666336, + 316933408,317200384,317467232,317733984,318000640,318267200,318533632,318799968, + 319066208,319332352,319598368,319864288,320130112,320395808,320661408,320926912, + 321192320,321457632,321722816,321987904,322252864,322517760,322782528,323047200, + 323311744,323576192,323840544,324104800,324368928,324632992,324896928,325160736, + 325424448,325688096,325951584,326215008,326478304,326741504,327004608,327267584, + 327530464,327793248,328055904,328318496,328580960,328843296,329105568,329367712, + 329629760,329891680,330153536,330415264,330676864,330938400,331199808,331461120, + 331722304,331983392,332244384,332505280,332766048,333026752,333287296,333547776, + 333808128,334068384,334328544,334588576,334848512,335108352,335368064,335627712, + 335887200,336146624,336405920,336665120,336924224,337183200,337442112,337700864, + 337959552,338218112,338476576,338734944,338993184,339251328,339509376,339767296, + 340025120,340282848,340540480,340797984,341055392,341312704,341569888,341826976, + 342083968,342340832,342597600,342854272,343110848,343367296,343623648,343879904, + 344136032,344392064,344648000,344903808,345159520,345415136,345670656,345926048, + 346181344,346436512,346691616,346946592,347201440,347456224,347710880,347965440, + 348219872,348474208,348728448,348982592,349236608,349490528,349744320,349998048, + 350251648,350505152,350758528,351011808,351264992,351518048,351771040,352023872, + 352276640,352529280,352781824,353034272,353286592,353538816,353790944,354042944, + 354294880,354546656,354798368,355049952,355301440,355552800,355804096,356055264, + 356306304,356557280,356808128,357058848,357309504,357560032,357810464,358060768, + 358311008,358561088,358811104,359060992,359310784,359560480,359810048,360059520, + 360308896,360558144,360807296,361056352,361305312,361554144,361802880,362051488, + 362300032,362548448,362796736,363044960,363293056,363541024,363788928,364036704, + 364284384,364531936,364779392,365026752,365274016,365521152,365768192,366015136, + 366261952,366508672,366755296,367001792,367248192,367494496,367740704,367986784, + 368232768,368478656,368724416,368970080,369215648,369461088,369706432,369951680, + 370196800,370441824,370686752,370931584,371176288,371420896,371665408,371909792, + 372154080,372398272,372642336,372886304,373130176,373373952,373617600,373861152, + 374104608,374347936,374591168,374834304,375077312,375320224,375563040,375805760, + 376048352,376290848,376533248,376775520,377017696,377259776,377501728,377743584, + 377985344,378227008,378468544,378709984,378951328,379192544,379433664,379674688, + 379915584,380156416,380397088,380637696,380878176,381118560,381358848,381599040, + 381839104,382079072,382318912,382558656,382798304,383037856,383277280,383516640, + 383755840,383994976,384233984,384472896,384711712,384950400,385188992,385427488, + 385665888,385904160,386142336,386380384,386618368,386856224,387093984,387331616, + 387569152,387806592,388043936,388281152,388518272,388755296,388992224,389229024, + 389465728,389702336,389938816,390175200,390411488,390647680,390883744,391119712, + 391355584,391591328,391826976,392062528,392297984,392533312,392768544,393003680, + 393238720,393473632,393708448,393943168,394177760,394412256,394646656,394880960, + 395115136,395349216,395583200,395817088,396050848,396284512,396518080,396751520, + 396984864,397218112,397451264,397684288,397917248,398150080,398382784,398615424, + 398847936,399080320,399312640,399544832,399776928,400008928,400240832,400472608, + 400704288,400935872,401167328,401398720,401629984,401861120,402092192,402323136, + 402553984,402784736,403015360,403245888,403476320,403706656,403936896,404167008, + 404397024,404626944,404856736,405086432,405316032,405545536,405774912,406004224, + 406233408,406462464,406691456,406920320,407149088,407377760,407606336,407834784, + 408063136,408291392,408519520,408747584,408975520,409203360,409431072,409658720, + 409886240,410113664,410340992,410568192,410795296,411022304,411249216,411476032, + 411702720,411929312,412155808,412382176,412608480,412834656,413060736,413286720, + 413512576,413738336,413964000,414189568,414415040,414640384,414865632,415090784, + 415315840,415540800,415765632,415990368,416215008,416439552,416663968,416888288, + 417112512,417336640,417560672,417784576,418008384,418232096,418455712,418679200, + 418902624,419125920,419349120,419572192,419795200,420018080,420240864,420463552, + 420686144,420908608,421130976,421353280,421575424,421797504,422019488,422241344, + 422463104,422684768,422906336,423127776,423349120,423570400,423791520,424012576, + 424233536,424454368,424675104,424895744,425116288,425336736,425557056,425777280, + 425997408,426217440,426437376,426657184,426876928,427096544,427316064,427535488, + 427754784,427974016,428193120,428412128,428631040,428849856,429068544,429287168, + 429505664,429724064,429942368,430160576,430378656,430596672,430814560,431032352, + 431250048,431467616,431685120,431902496,432119808,432336992,432554080,432771040, + 432987936,433204736,433421408,433637984,433854464,434070848,434287104,434503296, + 434719360,434935360,435151232,435367008,435582656,435798240,436013696,436229088, + 436444352,436659520,436874592,437089568,437304416,437519200,437733856,437948416, + 438162880,438377248,438591520,438805696,439019744,439233728,439447584,439661344, + 439875008,440088576,440302048,440515392,440728672,440941824,441154880,441367872, + 441580736,441793472,442006144,442218720,442431168,442643552,442855808,443067968, + 443280032,443492000,443703872,443915648,444127296,444338880,444550336,444761696, + 444972992,445184160,445395232,445606176,445817056,446027840,446238496,446449088, + 446659552,446869920,447080192,447290400,447500448,447710432,447920320,448130112, + 448339776,448549376,448758848,448968224,449177536,449386720,449595808,449804800, + 450013664,450222464,450431168,450639776,450848256,451056640,451264960,451473152, + 451681248,451889248,452097152,452304960,452512672,452720288,452927808,453135232, + 453342528,453549760,453756864,453963904,454170816,454377632,454584384,454791008, + 454997536,455203968,455410304,455616544,455822688,456028704,456234656,456440512, + 456646240,456851904,457057472,457262912,457468256,457673536,457878688,458083744, + 458288736,458493600,458698368,458903040,459107616,459312096,459516480,459720768, + 459924960,460129056,460333056,460536960,460740736,460944448,461148064,461351584, + 461554976,461758304,461961536,462164640,462367680,462570592,462773440,462976160, + 463178816,463381344,463583776,463786144,463988384,464190560,464392608,464594560, + 464796448,464998208,465199872,465401472,465602944,465804320,466005600,466206816, + 466407904,466608896,466809824,467010624,467211328,467411936,467612480,467812896, + 468013216,468213440,468413600,468613632,468813568,469013440,469213184,469412832, + 469612416,469811872,470011232,470210528,470409696,470608800,470807776,471006688, + 471205472,471404192,471602784,471801312,471999712,472198048,472396288,472594400, + 472792448,472990400,473188256,473385984,473583648,473781216,473978688,474176064, + 474373344,474570528,474767616,474964608,475161504,475358336,475555040,475751648, + 475948192,476144608,476340928,476537184,476733312,476929376,477125344,477321184, + 477516960,477712640,477908224,478103712,478299104,478494400,478689600,478884704, + 479079744,479274656,479469504,479664224,479858880,480053408,480247872,480442240, + 480636512,480830656,481024736,481218752,481412640,481606432,481800128,481993760, + 482187264,482380704,482574016,482767264,482960416,483153472,483346432,483539296, + 483732064,483924768,484117344,484309856,484502240,484694560,484886784,485078912, + 485270944,485462880,485654720,485846464,486038144,486229696,486421184,486612576, + 486803840,486995040,487186176,487377184,487568096,487758912,487949664,488140320, + 488330880,488521312,488711712,488901984,489092160,489282240,489472256,489662176, + 489851968,490041696,490231328,490420896,490610336,490799712,490988960,491178144, + 491367232,491556224,491745120,491933920,492122656,492311264,492499808,492688256, + 492876608,493064864,493253056,493441120,493629120,493817024,494004832,494192544, + 494380160,494567712,494755136,494942496,495129760,495316928,495504000,495691008, + 495877888,496064704,496251424,496438048,496624608,496811040,496997408,497183680, + 497369856,497555936,497741920,497927840,498113632,498299360,498484992,498670560, + 498856000,499041376,499226656,499411840,499596928,499781920,499966848,500151680, + 500336416,500521056,500705600,500890080,501074464,501258752,501442944,501627040, + 501811072,501995008,502178848,502362592,502546240,502729824,502913312,503096704, + 503280000,503463232,503646368,503829408,504012352,504195200,504377984,504560672, + 504743264,504925760,505108192,505290496,505472736,505654912,505836960,506018944, + 506200832,506382624,506564320,506745952,506927488,507108928,507290272,507471552, + 507652736,507833824,508014816,508195744,508376576,508557312,508737952,508918528, + 509099008,509279392,509459680,509639904,509820032,510000064,510180000,510359872, + 510539648,510719328,510898944,511078432,511257856,511437216,511616448,511795616, + 511974688,512153664,512332576,512511392,512690112,512868768,513047296,513225792, + 513404160,513582432,513760640,513938784,514116800,514294752,514472608,514650368, + 514828064,515005664,515183168,515360608,515537952,515715200,515892352,516069440, + 516246432,516423328,516600160,516776896,516953536,517130112,517306592,517482976, + 517659264,517835488,518011616,518187680,518363648,518539520,518715296,518891008, + 519066624,519242144,519417600,519592960,519768256,519943424,520118528,520293568, + 520468480,520643328,520818112,520992800,521167392,521341888,521516320,521690656, + 521864896,522039072,522213152,522387168,522561056,522734912,522908640,523082304, + 523255872,523429376,523602784,523776096,523949312,524122464,524295552,524468512, + 524641440,524814240,524986976,525159616,525332192,525504640,525677056,525849344, + 526021568,526193728,526365792,526537760,526709632,526881440,527053152,527224800, + 527396352,527567840,527739200,527910528,528081728,528252864,528423936,528594880, + 528765760,528936576,529107296,529277920,529448480,529618944,529789344,529959648, + 530129856,530300000,530470048,530640000,530809888,530979712,531149440,531319072, + 531488608,531658080,531827488,531996800,532166016,532335168,532504224,532673184, + 532842080,533010912,533179616,533348288,533516832,533685312,533853728,534022048, + 534190272,534358432,534526496,534694496,534862400,535030240,535197984,535365632, + 535533216,535700704,535868128,536035456,536202720,536369888,536536992,536704000, + 536870912 +}; + +// Now where did these came from? +const byte gammatable[5][256] = +{ + { + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16, + 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32, + 33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, + 49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64, + 65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80, + 81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96, + 97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112, + 113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 + }, + + { + 2,4,5,7,8,10,11,12,14,15,16,18,19,20,21,23, + 24,25,26,27,29,30,31,32,33,34,36,37,38,39,40,41, + 42,44,45,46,47,48,49,50,51,52,54,55,56,57,58,59, + 60,61,62,63,64,65,66,67,69,70,71,72,73,74,75,76, + 77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92, + 93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108, + 109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124, + 125,126,127,128,129,129,130,131,132,133,134,135,136,137,138,139, + 140,141,142,143,144,145,146,147,148,148,149,150,151,152,153,154, + 155,156,157,158,159,160,161,162,163,163,164,165,166,167,168,169, + 170,171,172,173,174,175,175,176,177,178,179,180,181,182,183,184, + 185,186,186,187,188,189,190,191,192,193,194,195,196,196,197,198, + 199,200,201,202,203,204,205,205,206,207,208,209,210,211,212,213, + 214,214,215,216,217,218,219,220,221,222,222,223,224,225,226,227, + 228,229,230,230,231,232,233,234,235,236,237,237,238,239,240,241, + 242,243,244,245,245,246,247,248,249,250,251,252,252,253,254,255 + }, + + { + 4,7,9,11,13,15,17,19,21,22,24,26,27,29,30,32, + 33,35,36,38,39,40,42,43,45,46,47,48,50,51,52,54, + 55,56,57,59,60,61,62,63,65,66,67,68,69,70,72,73, + 74,75,76,77,78,79,80,82,83,84,85,86,87,88,89,90, + 91,92,93,94,95,96,97,98,100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,114,115,116,117,118,119,120,121,122, + 123,124,125,126,127,128,129,130,131,132,133,133,134,135,136,137, + 138,139,140,141,142,143,144,144,145,146,147,148,149,150,151,152, + 153,153,154,155,156,157,158,159,160,160,161,162,163,164,165,166, + 166,167,168,169,170,171,172,172,173,174,175,176,177,178,178,179, + 180,181,182,183,183,184,185,186,187,188,188,189,190,191,192,193, + 193,194,195,196,197,197,198,199,200,201,201,202,203,204,205,206, + 206,207,208,209,210,210,211,212,213,213,214,215,216,217,217,218, + 219,220,221,221,222,223,224,224,225,226,227,228,228,229,230,231, + 231,232,233,234,235,235,236,237,238,238,239,240,241,241,242,243, + 244,244,245,246,247,247,248,249,250,251,251,252,253,254,254,255 + }, + + { + 8,12,16,19,22,24,27,29,31,34,36,38,40,41,43,45, + 47,49,50,52,53,55,57,58,60,61,63,64,65,67,68,70, + 71,72,74,75,76,77,79,80,81,82,84,85,86,87,88,90, + 91,92,93,94,95,96,98,99,100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123, + 124,125,126,127,128,129,130,131,132,133,134,135,135,136,137,138, + 139,140,141,142,143,143,144,145,146,147,148,149,150,150,151,152, + 153,154,155,155,156,157,158,159,160,160,161,162,163,164,165,165, + 166,167,168,169,169,170,171,172,173,173,174,175,176,176,177,178, + 179,180,180,181,182,183,183,184,185,186,186,187,188,189,189,190, + 191,192,192,193,194,195,195,196,197,197,198,199,200,200,201,202, + 202,203,204,205,205,206,207,207,208,209,210,210,211,212,212,213, + 214,214,215,216,216,217,218,219,219,220,221,221,222,223,223,224, + 225,225,226,227,227,228,229,229,230,231,231,232,233,233,234,235, + 235,236,237,237,238,238,239,240,240,241,242,242,243,244,244,245, + 246,246,247,247,248,249,249,250,251,251,252,253,253,254,254,255 + }, + + + { + 16,23,28,32,36,39,42,45,48,50,53,55,57,60,62,64, + 66,68,69,71,73,75,76,78,80,81,83,84,86,87,89,90, + 92,93,94,96,97,98,100,101,102,103,105,106,107,108,109,110, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,128, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 143,144,145,146,147,148,149,150,150,151,152,153,154,155,155,156, + 157,158,159,159,160,161,162,163,163,164,165,166,166,167,168,169, + 169,170,171,172,172,173,174,175,175,176,177,177,178,179,180,180, + 181,182,182,183,184,184,185,186,187,187,188,189,189,190,191,191, + 192,193,193,194,195,195,196,196,197,198,198,199,200,200,201,202, + 202,203,203,204,205,205,206,207,207,208,208,209,210,210,211,211, + 212,213,213,214,214,215,216,216,217,217,218,219,219,220,220,221, + 221,222,223,223,224,224,225,225,226,227,227,228,228,229,229,230, + 230,231,232,232,233,233,234,234,235,235,236,236,237,237,238,239, + 239,240,240,241,241,242,242,243,243,244,244,245,245,246,246,247, + 247,248,248,249,249,250,250,251,251,252,252,253,254,254,255,255 + } +}; + diff --git a/client/src/ipu/tables.h b/client/src/ipu/tables.h new file mode 100644 index 0000000..495fd53 --- /dev/null +++ b/client/src/ipu/tables.h @@ -0,0 +1,96 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Lookup tables. +// Do not try to look them up :-). +// In the order of appearance: +// +// int finetangent[4096] - Tangens LUT. +// Should work with BAM fairly well (12 of 16bit, +// effectively, by shifting). +// +// int finesine[10240] - Sine lookup. +// Guess what, serves as cosine, too. +// Remarkable thing is, how to use BAMs with this? +// +// int tantoangle[2049] - ArcTan LUT, +// maps tan(angle) to angle fast. Gotta search. +// + + +#ifndef __TABLES__ +#define __TABLES__ + +#include "doomtype.h" + +#include "m_fixed.h" + +#define FINEANGLES 8192 +#define FINEMASK (FINEANGLES-1) + + +// 0x100000000 to 0x2000 +#define ANGLETOFINESHIFT 19 + +// Effective size is 10240. +extern const fixed_t finesine[5*FINEANGLES/4]; + +// Re-use data, is just PI/2 pahse shift. +extern const fixed_t *finecosine; + + +// Effective size is 4096. +extern const fixed_t finetangent[FINEANGLES/2]; + +// Gamma correction tables. +extern const byte gammatable[5][256]; + +// Binary Angle Measument, BAM. + +#define ANG45 0x20000000 +#define ANG90 0x40000000 +#define ANG180 0x80000000 +#define ANG270 0xc0000000 +#define ANG_MAX 0xffffffff + +#define ANG1 (ANG45 / 45) +#define ANG60 (ANG180 / 3) + +// Heretic code uses this definition as though it represents one +// degree, but it is not! This is actually ~1.40 degrees. + +#define ANG1_X 0x01000000 + +#define SLOPERANGE 2048 +#define SLOPEBITS 11 +#define DBITS (FRACBITS-SLOPEBITS) + +typedef unsigned angle_t; + + +// Effective size is 2049; +// The +1 size is to handle the case when x==y +// without additional checking. +extern const angle_t tantoangle[SLOPERANGE+1]; + + +// Utility function, +// called by R_PointToAngle. +int SlopeDiv(unsigned int num, unsigned int den); + + +#endif + diff --git a/client/src/ipu/v_patch.h b/client/src/ipu/v_patch.h new file mode 100644 index 0000000..186b803 --- /dev/null +++ b/client/src/ipu/v_patch.h @@ -0,0 +1,52 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh/rendering module, shared data struct definitions. +// + + +#ifndef V_PATCH_H +#define V_PATCH_H + +#include "doomtype.h" + +// Patches. +// A patch holds one or more columns. +// Patches are used for sprites and all masked pictures, +// and we compose textures from the TEXTURE1/2 lists +// of patches. + +typedef PACKED_STRUCT ( +{ + short width; // bounding box size + short height; + short leftoffset; // pixels to the left of origin + short topoffset; // pixels below the origin + int columnofs[8]; // only [width] used + // the [0] is &columnofs[width] +}) patch_t; + +// posts are runs of non masked source pixels +typedef PACKED_STRUCT ( +{ + byte topdelta; // -1 is the last post in a column + byte length; // length data bytes follows +}) post_t; + +// column_t is a list of 0 or more post_t, (byte)-1 terminated +typedef post_t column_t; + +#endif + diff --git a/client/src/ipu/v_video.c b/client/src/ipu/v_video.c new file mode 100644 index 0000000..12fc71f --- /dev/null +++ b/client/src/ipu/v_video.c @@ -0,0 +1,997 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Gamma correction LUT stuff. +// Functions to draw patches (by post) directly to screen. +// Functions to blit a block to the screen. +// +/* LATER +#include +#include +#include +#include +#include +#include + +#include "doomtype.h" +#include "i_input.h" +#include "i_system.h" +#include "m_bbox.h" +#include "m_misc.h" +#include "pngconf.h" +#include "w_wad.h" +#include "z_zone.h" +*/ + +#include "i_swap.h" +#include "i_video.h" +#include "v_video.h" + +#include "ipu_print.h" + +// Blending table used for fuzzpatch, etc. +// Only used in Heretic/Hexen + +byte *tinttable = NULL; + +// villsa [STRIFE] Blending table used for Strife +byte *xlatab = NULL; + +// The screen buffer that the v_video.c code draws to. + +static pixel_t *dest_screen = NULL; + +int dirtybox[4]; + +/* LATER +// haleyjd 08/28/10: clipping callback function for patches. +// This is needed for Chocolate Strife, which clips patches to the screen. +static vpatchclipfunc_t patchclip_callback = NULL; + +// +// V_MarkRect +// +void V_MarkRect(int x, int y, int width, int height) +{ + // If we are temporarily using an alternate screen, do not + // affect the update box. + + if (dest_screen == I_VideoBuffer) + { + M_AddToBox (dirtybox, x, y); + M_AddToBox (dirtybox, x + width-1, y + height-1); + } +} + + +// +// V_CopyRect +// +void V_CopyRect(int srcx, int srcy, pixel_t *source, + int width, int height, + int destx, int desty) +{ + pixel_t *src; + pixel_t *dest; + + if (srcx < 0 + || srcx + width > SCREENWIDTH + || srcy < 0 + || srcy + height > SCREENHEIGHT + || destx < 0 + || destx + width > SCREENWIDTH + || desty < 0 + || desty + height > SCREENHEIGHT) + { + I_Error ("Bad V_CopyRect"); + } + + V_MarkRect(destx, desty, width, height); + + src = source + SCREENWIDTH * srcy + srcx; + dest = dest_screen + SCREENWIDTH * desty + destx; + + for ( ; height>0 ; height--) + { + memcpy(dest, src, width * sizeof(*dest)); + src += SCREENWIDTH; + dest += SCREENWIDTH; + } +} + +// +// V_SetPatchClipCallback +// +// haleyjd 08/28/10: Added for Strife support. +// By calling this function, you can setup runtime error checking for patch +// clipping. Strife never caused errors by drawing patches partway off-screen. +// Some versions of vanilla DOOM also behaved differently than the default +// implementation, so this could possibly be extended to those as well for +// accurate emulation. +// +void V_SetPatchClipCallback(vpatchclipfunc_t func) +{ + patchclip_callback = func; +} + +*/ + +// +// V_DrawPatch +// Masks a column based masked pic to the screen. +// + +void V_DrawPatch(int x, int y, patch_t *patch) +{ + int count; + int col; + column_t *column; + pixel_t *desttop; + pixel_t *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + /* JOSEF: Unsupported + // haleyjd 08/28/10: Strife needs silent error checking here. + if(patchclip_callback) + { + if(!patchclip_callback(patch, x, y)) + return; + } + */ + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + ipuprint("Bad V_DrawPatch!\n"); + } + + /* LATER + V_MarkRect(x, y, SHORT(patch->width), SHORT(patch->height)); + */ + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + + for ( ; colcolumnofs[col])); + + // step through the posts in a column + while (column->topdelta != 0xff) + { + source = (byte *)column + 3; + dest = desttop + column->topdelta*SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = *source++; + dest += SCREENWIDTH; + } + column = (column_t *)((byte *)column + column->length + 4); + } + } +} + +/* LATER + +// +// V_DrawPatchFlipped +// Masks a column based masked pic to the screen. +// Flips horizontally, e.g. to mirror face. +// + +void V_DrawPatchFlipped(int x, int y, patch_t *patch) +{ + int count; + int col; + column_t *column; + pixel_t *desttop; + pixel_t *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + // haleyjd 08/28/10: Strife needs silent error checking here. + if(patchclip_callback) + { + if(!patchclip_callback(patch, x, y)) + return; + } + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawPatchFlipped"); + } + + V_MarkRect (x, y, SHORT(patch->width), SHORT(patch->height)); + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + + for ( ; colcolumnofs[w-1-col])); + + // step through the posts in a column + while (column->topdelta != 0xff ) + { + source = (byte *)column + 3; + dest = desttop + column->topdelta*SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = *source++; + dest += SCREENWIDTH; + } + column = (column_t *)((byte *)column + column->length + 4); + } + } +} + + + +// +// V_DrawPatchDirect +// Draws directly to the screen on the pc. +// + +void V_DrawPatchDirect(int x, int y, patch_t *patch) +{ + V_DrawPatch(x, y, patch); +} + +// +// V_DrawTLPatch +// +// Masks a column based translucent masked pic to the screen. +// + +void V_DrawTLPatch(int x, int y, patch_t * patch) +{ + int count, col; + column_t *column; + pixel_t *desttop, *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawTLPatch"); + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + for (; col < w; x++, col++, desttop++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while (column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = tinttable[((*dest) << 8) + *source++]; + dest += SCREENWIDTH; + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// V_DrawXlaPatch +// +// villsa [STRIFE] Masks a column based translucent masked pic to the screen. +// + +void V_DrawXlaPatch(int x, int y, patch_t * patch) +{ + int count, col; + column_t *column; + pixel_t *desttop, *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if(patchclip_callback) + { + if(!patchclip_callback(patch, x, y)) + return; + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + for(; col < w; x++, col++, desttop++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while(column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + count = column->length; + + while(count--) + { + *dest = xlatab[*dest + ((*source) << 8)]; + source++; + dest += SCREENWIDTH; + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// V_DrawAltTLPatch +// +// Masks a column based translucent masked pic to the screen. +// + +void V_DrawAltTLPatch(int x, int y, patch_t * patch) +{ + int count, col; + column_t *column; + pixel_t *desttop, *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawAltTLPatch"); + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + for (; col < w; x++, col++, desttop++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while (column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = tinttable[((*dest) << 8) + *source++]; + dest += SCREENWIDTH; + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// V_DrawShadowedPatch +// +// Masks a column based masked pic to the screen. +// + +void V_DrawShadowedPatch(int x, int y, patch_t *patch) +{ + int count, col; + column_t *column; + pixel_t *desttop, *dest; + byte *source; + pixel_t *desttop2, *dest2; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawShadowedPatch"); + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + desttop2 = dest_screen + (y + 2) * SCREENWIDTH + x + 2; + + w = SHORT(patch->width); + for (; col < w; x++, col++, desttop++, desttop2++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while (column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + dest2 = desttop2 + column->topdelta * SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest2 = tinttable[((*dest2) << 8)]; + dest2 += SCREENWIDTH; + *dest = *source++; + dest += SCREENWIDTH; + + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// Load tint table from TINTTAB lump. +// + +void V_LoadTintTable(void) +{ + tinttable = W_CacheLumpName("TINTTAB", PU_STATIC); +} + +// +// V_LoadXlaTable +// +// villsa [STRIFE] Load xla table from XLATAB lump. +// + +void V_LoadXlaTable(void) +{ + xlatab = W_CacheLumpName("XLATAB", PU_STATIC); +} + +// +// V_DrawBlock +// Draw a linear block of pixels into the view buffer. +// + +void V_DrawBlock(int x, int y, int width, int height, pixel_t *src) +{ + pixel_t *dest; + + if (x < 0 + || x + width >SCREENWIDTH + || y < 0 + || y + height > SCREENHEIGHT) + { + I_Error ("Bad V_DrawBlock"); + } + + V_MarkRect (x, y, width, height); + + dest = dest_screen + y * SCREENWIDTH + x; + + while (height--) + { + memcpy (dest, src, width * sizeof(*dest)); + src += width; + dest += SCREENWIDTH; + } +} + +void V_DrawFilledBox(int x, int y, int w, int h, int c) +{ + uint8_t *buf, *buf1; + int x1, y1; + + buf = I_VideoBuffer + SCREENWIDTH * y + x; + + for (y1 = 0; y1 < h; ++y1) + { + buf1 = buf; + + for (x1 = 0; x1 < w; ++x1) + { + *buf1++ = c; + } + + buf += SCREENWIDTH; + } +} + +void V_DrawHorizLine(int x, int y, int w, int c) +{ + uint8_t *buf; + int x1; + + buf = I_VideoBuffer + SCREENWIDTH * y + x; + + for (x1 = 0; x1 < w; ++x1) + { + *buf++ = c; + } +} + +void V_DrawVertLine(int x, int y, int h, int c) +{ + uint8_t *buf; + int y1; + + buf = I_VideoBuffer + SCREENWIDTH * y + x; + + for (y1 = 0; y1 < h; ++y1) + { + *buf = c; + buf += SCREENWIDTH; + } +} + +void V_DrawBox(int x, int y, int w, int h, int c) +{ + V_DrawHorizLine(x, y, w, c); + V_DrawHorizLine(x, y+h-1, w, c); + V_DrawVertLine(x, y, h, c); + V_DrawVertLine(x+w-1, y, h, c); +} + +// +// Draw a "raw" screen (lump containing raw data to blit directly +// to the screen) +// + +void V_DrawRawScreen(byte *raw) +{ + memcpy(dest_screen, raw, SCREENWIDTH * SCREENHEIGHT); +} +*/ + +// +// V_Init +// +void V_Init (void) +{ + // no-op! + // There used to be separate screens that could be drawn to; these are + // now handled in the upper layers. +} + +// Set the buffer that the code draws to. + +void V_UseBuffer(pixel_t *buffer) +{ + dest_screen = buffer; +} + +// Restore screen buffer to the i_video screen buffer. + +void V_RestoreBuffer(void) +{ + dest_screen = I_VideoBuffer; +} + +/* LATER + +// +// SCREEN SHOTS +// + +typedef PACKED_STRUCT ( +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + + unsigned short xmin; + unsigned short ymin; + unsigned short xmax; + unsigned short ymax; + + unsigned short hres; + unsigned short vres; + + unsigned char palette[48]; + + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + + char filler[58]; + unsigned char data; // unbounded +}) pcx_t; + + +// +// WritePCXfile +// + +void WritePCXfile(char *filename, byte *data, + int width, int height, + byte *palette) +{ + int i; + int length; + pcx_t* pcx; + byte* pack; + + pcx = Z_Malloc (width*height*2+1000, PU_STATIC, NULL); + + pcx->manufacturer = 0x0a; // PCX id + pcx->version = 5; // 256 color + pcx->encoding = 1; // uncompressed + pcx->bits_per_pixel = 8; // 256 color + pcx->xmin = 0; + pcx->ymin = 0; + pcx->xmax = SHORT(width-1); + pcx->ymax = SHORT(height-1); + pcx->hres = SHORT(width); + pcx->vres = SHORT(height); + memset (pcx->palette,0,sizeof(pcx->palette)); + pcx->reserved = 0; // PCX spec: reserved byte must be zero + pcx->color_planes = 1; // chunky image + pcx->bytes_per_line = SHORT(width); + pcx->palette_type = SHORT(2); // not a grey scale + memset (pcx->filler,0,sizeof(pcx->filler)); + + // pack the image + pack = &pcx->data; + + for (i=0 ; i 0 && fabs(mouse_acceleration - 1) > 0.01) + { + draw_acceleration = true; + } + + // Calculate box position + + box_x = SCREENWIDTH - MOUSE_SPEED_BOX_WIDTH - 10; + box_y = 15; + + V_DrawFilledBox(box_x, box_y, + MOUSE_SPEED_BOX_WIDTH, MOUSE_SPEED_BOX_HEIGHT, bgcolor); + V_DrawBox(box_x, box_y, + MOUSE_SPEED_BOX_WIDTH, MOUSE_SPEED_BOX_HEIGHT, bordercolor); + + // Calculate the position of the red threshold line when calibrating + // acceleration. This is 1/3 of the way along the box. + + redline_x = MOUSE_SPEED_BOX_WIDTH / 3; + + // Calculate line length + + if (draw_acceleration && speed >= mouse_threshold) + { + // Undo acceleration and get back the original mouse speed + original_speed = speed - mouse_threshold; + original_speed = (int) (original_speed / mouse_acceleration); + original_speed += mouse_threshold; + + linelen = (original_speed * redline_x) / mouse_threshold; + } + else + { + linelen = speed / linelen_multiplier; + } + + // Draw horizontal "thermometer" + + if (linelen > MOUSE_SPEED_BOX_WIDTH - 1) + { + linelen = MOUSE_SPEED_BOX_WIDTH - 1; + if (!draw_acceleration) + { + linelen_multiplier++; + } + } + + V_DrawHorizLine(box_x + 1, box_y + 4, MOUSE_SPEED_BOX_WIDTH - 2, black); + + if (!draw_acceleration || linelen < redline_x) + { + V_DrawHorizLine(box_x + 1, box_y + MOUSE_SPEED_BOX_HEIGHT / 2, + linelen, white); + } + else + { + V_DrawHorizLine(box_x + 1, box_y + MOUSE_SPEED_BOX_HEIGHT / 2, + redline_x, white); + V_DrawHorizLine(box_x + redline_x, box_y + MOUSE_SPEED_BOX_HEIGHT / 2, + linelen - redline_x, yellow); + } + + if (draw_acceleration) + { + // Draw acceleration threshold line + V_DrawVertLine(box_x + redline_x, box_y + 1, + MOUSE_SPEED_BOX_HEIGHT - 2, red); + } + else + { + // Draw multiplier lines to indicate current resolution + for (i = 1; i < linelen_multiplier; i++) + { + V_DrawVertLine( + box_x + (i * MOUSE_SPEED_BOX_WIDTH / linelen_multiplier), + box_y + 1, MOUSE_SPEED_BOX_HEIGHT - 2, yellow); + } + } +} + +*/ \ No newline at end of file diff --git a/client/src/ipu/v_video.h b/client/src/ipu/v_video.h new file mode 100644 index 0000000..7b98c35 --- /dev/null +++ b/client/src/ipu/v_video.h @@ -0,0 +1,108 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Gamma correction LUT. +// Functions to draw patches (by post) directly to screen. +// Functions to blit a block to the screen. +// + + +#ifndef __V_VIDEO__ +#define __V_VIDEO__ + +#include "doomtype.h" +#include "i_video.h" +// Needed because we are refering to patches. +#include "v_patch.h" + +// +// VIDEO +// + +#define CENTERY (SCREENHEIGHT/2) + + +extern int dirtybox[4]; + +extern byte *tinttable; + +// haleyjd 08/28/10: implemented for Strife support +// haleyjd 08/28/10: Patch clipping callback, implemented to support Choco +// Strife. +typedef boolean (*vpatchclipfunc_t)(patch_t *, int, int); +void V_SetPatchClipCallback(vpatchclipfunc_t func); + + +// Allocates buffer screens, call before R_Init. +void V_Init (void); + +// Draw a block from the specified source screen to the screen. + +void V_CopyRect(int srcx, int srcy, pixel_t *source, + int width, int height, + int destx, int desty); + +void V_DrawPatch(int x, int y, patch_t *patch); +void V_DrawPatchFlipped(int x, int y, patch_t *patch); +void V_DrawTLPatch(int x, int y, patch_t *patch); +void V_DrawAltTLPatch(int x, int y, patch_t * patch); +void V_DrawShadowedPatch(int x, int y, patch_t *patch); +void V_DrawXlaPatch(int x, int y, patch_t * patch); // villsa [STRIFE] +void V_DrawPatchDirect(int x, int y, patch_t *patch); + +// Draw a linear block of pixels into the view buffer. + +void V_DrawBlock(int x, int y, int width, int height, pixel_t *src); + +void V_MarkRect(int x, int y, int width, int height); + +void V_DrawFilledBox(int x, int y, int w, int h, int c); +void V_DrawHorizLine(int x, int y, int w, int c); +void V_DrawVertLine(int x, int y, int h, int c); +void V_DrawBox(int x, int y, int w, int h, int c); + +// Draw a raw screen lump + +void V_DrawRawScreen(byte *raw); + +// Temporarily switch to using a different buffer to draw graphics, etc. + +void V_UseBuffer(pixel_t *buffer); + +// Return to using the normal screen buffer to draw graphics. + +void V_RestoreBuffer(void); + +// Save a screenshot of the current screen to a file, named in the +// format described in the string passed to the function, eg. +// "DOOM%02i.pcx" + +void V_ScreenShot(char *format); + +// Load the lookup table for translucency calculations from the TINTTAB +// lump. + +void V_LoadTintTable(void); + +// villsa [STRIFE] +// Load the lookup table for translucency calculations from the XLATAB +// lump. + +void V_LoadXlaTable(void); + +void V_DrawMouseSpeedBox(int speed); + +#endif + diff --git a/client/src/ipu/w_checksum.h b/client/src/ipu/w_checksum.h new file mode 100644 index 0000000..13acc18 --- /dev/null +++ b/client/src/ipu/w_checksum.h @@ -0,0 +1,27 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Generate a checksum of the WAD directory. +// + +#ifndef W_CHECKSUM_H +#define W_CHECKSUM_H + +#include "sha1.h" + +extern void W_Checksum(sha1_digest_t digest); + +#endif /* #ifndef W_CHECKSUM_H */ + diff --git a/client/src/ipu/w_file.h b/client/src/ipu/w_file.h new file mode 100644 index 0000000..8ffebe6 --- /dev/null +++ b/client/src/ipu/w_file.h @@ -0,0 +1,77 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// WAD I/O functions. +// + + +#ifndef __W_FILE__ +#define __W_FILE__ + +#include + +#include "doomtype.h" + +struct _wad_file_s; + +typedef struct _wad_file_s wad_file_t; + +typedef struct +{ + // Open a file for reading. + wad_file_t *(*OpenFile)(char *path); + + // Close the specified file. + void (*CloseFile)(wad_file_t *file); + + // Read data from the specified position in the file into the + // provided buffer. Returns the number of bytes read. + size_t (*Read)(wad_file_t *file, unsigned int offset, + void *buffer, size_t buffer_len); +} wad_file_class_t; + +struct _wad_file_s +{ + // Class of this file. + wad_file_class_t *file_class; + + // If this is NULL, the file cannot be mapped into memory. If this + // is non-NULL, it is a pointer to the mapped file. + byte *mapped; + + // Length of the file, in bytes. + unsigned int length; + + // File's location on disk. + const char *path; +}; + +// Open the specified file. Returns a pointer to a new wad_file_t +// handle for the WAD file, or NULL if it could not be opened. + +wad_file_t *W_OpenFile(char *path); + +// Close the specified WAD file. + +void W_CloseFile(wad_file_t *wad); + +// Read data from the specified file into the provided buffer. The +// data is read from the specified offset from the start of the file. +// Returns the number of bytes read. + +size_t W_Read(wad_file_t *wad, unsigned int offset, + void *buffer, size_t buffer_len); + +#endif /* #ifndef __W_FILE__ */ diff --git a/client/src/ipu/w_main.h b/client/src/ipu/w_main.h new file mode 100644 index 0000000..7a071c3 --- /dev/null +++ b/client/src/ipu/w_main.h @@ -0,0 +1,28 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Common code to parse command line, identifying WAD files to load. +// + +#ifndef W_MAIN_H +#define W_MAIN_H + +#include "d_mode.h" +#include "doomtype.h" + +boolean W_ParseCommandLine(void); +void W_CheckCorrectIWAD(GameMission_t mission); + +#endif /* #ifndef W_MAIN_H */ + diff --git a/client/src/ipu/w_merge.h b/client/src/ipu/w_merge.h new file mode 100644 index 0000000..c8ecc69 --- /dev/null +++ b/client/src/ipu/w_merge.h @@ -0,0 +1,44 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Handles merging of PWADs, similar to deutex's -merge option +// +// Ideally this should work exactly the same as in deutex, but trying to +// read the deutex source code made my brain hurt. +// + +#ifndef W_MERGE_H +#define W_MERGE_H + +#define W_NWT_MERGE_SPRITES 0x1 +#define W_NWT_MERGE_FLATS 0x2 + +// Add a new WAD and merge it into the main directory + +void W_MergeFile(char *filename); + +// NWT-style merging + +void W_NWTMergeFile(char *filename, int flags); + +// Acts the same as NWT's "-merge" option. + +void W_NWTDashMerge(char *filename); + +// Debug function that prints the WAD directory. + +void W_PrintDirectory(void); + +#endif /* #ifndef W_MERGE_H */ + diff --git a/client/src/ipu/w_wad.h b/client/src/ipu/w_wad.h new file mode 100644 index 0000000..c836f4f --- /dev/null +++ b/client/src/ipu/w_wad.h @@ -0,0 +1,74 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// WAD I/O functions. +// + + +#ifndef __W_WAD__ +#define __W_WAD__ + +#include "w_file.h" + +struct lumpinfo_s; + + +// +// TYPES +// + +// +// WADFILE I/O related stuff. +// + +typedef struct lumpinfo_s lumpinfo_t; +typedef int lumpindex_t; + +struct lumpinfo_s +{ + char name[8]; + wad_file_t *wad_file; + int position; + int size; + void *cache; + + // Used for hash table lookups + lumpindex_t next; +}; + + +extern lumpinfo_t **lumpinfo; +extern unsigned int numlumps; + +wad_file_t *W_AddFile(char *filename); +void W_Reload(void); + +lumpindex_t W_CheckNumForName(char *name); +lumpindex_t W_GetNumForName(char *name); + +int W_LumpLength(lumpindex_t lump); +void W_ReadLump(lumpindex_t lump, void *dest); + +void *W_CacheLumpNum(lumpindex_t lump, int tag); +void *W_CacheLumpName(char *name, int tag); + +void W_GenerateHashTable(void); + +extern unsigned int W_LumpNameHash(const char *s); + +void W_ReleaseLumpNum(lumpindex_t lump); +void W_ReleaseLumpName(char *name); + +#endif diff --git a/client/src/ipu/wi_stuff.h b/client/src/ipu/wi_stuff.h new file mode 100644 index 0000000..7a551d7 --- /dev/null +++ b/client/src/ipu/wi_stuff.h @@ -0,0 +1,47 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Intermission. +// + +#ifndef __WI_STUFF__ +#define __WI_STUFF__ + +//#include "v_video.h" + +#include "d_player.h" + +// States for the intermission + +typedef enum { + NoState = -1, + StatCount, + ShowNextLoc, +} stateenum_t; + +// Called by main loop, animate the intermission. +void WI_Ticker(void); + +// Called by main loop, +// draws the intermission directly into the screen buffer. +void WI_Drawer(void); + +// Setup for an intermission screen. +void WI_Start(wbstartstruct_t *wbstartstruct); + +// Shut down the intermission screen +void WI_End(void); + +#endif diff --git a/client/src/ipu/z_zone.h b/client/src/ipu/z_zone.h new file mode 100644 index 0000000..526f30d --- /dev/null +++ b/client/src/ipu/z_zone.h @@ -0,0 +1,73 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Zone Memory Allocation, perhaps NeXT ObjectiveC inspired. +// Remark: this was the only stuff that, according +// to John Carmack, might have been useful for +// Quake. +// + + + +#ifndef __Z_ZONE__ +#define __Z_ZONE__ + +#include + +// +// ZONE MEMORY +// PU - purge tags. + +enum +{ + PU_STATIC = 1, // static entire execution time + PU_SOUND, // static while playing + PU_MUSIC, // static while playing + PU_FREE, // a free block + PU_LEVEL, // static until level exited + PU_LEVSPEC, // a special thinker in a level + + // Tags >= PU_PURGELEVEL are purgable whenever needed. + + PU_PURGELEVEL, + PU_CACHE, + + // Total number of different tag types + + PU_NUM_TAGS +}; + + +void Z_Init (void); +void* Z_Malloc (int size, int tag, void *ptr); +void Z_Free (void *ptr); +void Z_FreeTags (int lowtag, int hightag); +void Z_DumpHeap (int lowtag, int hightag); +void Z_FileDumpHeap (FILE *f); +void Z_CheckHeap (void); +void Z_ChangeTag2 (void *ptr, int tag, char *file, int line); +void Z_ChangeUser(void *ptr, void **user); +int Z_FreeMemory (void); +unsigned int Z_ZoneSize(void); + +// +// This is used to get the local FILE:LINE info from CPP +// prior to really call the function in question. +// +#define Z_ChangeTag(p,t) \ + Z_ChangeTag2((p), (t), __FILE__, __LINE__) + + +#endif diff --git a/client/src/ipu_host.cpp b/client/src/ipu_host.cpp new file mode 100644 index 0000000..fd45cbd --- /dev/null +++ b/client/src/ipu_host.cpp @@ -0,0 +1,298 @@ +#include "ipu_host.h" + +#include +#include +#include +#include +#include + +#include "i_video.h" +#include "ipu/ipu_interface.h" +#include "ipu_transfer.h" + + +poplar::Device getIpu(bool use_hardware, int num_ipus) { + if (use_hardware) { + auto manager = poplar::DeviceManager::createDeviceManager(); + auto devices = manager.getDevices(poplar::TargetType::IPU, num_ipus); + auto it = + std::find_if(devices.begin(), devices.end(), [](poplar::Device& device) { return device.attach(); }); + if (it == devices.end()) { + std::cerr << "IPU: Error attaching to device\n"; + exit(EXIT_FAILURE); + } + std::cout << "Attached to IPU " << it->getId() << std::endl; + return std::move(*it); + + } else { + poplar::IPUModel ipuModel; + std::cout << "Using simulated IPU" << std::endl; + return ipuModel.createDevice(); + } +} + +class IpuDoom { + public: + IpuDoom(); + ~IpuDoom(); + + void buildIpuGraph(); + void run_AM_Drawer(); + void run_IPU_Setup(); + void run_G_DoLoadLevel(); + void run_G_Ticker(); + void run_G_Responder(G_Responder_MiscValues_t* buf); + + poplar::Device m_ipuDevice; + poplar::Graph m_ipuGraph; + std::unique_ptr m_ipuEngine; + + private: + poplar::Tensor m_lumpBuf; + poplar::Tensor m_lumpNum; + int m_lumpNum_h; + poplar::Tensor m_miscValuesBuf; + unsigned char m_miscValuesBuf_h[IPUMISCVALUESSIZE]; +}; + +IpuDoom::IpuDoom() + : m_ipuDevice(getIpu(false, 1)), + m_ipuGraph(/*poplar::Target::createIPUTarget(1, "ipu2")*/ m_ipuDevice.getTarget()), + m_ipuEngine(nullptr) { + buildIpuGraph(); +} +IpuDoom::~IpuDoom(){}; + +void IpuDoom::buildIpuGraph() { + m_ipuGraph.addCodelets("build/ipu_rt.gp"); + + // -------- AM_Drawer_CS ------ // + + poplar::Tensor printbuf = + m_ipuGraph.addVariable(poplar::CHAR, {(ulong)IPUPRINTBUFSIZE}, "ipuprint_buf"); + m_ipuGraph.setTileMapping(printbuf, 0); + auto printbufOutStream = + m_ipuGraph.addDeviceToHostFIFO("printbuf-stream", poplar::CHAR, IPUPRINTBUFSIZE); + + poplar::ComputeSet GetPrintbuf_CS = m_ipuGraph.addComputeSet("GetPrintbuf_CS"); + poplar::VertexRef vtx = m_ipuGraph.addVertex(GetPrintbuf_CS, "IPU_GetPrintBuf_Vertex", {{"printbuf", printbuf}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, IPUPRINTBUFSIZE); + + poplar::program::Sequence GetPrintbuf_prog({ + poplar::program::Execute(GetPrintbuf_CS), + poplar::program::Copy(printbuf, printbufOutStream), + }); + + // -------- AM_Drawer_CS ------ // + + poplar::Tensor ipuFrame = + m_ipuGraph.addVariable(poplar::UNSIGNED_CHAR, {(ulong)SCREENWIDTH * SCREENHEIGHT}, "frame"); + m_ipuGraph.setTileMapping(ipuFrame, 0); + auto frameInStream = + m_ipuGraph.addHostToDeviceFIFO("frame-instream", poplar::UNSIGNED_CHAR, SCREENWIDTH * SCREENHEIGHT); + auto frameOutStream = + m_ipuGraph.addDeviceToHostFIFO("frame-outstream", poplar::UNSIGNED_CHAR, SCREENWIDTH * SCREENHEIGHT); + + poplar::ComputeSet AM_Drawer_CS = m_ipuGraph.addComputeSet("AM_Drawer_CS"); + vtx = m_ipuGraph.addVertex(AM_Drawer_CS, "AM_Drawer_Vertex", {{"frame", ipuFrame}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, 10000000); + + poplar::program::Sequence AM_Drawer_prog({ + poplar::program::Copy(frameInStream, ipuFrame), + poplar::program::Execute(AM_Drawer_CS), + poplar::program::Copy(ipuFrame, frameOutStream), + }); + + // -------- IPU_G_DoLoadLevel ------ // + + m_miscValuesBuf = m_ipuGraph.addVariable(poplar::UNSIGNED_CHAR, {(ulong)IPUMISCVALUESSIZE}, "miscValues"); + m_ipuGraph.setTileMapping(m_miscValuesBuf, 0); + auto miscValuesStream = + m_ipuGraph.addHostToDeviceFIFO("miscValues-stream", poplar::UNSIGNED_CHAR, IPUMISCVALUESSIZE); + poplar::ComputeSet G_DoLoadLevel_CS = m_ipuGraph.addComputeSet("G_DoLoadLevel_CS"); + vtx = m_ipuGraph.addVertex(G_DoLoadLevel_CS, "G_DoLoadLevel_Vertex", {{"miscValues", m_miscValuesBuf}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, 10000000); + + m_lumpBuf = m_ipuGraph.addVariable(poplar::UNSIGNED_CHAR, {(ulong)IPUMAXLUMPBYTES}, "lumpBuf"); + m_lumpNum = m_ipuGraph.addVariable(poplar::INT, {}, "lumpNum"); + m_ipuGraph.setTileMapping(m_lumpBuf, 0); + m_ipuGraph.setTileMapping(m_lumpNum, 0); + auto lumpBufStream = m_ipuGraph.addHostToDeviceFIFO("lumpBuf-stream", poplar::UNSIGNED_CHAR, IPUMAXLUMPBYTES); + auto lumpNumStream = m_ipuGraph.addDeviceToHostFIFO("lumpNum-stream", poplar::INT, 1); + + poplar::ComputeSet P_SetupLevel_CS = m_ipuGraph.addComputeSet("P_SetupLevel_CS"); + vtx = m_ipuGraph.addVertex(P_SetupLevel_CS, "P_SetupLevel_pt0_Vertex", {{"lumpNum", m_lumpNum}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, 100); + + poplar::ComputeSet P_LoadBlockMap_CS = m_ipuGraph.addComputeSet("P_LoadBlockMap_CS"); + vtx = m_ipuGraph.addVertex(P_LoadBlockMap_CS, "P_LoadBlockMap_Vertex", { + {"lumpNum", m_lumpNum}, {"lumpBuf", m_lumpBuf}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, 100); + + poplar::ComputeSet P_LoadVertexes_CS = m_ipuGraph.addComputeSet("P_LoadVertexes_CS"); + vtx = m_ipuGraph.addVertex(P_LoadVertexes_CS, "P_LoadVertexes_Vertex", { + {"lumpNum", m_lumpNum}, {"lumpBuf", m_lumpBuf}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, 100); + + poplar::ComputeSet P_LoadSectors_CS = m_ipuGraph.addComputeSet("P_LoadSectors_CS"); + vtx = m_ipuGraph.addVertex(P_LoadSectors_CS, "P_LoadSectors_Vertex", { + {"lumpNum", m_lumpNum}, {"lumpBuf", m_lumpBuf}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, 100); + + poplar::ComputeSet P_LoadSideDefs_CS = m_ipuGraph.addComputeSet("P_LoadSideDefs_CS"); + vtx = m_ipuGraph.addVertex(P_LoadSideDefs_CS, "P_LoadSideDefs_Vertex", { + {"lumpNum", m_lumpNum}, {"lumpBuf", m_lumpBuf}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, 100); + + poplar::ComputeSet P_LoadLineDefs_CS = m_ipuGraph.addComputeSet("P_LoadLineDefs_CS"); + vtx = m_ipuGraph.addVertex(P_LoadLineDefs_CS, "P_LoadLineDefs_Vertex", { + {"lumpNum", m_lumpNum}, {"lumpBuf", m_lumpBuf}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, 100); + + poplar::ComputeSet P_LoadSubsectors_CS = m_ipuGraph.addComputeSet("P_LoadSubsectors_CS"); + vtx = m_ipuGraph.addVertex(P_LoadSubsectors_CS, "P_LoadSubsectors_Vertex", { + {"lumpNum", m_lumpNum}, {"lumpBuf", m_lumpBuf}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, 100); + + + poplar::program::Sequence G_DoLoadLevel_prog({ + poplar::program::Copy(miscValuesStream, m_miscValuesBuf), + poplar::program::Execute(G_DoLoadLevel_CS), + poplar::program::Execute(P_SetupLevel_CS), + poplar::program::Copy(m_lumpNum, lumpNumStream), + poplar::program::Copy(lumpBufStream, m_lumpBuf), + poplar::program::Execute(P_LoadBlockMap_CS), + poplar::program::Copy(m_lumpNum, lumpNumStream), + poplar::program::Copy(lumpBufStream, m_lumpBuf), + poplar::program::Execute(P_LoadVertexes_CS), + poplar::program::Copy(m_lumpNum, lumpNumStream), + poplar::program::Copy(lumpBufStream, m_lumpBuf), + poplar::program::Execute(P_LoadSectors_CS), + poplar::program::Copy(m_lumpNum, lumpNumStream), + poplar::program::Copy(lumpBufStream, m_lumpBuf), + poplar::program::Execute(P_LoadSideDefs_CS), + poplar::program::Copy(m_lumpNum, lumpNumStream), + poplar::program::Copy(lumpBufStream, m_lumpBuf), + poplar::program::Execute(P_LoadLineDefs_CS), + poplar::program::Copy(m_lumpNum, lumpNumStream), + poplar::program::Copy(lumpBufStream, m_lumpBuf), + poplar::program::Execute(P_LoadSubsectors_CS), + GetPrintbuf_prog, + }); + + // ---------------- G_Ticker --------------// + + + poplar::ComputeSet G_Ticker_CS = m_ipuGraph.addComputeSet("G_Ticker_CS"); + vtx = m_ipuGraph.addVertex(G_Ticker_CS, "G_Ticker_Vertex", {{"miscValues", m_miscValuesBuf}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, 100); + + poplar::program::Sequence G_Ticker_prog({ + poplar::program::Copy(miscValuesStream, m_miscValuesBuf), + poplar::program::Execute(G_Ticker_CS), + GetPrintbuf_prog, + }); + + + // ---------------- G_Responder --------------// + + + poplar::ComputeSet G_Responder_CS = m_ipuGraph.addComputeSet("G_Responder_CS"); + vtx = m_ipuGraph.addVertex(G_Responder_CS, "G_Responder_Vertex", {{"miscValues", m_miscValuesBuf}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, 100); + + poplar::program::Sequence G_Responder_prog({ + poplar::program::Copy(miscValuesStream, m_miscValuesBuf), + poplar::program::Execute(G_Responder_CS), + GetPrintbuf_prog, + }); + + + // -------------- IPU state setup ------------// + + poplar::Tensor marknumSpriteBuf = m_ipuGraph.addVariable(poplar::UNSIGNED_CHAR, {(ulong)IPUAMMARKBUFSIZE}, "marknumSpriteBuf"); + m_ipuGraph.setTileMapping(marknumSpriteBuf, 0); + auto marknumSpriteBufStream = + m_ipuGraph.addHostToDeviceFIFO("marknumSpriteBuf-stream", poplar::UNSIGNED_CHAR, IPUAMMARKBUFSIZE); + + poplar::ComputeSet IPU_Setup_UnpackMarknumSprites_CS = m_ipuGraph.addComputeSet("IPU_Setup_UnpackMarknumSprites_CS"); + vtx = m_ipuGraph.addVertex(IPU_Setup_UnpackMarknumSprites_CS, "IPU_Setup_UnpackMarknumSprites_Vertex", + {{"buf", marknumSpriteBuf}}); + m_ipuGraph.setTileMapping(vtx, 0); + m_ipuGraph.setPerfEstimate(vtx, IPUAMMARKBUFSIZE * 100); + + poplar::program::Sequence IPU_Setup_prog({ + poplar::program::Copy(marknumSpriteBufStream, marknumSpriteBuf), + poplar::program::Execute(IPU_Setup_UnpackMarknumSprites_CS), + GetPrintbuf_prog, + }); + + + // ---------------- Final prog --------------// + + printf("Creating engine...\n"); + m_ipuEngine = std::make_unique(std::move(poplar::Engine( + m_ipuGraph, { + IPU_Setup_prog, + G_DoLoadLevel_prog, + G_Ticker_prog, + G_Responder_prog, + AM_Drawer_prog, + }))); + + m_ipuEngine->connectStream("frame-instream", I_VideoBuffer); + m_ipuEngine->connectStream("frame-outstream", I_VideoBuffer); + + m_ipuEngine->connectStreamToCallback("printbuf-stream", [](void* p) { + if (((char*)p)[0] == '\0') return; + printf("[IPU] %.*s\n", IPUPRINTBUFSIZE, (char*)p); + }); + + m_ipuEngine->connectStream("miscValues-stream", m_miscValuesBuf_h); + + m_ipuEngine->connectStream("lumpNum-stream", &m_lumpNum_h); + m_ipuEngine->connectStreamToCallback("lumpBuf-stream", [this](void* p) { + IPU_LoadLumpForTransfer(m_lumpNum_h, (byte*) p); + }); + + m_ipuEngine->connectStreamToCallback("marknumSpriteBuf-stream", [this](void* p) { + IPU_Setup_PackMarkNums(p); + }); + + m_ipuEngine->load(m_ipuDevice); + +} + + +void IpuDoom::run_IPU_Setup() { m_ipuEngine->run(0); } +void IpuDoom::run_G_DoLoadLevel() { IPU_G_LoadLevel_PackMiscValues(m_miscValuesBuf_h); m_ipuEngine->run(1); } +void IpuDoom::run_G_Ticker() { IPU_G_Ticker_PackMiscValues(m_miscValuesBuf_h); m_ipuEngine->run(2); } +void IpuDoom::run_G_Responder(G_Responder_MiscValues_t* src_buf) { + IPU_G_Responder_PackMiscValues(src_buf, m_miscValuesBuf_h); + m_ipuEngine->run(3); +} +void IpuDoom::run_AM_Drawer() { m_ipuEngine->run(4); } + +static std::unique_ptr ipuDoomInstance = nullptr; +extern "C" { +void IPU_Init() { + ipuDoomInstance = std::make_unique(); + ipuDoomInstance->run_IPU_Setup(); +} +void IPU_AM_Drawer() { ipuDoomInstance->run_AM_Drawer(); } +void IPU_G_DoLoadLevel() { ipuDoomInstance->run_G_DoLoadLevel(); } +void IPU_G_Ticker() { ipuDoomInstance->run_G_Ticker(); } +void IPU_G_Responder(G_Responder_MiscValues_t* buf) { ipuDoomInstance->run_G_Responder(buf); } +} \ No newline at end of file diff --git a/client/src/ipu_host.h b/client/src/ipu_host.h new file mode 100644 index 0000000..0c5162c --- /dev/null +++ b/client/src/ipu_host.h @@ -0,0 +1,24 @@ +#ifndef __IPUDOOM__ +#define __IPUDOOM__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ipu/ipu_interface.h" + + +void IPU_Init(void); + +void IPU_AM_Drawer(void); + +void IPU_G_DoLoadLevel(void); +void IPU_G_Ticker(void); +void IPU_G_Responder(G_Responder_MiscValues_t*); + + +#ifdef __cplusplus +} +#endif + +#endif // __IPUDOOM__ // \ No newline at end of file diff --git a/client/src/ipu_transfer.c b/client/src/ipu_transfer.c new file mode 100644 index 0000000..dd9a123 --- /dev/null +++ b/client/src/ipu_transfer.c @@ -0,0 +1,117 @@ +#include +#include +#include + +// #include "r_defs.h" +// #include "r_state.h" +#include "doomdata.h" +#include "doomstat.h" +#include "i_system.h" +#include "m_misc.h" +#include "r_defs.h" +#include "r_state.h" +#include "v_patch.h" +#include "w_wad.h" +#include "z_zone.h" + +#include "ipu/ipu_interface.h" + + +void IPU_G_LoadLevel_PackMiscValues(void* buf) { + assert(sizeof(G_LoadLevel_MiscValues_t) <= IPUMISCVALUESSIZE); + + G_LoadLevel_MiscValues_t pack; + pack.gameepisode = gameepisode; + pack.gamemap = gamemap; + + char lumpname[9]; + lumpname[0] = 'E'; + lumpname[1] = '0' + gameepisode; + lumpname[2] = 'M'; + lumpname[3] = '0' + gamemap; + lumpname[4] = '\0'; + pack.lumpnum = W_GetNumForName(lumpname); + + memcpy(buf, &pack, sizeof(pack)); +} + + +#define MAX_BUFFERED_MAPPED_LINES 1000 +static int mapped_line_buf[MAX_BUFFERED_MAPPED_LINES]; +static int mapped_line_count = 0; + +void IPU_NotifyLineMapped(line_t *line) { + int index = line - lines; + mapped_line_buf[mapped_line_count++] = index; + if (mapped_line_count == MAX_BUFFERED_MAPPED_LINES) { + I_Error("\nERROR: mapped_line_buf circular buffer is overflowing\n"); + } +} + +void IPU_CheckAlreadyMappedLines(void) { + for (int i = 0; i < numlines; ++i) { + if (lines[i].flags & ML_MAPPED) { + IPU_NotifyLineMapped(&lines[i]); + } + } +} + +void IPU_G_Ticker_PackMiscValues(void* buf) { + assert(sizeof(G_Ticker_MiscValues_t) <= IPUMISCVALUESSIZE); + + G_Ticker_MiscValues_t* pack = (G_Ticker_MiscValues_t*) buf; + pack->gamestate = gamestate; + if (gamestate != GS_LEVEL) + return; + pack->player_mobj.x = players[consoleplayer].mo->x; + pack->player_mobj.y = players[consoleplayer].mo->y; + pack->player_mobj.z = players[consoleplayer].mo->z; + pack->player_mobj.angle = players[consoleplayer].mo->angle; + for (int i = 0; i < IPUMAPPEDLINEUPDATES; ++i) { + if (!mapped_line_count) { + pack->mappedline_updates[i] = -1; + break; + } + pack->mappedline_updates[i] = mapped_line_buf[--mapped_line_count]; + } +} + +void IPU_G_Responder_PackMiscValues(void* src_buf, void* dst_buf) { + assert(sizeof(G_Responder_MiscValues_t) <= IPUMISCVALUESSIZE); + memcpy(dst_buf, src_buf, sizeof(G_Responder_MiscValues_t)); +} + +void IPU_Setup_PackMarkNums(void* buf) { + int bufpos = 10 * sizeof(short); + char namebuf[9] = "AMMNUM0\0"; + + for (int i = 0; i < 10; ++i, ++namebuf[6]) { + lumpindex_t lumpnum = W_GetNumForName(namebuf); + int size = W_LumpLength(lumpnum); + if (bufpos + size > IPUAMMARKBUFSIZE) { + I_Error("\nERROR: not enough space for AM mark patches\n"); + } + patch_t* lump = W_CacheLumpNum(lumpnum, PU_STATIC); + memcpy(((char*) buf) + bufpos, lump, size); + W_ReleaseLumpNum(lumpnum); + ((short*)buf)[i] = bufpos; + bufpos += size; + } + if (bufpos != IPUAMMARKBUFSIZE) { + I_Error("\nERROR: marknum patches don't fill buffer...?\n"); + } +} + +void IPU_LoadLumpForTransfer(int lumpnum, byte* buf) { + int size = W_LumpLength(lumpnum); + int required = size + sizeof(int); // Space for size field + if (required > IPUMAXLUMPBYTES) { + I_Error("\nERROR: Need %d bytes to transfer lump %d to IPU, only have %d\n", + required, lumpnum, IPUMAXLUMPBYTES); + } + ((int*)buf)[0] = size; + byte* data = W_CacheLumpNum(lumpnum, PU_STATIC); + memcpy(buf + sizeof(int), data, size); + W_ReleaseLumpNum(lumpnum); +} + diff --git a/client/src/ipu_transfer.h b/client/src/ipu_transfer.h new file mode 100644 index 0000000..cda3908 --- /dev/null +++ b/client/src/ipu_transfer.h @@ -0,0 +1,25 @@ +#ifndef __IPU_TRANSFER__ +#define __IPU_TRANSFER__ +#ifdef __cplusplus +extern "C" { +#endif + + +#include "doomtype.h" +#include "r_defs.h" + + +void IPU_G_LoadLevel_PackMiscValues(void* buf); +void IPU_G_Ticker_PackMiscValues(void* buf); +void IPU_G_Responder_PackMiscValues(void* src_buf, void* dst_buf); +void IPU_LoadLumpForTransfer(int lumpnum, byte* buf); +void IPU_Setup_PackMarkNums(void* buf); +void IPU_NotifyLineMapped(line_t *line); +void IPU_CheckAlreadyMappedLines(void); + + + +#ifdef __cplusplus +} +#endif +#endif // __IPU_TRANSFER__ // \ No newline at end of file diff --git a/client/src/m_argv.c b/client/src/m_argv.c new file mode 100644 index 0000000..5de2f28 --- /dev/null +++ b/client/src/m_argv.c @@ -0,0 +1,262 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// + + +#include +#include +#include +#include +#include + +#include "doomtype.h" +#include "i_system.h" +#include "m_argv.h" // haleyjd 20110212: warning fix +#include "m_misc.h" + +int myargc; +char** myargv; + + + + +// +// M_CheckParm +// Checks for the given parameter +// in the program's command line arguments. +// Returns the argument number (1 to argc-1) +// or 0 if not present +// + +int M_CheckParmWithArgs(char *check, int num_args) +{ + int i; + + for (i = 1; i < myargc - num_args; i++) + { + if (!strcasecmp(check, myargv[i])) + return i; + } + + return 0; +} + +// +// M_ParmExists +// +// Returns true if the given parameter exists in the program's command +// line arguments, false if not. +// + +boolean M_ParmExists(char *check) +{ + return M_CheckParm(check) != 0; +} + +int M_CheckParm(char *check) +{ + return M_CheckParmWithArgs(check, 0); +} + +#define MAXARGVS 100 + +static void LoadResponseFile(int argv_index) +{ + FILE *handle; + int size; + char *infile; + char *file; + char *response_filename; + char **newargv; + int newargc; + int i, k; + + response_filename = myargv[argv_index] + 1; + + // Read the response file into memory + handle = fopen(response_filename, "rb"); + + if (handle == NULL) + { + printf ("\nNo such response file!"); + exit(1); + } + + printf("Found response file %s!\n", response_filename); + + size = M_FileLength(handle); + + // Read in the entire file + // Allocate one byte extra - this is in case there is an argument + // at the end of the response file, in which case a '\0' will be + // needed. + + file = malloc(size + 1); + + i = 0; + + while (i < size) + { + k = fread(file + i, 1, size - i, handle); + + if (k < 0) + { + I_Error("Failed to read full contents of '%s'", response_filename); + } + + i += k; + } + + fclose(handle); + + // Create new arguments list array + + newargv = malloc(sizeof(char *) * MAXARGVS); + newargc = 0; + memset(newargv, 0, sizeof(char *) * MAXARGVS); + + // Copy all the arguments in the list up to the response file + + for (i=0; i= size) + { + break; + } + + // If the next argument is enclosed in quote marks, treat + // the contents as a single argument. This allows long filenames + // to be specified. + + if (infile[k] == '\"') + { + // Skip the first character(") + ++k; + + newargv[newargc++] = &infile[k]; + + // Read all characters between quotes + + while (k < size && infile[k] != '\"' && infile[k] != '\n') + { + ++k; + } + + if (k >= size || infile[k] == '\n') + { + I_Error("Quotes unclosed in response file '%s'", + response_filename); + } + + // Cut off the string at the closing quote + + infile[k] = '\0'; + ++k; + } + else + { + // Read in the next argument until a space is reached + + newargv[newargc++] = &infile[k]; + + while(k < size && !isspace(infile[k])) + { + ++k; + } + + // Cut off the end of the argument at the first space + + infile[k] = '\0'; + + ++k; + } + } + + // Add arguments following the response file argument + + for (i=argv_index + 1; i + + + + +void M_ClearBox (fixed_t *box) +{ + box[BOXTOP] = box[BOXRIGHT] = INT_MIN; + box[BOXBOTTOM] = box[BOXLEFT] = INT_MAX; +} + +void +M_AddToBox +( fixed_t* box, + fixed_t x, + fixed_t y ) +{ + if (xbox[BOXRIGHT]) + box[BOXRIGHT] = x; + if (ybox[BOXTOP]) + box[BOXTOP] = y; +} + + + + + diff --git a/client/src/m_bbox.h b/client/src/m_bbox.h new file mode 100644 index 0000000..958e21f --- /dev/null +++ b/client/src/m_bbox.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Nil. +// + + +#ifndef __M_BBOX__ +#define __M_BBOX__ + +#include "m_fixed.h" + + +// Bounding box coordinate storage. +enum +{ + BOXTOP, + BOXBOTTOM, + BOXLEFT, + BOXRIGHT +}; // bbox coordinates + +// Bounding box functions. +void M_ClearBox (fixed_t* box); + +void +M_AddToBox +( fixed_t* box, + fixed_t x, + fixed_t y ); + + +#endif diff --git a/client/src/m_cheat.c b/client/src/m_cheat.c new file mode 100644 index 0000000..1565f9e --- /dev/null +++ b/client/src/m_cheat.c @@ -0,0 +1,89 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Cheat sequence checking. +// + + + +#include + +#include "doomtype.h" +#include "m_cheat.h" + +// +// CHEAT SEQUENCE PACKAGE +// + +// +// Called in st_stuff module, which handles the input. +// Returns a 1 if the cheat was successful, 0 if failed. +// +int +cht_CheckCheat +( cheatseq_t* cht, + char key ) +{ + // if we make a short sequence on a cheat with parameters, this + // will not work in vanilla doom. behave the same. + + if (cht->parameter_chars > 0 && strlen(cht->sequence) < cht->sequence_len) + return false; + + if (cht->chars_read < strlen(cht->sequence)) + { + // still reading characters from the cheat code + // and verifying. reset back to the beginning + // if a key is wrong + + if (key == cht->sequence[cht->chars_read]) + ++cht->chars_read; + else + cht->chars_read = 0; + + cht->param_chars_read = 0; + } + else if (cht->param_chars_read < cht->parameter_chars) + { + // we have passed the end of the cheat sequence and are + // entering parameters now + + cht->parameter_buf[cht->param_chars_read] = key; + + ++cht->param_chars_read; + } + + if (cht->chars_read >= strlen(cht->sequence) + && cht->param_chars_read >= cht->parameter_chars) + { + cht->chars_read = cht->param_chars_read = 0; + + return true; + } + + // cheat not matched yet + + return false; +} + +void +cht_GetParam +( cheatseq_t* cht, + char* buffer ) +{ + memcpy(buffer, cht->parameter_buf, cht->parameter_chars); +} + + diff --git a/client/src/m_cheat.h b/client/src/m_cheat.h new file mode 100644 index 0000000..1a49465 --- /dev/null +++ b/client/src/m_cheat.h @@ -0,0 +1,63 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Cheat code checking. +// + + +#ifndef __M_CHEAT__ +#define __M_CHEAT__ + +#include +// +// CHEAT SEQUENCE PACKAGE +// + +// declaring a cheat + +#define CHEAT(value, parameters) \ + { value, sizeof(value) - 1, parameters, 0, 0, "" } + +#define MAX_CHEAT_LEN 25 +#define MAX_CHEAT_PARAMS 5 + +typedef struct +{ + // settings for this cheat + + char sequence[MAX_CHEAT_LEN]; + size_t sequence_len; + int parameter_chars; + + // state used during the game + + size_t chars_read; + int param_chars_read; + char parameter_buf[MAX_CHEAT_PARAMS]; +} cheatseq_t; + +int +cht_CheckCheat +( cheatseq_t* cht, + char key ); + + +void +cht_GetParam +( cheatseq_t* cht, + char* buffer ); + + +#endif diff --git a/client/src/m_config.c b/client/src/m_config.c new file mode 100644 index 0000000..2a773ca --- /dev/null +++ b/client/src/m_config.c @@ -0,0 +1,2134 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Configuration file interface. +// + + +#include +#include +#include +#include +#include + +#include "SDL2/SDL_filesystem.h" +#include "doomkeys.h" +#include "doomtype.h" +#include "i_system.h" +#include "m_argv.h" +#include "m_misc.h" + +// +// DEFAULTS +// + +// Location where all configuration data is stored - +// default.cfg, savegames, etc. + +char *configdir; + +// Default filenames for configuration files. + +static char *default_main_config; +static char *default_extra_config; + +typedef enum +{ + DEFAULT_INT, + DEFAULT_INT_HEX, + DEFAULT_STRING, + DEFAULT_FLOAT, + DEFAULT_KEY, +} default_type_t; + +typedef struct +{ + // Name of the variable + char *name; + + // Pointer to the location in memory of the variable + union { + int *i; + char **s; + float *f; + } location; + + // Type of the variable + default_type_t type; + + // If this is a key value, the original integer scancode we read from + // the config file before translating it to the internal key value. + // If zero, we didn't read this value from a config file. + int untranslated; + + // The value we translated the scancode into when we read the + // config file on startup. If the variable value is different from + // this, it has been changed and needs to be converted; otherwise, + // use the 'untranslated' value. + int original_translated; + + // If true, this config variable has been bound to a variable + // and is being used. + boolean bound; +} default_t; + +typedef struct +{ + default_t *defaults; + int numdefaults; + char *filename; +} default_collection_t; + +#define CONFIG_VARIABLE_GENERIC(name, type) \ + { #name, {NULL}, type, 0, 0, false } + +#define CONFIG_VARIABLE_KEY(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_KEY) +#define CONFIG_VARIABLE_INT(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_INT) +#define CONFIG_VARIABLE_INT_HEX(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_INT_HEX) +#define CONFIG_VARIABLE_FLOAT(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_FLOAT) +#define CONFIG_VARIABLE_STRING(name) \ + CONFIG_VARIABLE_GENERIC(name, DEFAULT_STRING) + +//! @begin_config_file default + +static default_t doom_defaults_list[] = +{ + //! + // Mouse sensitivity. This value is used to multiply input mouse + // movement to control the effect of moving the mouse. + // + // The "normal" maximum value available for this through the + // in-game options menu is 9. A value of 31 or greater will cause + // the game to crash when entering the options menu. + // + + CONFIG_VARIABLE_INT(mouse_sensitivity), + + //! + // Volume of sound effects, range 0-15. + // + + CONFIG_VARIABLE_INT(sfx_volume), + + //! + // Volume of in-game music, range 0-15. + // + + CONFIG_VARIABLE_INT(music_volume), + + //! + // @game strife + // + // If non-zero, dialogue text is displayed over characters' pictures + // when engaging actors who have voices. + // + + CONFIG_VARIABLE_INT(show_talk), + + //! + // @game strife + // + // Volume of voice sound effects, range 0-15. + // + + CONFIG_VARIABLE_INT(voice_volume), + + //! + // @game doom + // + // If non-zero, messages are displayed on the heads-up display + // in the game ("picked up a clip", etc). If zero, these messages + // are not displayed. + // + + CONFIG_VARIABLE_INT(show_messages), + + //! + // Keyboard key to turn right. + // + + CONFIG_VARIABLE_KEY(key_right), + + //! + // Keyboard key to turn left. + // + + CONFIG_VARIABLE_KEY(key_left), + + //! + // Keyboard key to move forward. + // + + CONFIG_VARIABLE_KEY(key_up), + + //! + // Keyboard key to move backward. + // + + CONFIG_VARIABLE_KEY(key_down), + + //! + // Keyboard key to strafe left. + // + + CONFIG_VARIABLE_KEY(key_strafeleft), + + //! + // Keyboard key to strafe right. + // + + CONFIG_VARIABLE_KEY(key_straferight), + + //! + // @game strife + // + // Keyboard key to use health. + // + + CONFIG_VARIABLE_KEY(key_useHealth), + + //! + // @game hexen + // + // Keyboard key to jump. + // + + CONFIG_VARIABLE_KEY(key_jump), + + //! + // @game heretic hexen + // + // Keyboard key to fly upward. + // + + CONFIG_VARIABLE_KEY(key_flyup), + + //! + // @game heretic hexen + // + // Keyboard key to fly downwards. + // + + CONFIG_VARIABLE_KEY(key_flydown), + + //! + // @game heretic hexen + // + // Keyboard key to center flying. + // + + CONFIG_VARIABLE_KEY(key_flycenter), + + //! + // @game heretic hexen + // + // Keyboard key to look up. + // + + CONFIG_VARIABLE_KEY(key_lookup), + + //! + // @game heretic hexen + // + // Keyboard key to look down. + // + + CONFIG_VARIABLE_KEY(key_lookdown), + + //! + // @game heretic hexen + // + // Keyboard key to center the view. + // + + CONFIG_VARIABLE_KEY(key_lookcenter), + + //! + // @game strife + // + // Keyboard key to query inventory. + // + + CONFIG_VARIABLE_KEY(key_invquery), + + //! + // @game strife + // + // Keyboard key to display mission objective. + // + + CONFIG_VARIABLE_KEY(key_mission), + + //! + // @game strife + // + // Keyboard key to display inventory popup. + // + + CONFIG_VARIABLE_KEY(key_invPop), + + //! + // @game strife + // + // Keyboard key to display keys popup. + // + + CONFIG_VARIABLE_KEY(key_invKey), + + //! + // @game strife + // + // Keyboard key to jump to start of inventory. + // + + CONFIG_VARIABLE_KEY(key_invHome), + + //! + // @game strife + // + // Keyboard key to jump to end of inventory. + // + + CONFIG_VARIABLE_KEY(key_invEnd), + + //! + // @game heretic hexen + // + // Keyboard key to scroll left in the inventory. + // + + CONFIG_VARIABLE_KEY(key_invleft), + + //! + // @game heretic hexen + // + // Keyboard key to scroll right in the inventory. + // + + CONFIG_VARIABLE_KEY(key_invright), + + //! + // @game strife + // + // Keyboard key to scroll left in the inventory. + // + + CONFIG_VARIABLE_KEY(key_invLeft), + + //! + // @game strife + // + // Keyboard key to scroll right in the inventory. + // + + CONFIG_VARIABLE_KEY(key_invRight), + + //! + // @game heretic hexen + // + // Keyboard key to use the current item in the inventory. + // + + CONFIG_VARIABLE_KEY(key_useartifact), + + //! + // @game strife + // + // Keyboard key to use inventory item. + // + + CONFIG_VARIABLE_KEY(key_invUse), + + //! + // @game strife + // + // Keyboard key to drop an inventory item. + // + + CONFIG_VARIABLE_KEY(key_invDrop), + + //! + // @game strife + // + // Keyboard key to look up. + // + + CONFIG_VARIABLE_KEY(key_lookUp), + + //! + // @game strife + // + // Keyboard key to look down. + // + + CONFIG_VARIABLE_KEY(key_lookDown), + + //! + // Keyboard key to fire the currently selected weapon. + // + + CONFIG_VARIABLE_KEY(key_fire), + + //! + // Keyboard key to "use" an object, eg. a door or switch. + // + + CONFIG_VARIABLE_KEY(key_use), + + //! + // Keyboard key to turn on strafing. When held down, pressing the + // key to turn left or right causes the player to strafe left or + // right instead. + // + + CONFIG_VARIABLE_KEY(key_strafe), + + //! + // Keyboard key to make the player run. + // + + CONFIG_VARIABLE_KEY(key_speed), + + //! + // If non-zero, mouse input is enabled. If zero, mouse input is + // disabled. + // + + CONFIG_VARIABLE_INT(use_mouse), + + //! + // Mouse button to fire the currently selected weapon. + // + + CONFIG_VARIABLE_INT(mouseb_fire), + + //! + // Mouse button to turn on strafing. When held down, the player + // will strafe left and right instead of turning left and right. + // + + CONFIG_VARIABLE_INT(mouseb_strafe), + + //! + // Mouse button to move forward. + // + + CONFIG_VARIABLE_INT(mouseb_forward), + + //! + // @game hexen strife + // + // Mouse button to jump. + // + + CONFIG_VARIABLE_INT(mouseb_jump), + + //! + // If non-zero, joystick input is enabled. + // + + CONFIG_VARIABLE_INT(use_joystick), + + //! + // Joystick virtual button that fires the current weapon. + // + + CONFIG_VARIABLE_INT(joyb_fire), + + //! + // Joystick virtual button that makes the player strafe while + // held down. + // + + CONFIG_VARIABLE_INT(joyb_strafe), + + //! + // Joystick virtual button to "use" an object, eg. a door or switch. + // + + CONFIG_VARIABLE_INT(joyb_use), + + //! + // Joystick virtual button that makes the player run while held + // down. + // + // If this has a value of 20 or greater, the player will always run, + // even if use_joystick is 0. + // + + CONFIG_VARIABLE_INT(joyb_speed), + + //! + // @game hexen strife + // + // Joystick virtual button that makes the player jump. + // + + CONFIG_VARIABLE_INT(joyb_jump), + + //! + // @game doom heretic hexen + // + // Screen size, range 3-11. + // + // A value of 11 gives a full-screen view with the status bar not + // displayed. A value of 10 gives a full-screen view with the + // status bar displayed. + // + + CONFIG_VARIABLE_INT(screenblocks), + + //! + // @game strife + // + // Screen size, range 3-11. + // + // A value of 11 gives a full-screen view with the status bar not + // displayed. A value of 10 gives a full-screen view with the + // status bar displayed. + // + + CONFIG_VARIABLE_INT(screensize), + + //! + // @game doom + // + // Screen detail. Zero gives normal "high detail" mode, while + // a non-zero value gives "low detail" mode. + // + + CONFIG_VARIABLE_INT(detaillevel), + + //! + // Number of sounds that will be played simultaneously. + // + + CONFIG_VARIABLE_INT(snd_channels), + + //! + // Music output device. A non-zero value gives MIDI sound output, + // while a value of zero disables music. + // + + CONFIG_VARIABLE_INT(snd_musicdevice), + + //! + // Sound effects device. A value of zero disables in-game sound + // effects, a value of 1 enables PC speaker sound effects, while + // a value in the range 2-9 enables the "normal" digital sound + // effects. + // + + CONFIG_VARIABLE_INT(snd_sfxdevice), + + //! + // Gamma correction level. A value of zero disables gamma + // correction, while a value in the range 1-4 gives increasing + // levels of gamma correction. + // + + CONFIG_VARIABLE_INT(usegamma), + + //! + // @game hexen + // + // Directory in which to store savegames. + // + + CONFIG_VARIABLE_STRING(savedir), + + //! + // @game hexen + // + // Controls whether messages are displayed in the heads-up display. + // If this has a non-zero value, messages are displayed. + // + + CONFIG_VARIABLE_INT(messageson), + + //! + // @game strife + // + // Name of background flat used by view border. + // + + CONFIG_VARIABLE_STRING(back_flat), + + //! + // @game strife + // + // Multiplayer nickname (?). + // + + CONFIG_VARIABLE_STRING(nickname), + + //! + // Multiplayer chat macro: message to send when alt+0 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro0), + + //! + // Multiplayer chat macro: message to send when alt+1 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro1), + + //! + // Multiplayer chat macro: message to send when alt+2 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro2), + + //! + // Multiplayer chat macro: message to send when alt+3 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro3), + + //! + // Multiplayer chat macro: message to send when alt+4 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro4), + + //! + // Multiplayer chat macro: message to send when alt+5 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro5), + + //! + // Multiplayer chat macro: message to send when alt+6 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro6), + + //! + // Multiplayer chat macro: message to send when alt+7 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro7), + + //! + // Multiplayer chat macro: message to send when alt+8 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro8), + + //! + // Multiplayer chat macro: message to send when alt+9 is pressed. + // + + CONFIG_VARIABLE_STRING(chatmacro9), + +}; + +static default_collection_t doom_defaults = +{ + doom_defaults_list, + arrlen(doom_defaults_list), + NULL, +}; + +//! @begin_config_file extended + +static default_t extra_defaults_list[] = +{ + //! + // Name of the SDL video driver to use. If this is an empty string, + // the default video driver is used. + // + + CONFIG_VARIABLE_STRING(video_driver), + + //! + // Position of the window on the screen when running in windowed + // mode. Accepted values are: "" (empty string) - don't care, + // "center" - place window at center of screen, "x,y" - place + // window at the specified coordinates. + // + + CONFIG_VARIABLE_STRING(window_position), + + //! + // If non-zero, the game will run in full screen mode. If zero, + // the game will run in a window. + // + + CONFIG_VARIABLE_INT(fullscreen), + + //! + // Index of the display on which the game should run. This has no + // effect if running in windowed mode (fullscreen=0) and + // window_position is not set to "center". + // + + CONFIG_VARIABLE_INT(video_display), + + //! + // If non-zero, the screen will be stretched vertically to display + // correctly on a square pixel video mode. + // + + CONFIG_VARIABLE_INT(aspect_ratio_correct), + + //! + // If non-zero, forces integer scales for resolution-independent rendering. + // + + CONFIG_VARIABLE_INT(integer_scaling), + + // If non-zero, any pillar/letter boxes drawn around the game area + // will "flash" when the game palette changes, simulating the VGA + // "porch" + + CONFIG_VARIABLE_INT(vga_porch_flash), + + //! + // Window width when running in windowed mode. + // + + CONFIG_VARIABLE_INT(window_width), + + //! + // Window height when running in windowed mode. + // + + CONFIG_VARIABLE_INT(window_height), + + //! + // Width for screen mode when running fullscreen. + // If this and fullscreen_height are both set to zero, we run + // fullscreen as a desktop window that covers the entire screen, + // rather than ever switching screen modes. It should usually + // be unnecessary to set this value. + // + + CONFIG_VARIABLE_INT(fullscreen_width), + + //! + // Height for screen mode when running fullscreen. + // See documentation for fullscreen_width. + // + + CONFIG_VARIABLE_INT(fullscreen_height), + + //! + // If non-zero, force the use of a software renderer. For use on + // systems lacking hardware acceleration. + // + + CONFIG_VARIABLE_INT(force_software_renderer), + + //! + // Maximum number of pixels to use for intermediate scaling buffer. + // More pixels mean that the screen can be rendered more precisely, + // but there are diminishing returns on quality. The default limits to + // 16,000,000 pixels, which is enough to cover 4K monitor standards. + + CONFIG_VARIABLE_INT(max_scaling_buffer_pixels), + + //! + // Number of milliseconds to wait on startup after the video mode + // has been set, before the game will start. This allows the + // screen to settle on some monitors that do not display an image + // for a brief interval after changing video modes. + // + + CONFIG_VARIABLE_INT(startup_delay), + + //! + // If non-zero, save screenshots in PNG format. If zero, screenshots are + // saved in PCX format, as Vanilla Doom does. + // + + CONFIG_VARIABLE_INT(png_screenshots), + + //! + // Sound output sample rate, in Hz. Typical values to use are + // 11025, 22050, 44100 and 48000. + // + + CONFIG_VARIABLE_INT(snd_samplerate), + + //! + // Maximum number of bytes to allocate for caching converted sound + // effects in memory. If set to zero, there is no limit applied. + // + + CONFIG_VARIABLE_INT(snd_cachesize), + + //! + // Maximum size of the output sound buffer size in milliseconds. + // Sound output is generated periodically in slices. Higher values + // might be more efficient but will introduce latency to the + // sound output. The default is 28ms (one slice per tic with the + // 35fps timer). + // + + CONFIG_VARIABLE_INT(snd_maxslicetime_ms), + + //! + // If non-zero, sound effects will have their pitch varied up or + // down by a random amount during play. If zero, sound effects + // play back at their default pitch. + // + + CONFIG_VARIABLE_INT(snd_pitchshift), + + //! + // External command to invoke to perform MIDI playback. If set to + // the empty string, SDL_mixer's internal MIDI playback is used. + // This only has any effect when snd_musicdevice is set to General + // MIDI output. + // + + CONFIG_VARIABLE_STRING(snd_musiccmd), + + //! + // Controls whether libsamplerate support is used for performing + // sample rate conversions of sound effects. Support for this + // must be compiled into the program. + // + // If zero, libsamplerate support is disabled. If non-zero, + // libsamplerate is enabled. Increasing values roughly correspond + // to higher quality conversion; the higher the quality, the + // slower the conversion process. Linear conversion = 1; + // Zero order hold = 2; Fast Sinc filter = 3; Medium quality + // Sinc filter = 4; High quality Sinc filter = 5. + // + + CONFIG_VARIABLE_INT(use_libsamplerate), + + //! + // Scaling factor used by libsamplerate. This is used when converting + // sounds internally back into integer form; normally it should not + // be necessary to change it from the default value. The only time + // it might be needed is if a PWAD file is loaded that contains very + // loud sounds, in which case the conversion may cause sound clipping + // and the scale factor should be reduced. The lower the value, the + // quieter the sound effects become, so it should be set as high as is + // possible without clipping occurring. + + CONFIG_VARIABLE_FLOAT(libsamplerate_scale), + + //! + // Full path to a directory containing configuration files for + // substitute music packs. These packs contain high quality renderings + // of game music to be played instead of using the system's built-in + // MIDI playback. + // + + CONFIG_VARIABLE_STRING(music_pack_path), + + //! + // Full path to a Timidity configuration file to use for MIDI + // playback. The file will be evaluated from the directory where + // it is evaluated, so there is no need to add "dir" commands + // into it. + // + + CONFIG_VARIABLE_STRING(timidity_cfg_path), + + //! + // Path to GUS patch files to use when operating in GUS emulation + // mode. + // + + CONFIG_VARIABLE_STRING(gus_patch_path), + + //! + // Number of kilobytes of RAM to use in GUS emulation mode. Valid + // values are 256, 512, 768 or 1024. + // + + CONFIG_VARIABLE_INT(gus_ram_kb), + + //! + // @game doom strife + // + // If non-zero, the Vanilla savegame limit is enforced; if the + // savegame exceeds 180224 bytes in size, the game will exit with + // an error. If this has a value of zero, there is no limit to + // the size of savegames. + // + + CONFIG_VARIABLE_INT(vanilla_savegame_limit), + + //! + // @game doom strife + // + // If non-zero, the Vanilla demo size limit is enforced; the game + // exits with an error when a demo exceeds the demo size limit + // (128KiB by default). If this has a value of zero, there is no + // limit to the size of demos. + // + + CONFIG_VARIABLE_INT(vanilla_demo_limit), + + //! + // If non-zero, the game behaves like Vanilla Doom, always assuming + // an American keyboard mapping. If this has a value of zero, the + // native keyboard mapping of the keyboard is used. + // + + CONFIG_VARIABLE_INT(vanilla_keyboard_mapping), + + //! + // Name to use in network games for identification. This is only + // used on the "waiting" screen while waiting for the game to start. + // + + CONFIG_VARIABLE_STRING(player_name), + + //! + // If this is non-zero, the mouse will be "grabbed" when running + // in windowed mode so that it can be used as an input device. + // When running full screen, this has no effect. + // + + CONFIG_VARIABLE_INT(grabmouse), + + //! + // If non-zero, all vertical mouse movement is ignored. This + // emulates the behavior of the "novert" tool available under DOS + // that performs the same function. + // + + CONFIG_VARIABLE_INT(novert), + + //! + // Mouse acceleration factor. When the speed of mouse movement + // exceeds the threshold value (mouse_threshold), the speed is + // multiplied by this value. + // + + CONFIG_VARIABLE_FLOAT(mouse_acceleration), + + //! + // Mouse acceleration threshold. When the speed of mouse movement + // exceeds this threshold value, the speed is multiplied by an + // acceleration factor (mouse_acceleration). + // + + CONFIG_VARIABLE_INT(mouse_threshold), + + //! + // Mouse button to strafe left. + // + + CONFIG_VARIABLE_INT(mouseb_strafeleft), + + //! + // Mouse button to strafe right. + // + + CONFIG_VARIABLE_INT(mouseb_straferight), + + //! + // Mouse button to "use" an object, eg. a door or switch. + // + + CONFIG_VARIABLE_INT(mouseb_use), + + //! + // Mouse button to move backwards. + // + + CONFIG_VARIABLE_INT(mouseb_backward), + + //! + // Mouse button to cycle to the previous weapon. + // + + CONFIG_VARIABLE_INT(mouseb_prevweapon), + + //! + // Mouse button to cycle to the next weapon. + // + + CONFIG_VARIABLE_INT(mouseb_nextweapon), + + //! + // If non-zero, double-clicking a mouse button acts like pressing + // the "use" key to use an object in-game, eg. a door or switch. + // + + CONFIG_VARIABLE_INT(dclick_use), + + //! + // SDL GUID string indicating the joystick to use. An empty string + // indicates that no joystick is configured. + // + + CONFIG_VARIABLE_STRING(joystick_guid), + + //! + // Index of SDL joystick to use; this is only used in the case where + // multiple identical joystick devices are connected which have the + // same GUID, to distinguish between devices. + // + + CONFIG_VARIABLE_INT(joystick_index), + + //! + // Joystick axis to use to for horizontal (X) movement. + // + + CONFIG_VARIABLE_INT(joystick_x_axis), + + //! + // If non-zero, movement on the horizontal joystick axis is inverted. + // + + CONFIG_VARIABLE_INT(joystick_x_invert), + + //! + // Joystick axis to use to for vertical (Y) movement. + // + + CONFIG_VARIABLE_INT(joystick_y_axis), + + //! + // If non-zero, movement on the vertical joystick axis is inverted. + // + + CONFIG_VARIABLE_INT(joystick_y_invert), + + //! + // Joystick axis to use to for strafing movement. + // + + CONFIG_VARIABLE_INT(joystick_strafe_axis), + + //! + // If non-zero, movement on the joystick axis used for strafing + // is inverted. + // + + CONFIG_VARIABLE_INT(joystick_strafe_invert), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #0. + // + + CONFIG_VARIABLE_INT(joystick_physical_button0), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #1. + // + + CONFIG_VARIABLE_INT(joystick_physical_button1), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #2. + // + + CONFIG_VARIABLE_INT(joystick_physical_button2), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #3. + // + + CONFIG_VARIABLE_INT(joystick_physical_button3), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #4. + // + + CONFIG_VARIABLE_INT(joystick_physical_button4), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #5. + // + + CONFIG_VARIABLE_INT(joystick_physical_button5), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #6. + // + + CONFIG_VARIABLE_INT(joystick_physical_button6), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #7. + // + + CONFIG_VARIABLE_INT(joystick_physical_button7), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #8. + // + + CONFIG_VARIABLE_INT(joystick_physical_button8), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #9. + // + + CONFIG_VARIABLE_INT(joystick_physical_button9), + + //! + // The physical joystick button that corresponds to joystick + // virtual button #10. + // + + CONFIG_VARIABLE_INT(joystick_physical_button10), + + //! + // Joystick virtual button to make the player strafe left. + // + + CONFIG_VARIABLE_INT(joyb_strafeleft), + + //! + // Joystick virtual button to make the player strafe right. + // + + CONFIG_VARIABLE_INT(joyb_straferight), + + //! + // Joystick virtual button to activate the menu. + // + + CONFIG_VARIABLE_INT(joyb_menu_activate), + + //! + // Joystick virtual button to toggle the automap. + // + + CONFIG_VARIABLE_INT(joyb_toggle_automap), + + //! + // Joystick virtual button that cycles to the previous weapon. + // + + CONFIG_VARIABLE_INT(joyb_prevweapon), + + //! + // Joystick virtual button that cycles to the next weapon. + // + + CONFIG_VARIABLE_INT(joyb_nextweapon), + + //! + // Key to pause or unpause the game. + // + + CONFIG_VARIABLE_KEY(key_pause), + + //! + // Key that activates the menu when pressed. + // + + CONFIG_VARIABLE_KEY(key_menu_activate), + + //! + // Key that moves the cursor up on the menu. + // + + CONFIG_VARIABLE_KEY(key_menu_up), + + //! + // Key that moves the cursor down on the menu. + // + + CONFIG_VARIABLE_KEY(key_menu_down), + + //! + // Key that moves the currently selected slider on the menu left. + // + + CONFIG_VARIABLE_KEY(key_menu_left), + + //! + // Key that moves the currently selected slider on the menu right. + // + + CONFIG_VARIABLE_KEY(key_menu_right), + + //! + // Key to go back to the previous menu. + // + + CONFIG_VARIABLE_KEY(key_menu_back), + + //! + // Key to activate the currently selected menu item. + // + + CONFIG_VARIABLE_KEY(key_menu_forward), + + //! + // Key to answer 'yes' to a question in the menu. + // + + CONFIG_VARIABLE_KEY(key_menu_confirm), + + //! + // Key to answer 'no' to a question in the menu. + // + + CONFIG_VARIABLE_KEY(key_menu_abort), + + //! + // Keyboard shortcut to bring up the help screen. + // + + CONFIG_VARIABLE_KEY(key_menu_help), + + //! + // Keyboard shortcut to bring up the save game menu. + // + + CONFIG_VARIABLE_KEY(key_menu_save), + + //! + // Keyboard shortcut to bring up the load game menu. + // + + CONFIG_VARIABLE_KEY(key_menu_load), + + //! + // Keyboard shortcut to bring up the sound volume menu. + // + + CONFIG_VARIABLE_KEY(key_menu_volume), + + //! + // Keyboard shortcut to toggle the detail level. + // + + CONFIG_VARIABLE_KEY(key_menu_detail), + + //! + // Keyboard shortcut to quicksave the current game. + // + + CONFIG_VARIABLE_KEY(key_menu_qsave), + + //! + // Keyboard shortcut to end the game. + // + + CONFIG_VARIABLE_KEY(key_menu_endgame), + + //! + // Keyboard shortcut to toggle heads-up messages. + // + + CONFIG_VARIABLE_KEY(key_menu_messages), + + //! + // Keyboard shortcut to load the last quicksave. + // + + CONFIG_VARIABLE_KEY(key_menu_qload), + + //! + // Keyboard shortcut to quit the game. + // + + CONFIG_VARIABLE_KEY(key_menu_quit), + + //! + // Keyboard shortcut to toggle the gamma correction level. + // + + CONFIG_VARIABLE_KEY(key_menu_gamma), + + //! + // Keyboard shortcut to switch view in multiplayer. + // + + CONFIG_VARIABLE_KEY(key_spy), + + //! + // Keyboard shortcut to increase the screen size. + // + + CONFIG_VARIABLE_KEY(key_menu_incscreen), + + //! + // Keyboard shortcut to decrease the screen size. + // + + CONFIG_VARIABLE_KEY(key_menu_decscreen), + + //! + // Keyboard shortcut to save a screenshot. + // + + CONFIG_VARIABLE_KEY(key_menu_screenshot), + + //! + // Key to toggle the map view. + // + + CONFIG_VARIABLE_KEY(key_map_toggle), + + //! + // Key to pan north when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_north), + + //! + // Key to pan south when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_south), + + //! + // Key to pan east when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_east), + + //! + // Key to pan west when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_west), + + //! + // Key to zoom in when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_zoomin), + + //! + // Key to zoom out when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_zoomout), + + //! + // Key to zoom out the maximum amount when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_maxzoom), + + //! + // Key to toggle follow mode when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_follow), + + //! + // Key to toggle the grid display when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_grid), + + //! + // Key to set a mark when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_mark), + + //! + // Key to clear all marks when in the map view. + // + + CONFIG_VARIABLE_KEY(key_map_clearmark), + + //! + // Key to select weapon 1. + // + + CONFIG_VARIABLE_KEY(key_weapon1), + + //! + // Key to select weapon 2. + // + + CONFIG_VARIABLE_KEY(key_weapon2), + + //! + // Key to select weapon 3. + // + + CONFIG_VARIABLE_KEY(key_weapon3), + + //! + // Key to select weapon 4. + // + + CONFIG_VARIABLE_KEY(key_weapon4), + + //! + // Key to select weapon 5. + // + + CONFIG_VARIABLE_KEY(key_weapon5), + + //! + // Key to select weapon 6. + // + + CONFIG_VARIABLE_KEY(key_weapon6), + + //! + // Key to select weapon 7. + // + + CONFIG_VARIABLE_KEY(key_weapon7), + + //! + // Key to select weapon 8. + // + + CONFIG_VARIABLE_KEY(key_weapon8), + + //! + // Key to cycle to the previous weapon. + // + + CONFIG_VARIABLE_KEY(key_prevweapon), + + //! + // Key to cycle to the next weapon. + // + + CONFIG_VARIABLE_KEY(key_nextweapon), + + //! + // @game hexen + // + // Key to use one of each artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_all), + + //! + // @game hexen + // + // Key to use "quartz flask" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_health), + + //! + // @game hexen + // + // Key to use "flechette" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_poisonbag), + + //! + // @game hexen + // + // Key to use "disc of repulsion" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_blastradius), + + //! + // @game hexen + // + // Key to use "chaos device" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_teleport), + + //! + // @game hexen + // + // Key to use "banishment device" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_teleportother), + + //! + // @game hexen + // + // Key to use "porkalator" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_egg), + + //! + // @game hexen + // + // Key to use "icon of the defender" artifact. + // + + CONFIG_VARIABLE_KEY(key_arti_invulnerability), + + //! + // Key to re-display last message. + // + + CONFIG_VARIABLE_KEY(key_message_refresh), + + //! + // Key to quit the game when recording a demo. + // + + CONFIG_VARIABLE_KEY(key_demo_quit), + + //! + // Key to send a message during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msg), + + //! + // Key to send a message to player 1 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer1), + + //! + // Key to send a message to player 2 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer2), + + //! + // Key to send a message to player 3 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer3), + + //! + // Key to send a message to player 4 during multiplayer games. + // + + CONFIG_VARIABLE_KEY(key_multi_msgplayer4) + +}; + +static default_collection_t extra_defaults = +{ + extra_defaults_list, + arrlen(extra_defaults_list), + NULL, +}; + +// Search a collection for a variable + +static default_t *SearchCollection(default_collection_t *collection, char *name) +{ + int i; + + for (i=0; inumdefaults; ++i) + { + if (!strcmp(name, collection->defaults[i].name)) + { + return &collection->defaults[i]; + } + } + + return NULL; +} + +// Mapping from DOS keyboard scan code to internal key code (as defined +// in doomkey.h). I think I (fraggle) reused this from somewhere else +// but I can't find where. Anyway, notes: +// * KEY_PAUSE is wrong - it's in the KEY_NUMLOCK spot. This shouldn't +// matter in terms of Vanilla compatibility because neither of +// those were valid for key bindings. +// * There is no proper scan code for PrintScreen (on DOS machines it +// sends an interrupt). So I added a fake scan code of 126 for it. +// The presence of this is important so we can bind PrintScreen as +// a screenshot key. +static const int scantokey[128] = +{ + 0 , 27, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '-', '=', KEY_BACKSPACE, 9, + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', 13, KEY_RCTRL, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', + '\'', '`', KEY_RSHIFT,'\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', KEY_RSHIFT,KEYP_MULTIPLY, + KEY_RALT, ' ', KEY_CAPSLOCK,KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, + KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, /*KEY_NUMLOCK?*/KEY_PAUSE,KEY_SCRLCK,KEY_HOME, + KEY_UPARROW,KEY_PGUP,KEY_MINUS,KEY_LEFTARROW,KEYP_5,KEY_RIGHTARROW,KEYP_PLUS,KEY_END, + KEY_DOWNARROW,KEY_PGDN,KEY_INS,KEY_DEL,0, 0, 0, KEY_F11, + KEY_F12, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, KEY_PRTSCR, 0 +}; + + +static void SaveDefaultCollection(default_collection_t *collection) +{ + default_t *defaults; + int i, v; + FILE *f; + + f = fopen (collection->filename, "w"); + if (!f) + return; // can't write the file, but don't complain + + defaults = collection->defaults; + + for (i=0 ; inumdefaults ; i++) + { + int chars_written; + + // Ignore unbound variables + + if (!defaults[i].bound) + { + continue; + } + + // Print the name and line up all values at 30 characters + + chars_written = fprintf(f, "%s ", defaults[i].name); + + for (; chars_written < 30; ++chars_written) + fprintf(f, " "); + + // Print the value + + switch (defaults[i].type) + { + case DEFAULT_KEY: + + // use the untranslated version if we can, to reduce + // the possibility of screwing up the user's config + // file + + v = *defaults[i].location.i; + + if (v == KEY_RSHIFT) + { + // Special case: for shift, force scan code for + // right shift, as this is what Vanilla uses. + // This overrides the change check below, to fix + // configuration files made by old versions that + // mistakenly used the scan code for left shift. + + v = 54; + } + else if (defaults[i].untranslated + && v == defaults[i].original_translated) + { + // Has not been changed since the last time we + // read the config file. + + v = defaults[i].untranslated; + } + else + { + // search for a reverse mapping back to a scancode + // in the scantokey table + + int s; + + for (s=0; s<128; ++s) + { + if (scantokey[s] == v) + { + v = s; + break; + } + } + } + + fprintf(f, "%i", v); + break; + + case DEFAULT_INT: + fprintf(f, "%i", *defaults[i].location.i); + break; + + case DEFAULT_INT_HEX: + fprintf(f, "0x%x", *defaults[i].location.i); + break; + + case DEFAULT_FLOAT: + fprintf(f, "%f", *defaults[i].location.f); + break; + + case DEFAULT_STRING: + fprintf(f,"\"%s\"", *defaults[i].location.s); + break; + } + + fprintf(f, "\n"); + } + + fclose (f); +} + +// Parses integer values in the configuration file + +static int ParseIntParameter(char *strparm) +{ + int parm; + + if (strparm[0] == '0' && strparm[1] == 'x') + sscanf(strparm+2, "%x", &parm); + else + sscanf(strparm, "%i", &parm); + + return parm; +} + +static void SetVariable(default_t *def, char *value) +{ + int intparm; + + // parameter found + + switch (def->type) + { + case DEFAULT_STRING: + *def->location.s = M_StringDuplicate(value); + break; + + case DEFAULT_INT: + case DEFAULT_INT_HEX: + *def->location.i = ParseIntParameter(value); + break; + + case DEFAULT_KEY: + + // translate scancodes read from config + // file (save the old value in untranslated) + + intparm = ParseIntParameter(value); + def->untranslated = intparm; + if (intparm >= 0 && intparm < 128) + { + intparm = scantokey[intparm]; + } + else + { + intparm = 0; + } + + def->original_translated = intparm; + *def->location.i = intparm; + break; + + case DEFAULT_FLOAT: + *def->location.f = (float) atof(value); + break; + } +} + +static void LoadDefaultCollection(default_collection_t *collection) +{ + FILE *f; + default_t *def; + char defname[80]; + char strparm[100]; + + // read the file in, overriding any set defaults + f = fopen(collection->filename, "r"); + + if (f == NULL) + { + // File not opened, but don't complain. + // It's probably just the first time they ran the game. + + return; + } + + while (!feof(f)) + { + if (fscanf(f, "%79s %99[^\n]\n", defname, strparm) != 2) + { + // This line doesn't match + + continue; + } + + // Find the setting in the list + + def = SearchCollection(collection, defname); + + if (def == NULL || !def->bound) + { + // Unknown variable? Unbound variables are also treated + // as unknown. + + continue; + } + + // Strip off trailing non-printable characters (\r characters + // from DOS text files) + + while (strlen(strparm) > 0 && !isprint(strparm[strlen(strparm)-1])) + { + strparm[strlen(strparm)-1] = '\0'; + } + + // Surrounded by quotes? If so, remove them. + if (strlen(strparm) >= 2 + && strparm[0] == '"' && strparm[strlen(strparm) - 1] == '"') + { + strparm[strlen(strparm) - 1] = '\0'; + memmove(strparm, strparm + 1, sizeof(strparm) - 1); + } + + SetVariable(def, strparm); + } + + fclose (f); +} + +// Set the default filenames to use for configuration files. + +void M_SetConfigFilenames(char *main_config, char *extra_config) +{ + default_main_config = main_config; + default_extra_config = extra_config; +} + +// +// M_SaveDefaults +// + +void M_SaveDefaults (void) +{ + SaveDefaultCollection(&doom_defaults); + SaveDefaultCollection(&extra_defaults); +} + +// +// Save defaults to alternate filenames +// + +void M_SaveDefaultsAlternate(char *main, char *extra) +{ + char *orig_main; + char *orig_extra; + + // Temporarily change the filenames + + orig_main = doom_defaults.filename; + orig_extra = extra_defaults.filename; + + doom_defaults.filename = main; + extra_defaults.filename = extra; + + M_SaveDefaults(); + + // Restore normal filenames + + doom_defaults.filename = orig_main; + extra_defaults.filename = orig_extra; +} + +// +// M_LoadDefaults +// + +void M_LoadDefaults (void) +{ + int i; + + // check for a custom default file + + //! + // @arg + // @vanilla + // + // Load main configuration from the specified file, instead of the + // default. + // + + i = M_CheckParmWithArgs("-config", 1); + + if (i) + { + doom_defaults.filename = myargv[i+1]; + printf (" default file: %s\n",doom_defaults.filename); + } + else + { + doom_defaults.filename + = M_StringJoin(configdir, default_main_config, NULL); + } + + printf("saving config in %s\n", doom_defaults.filename); + + //! + // @arg + // + // Load additional configuration from the specified file, instead of + // the default. + // + + i = M_CheckParmWithArgs("-extraconfig", 1); + + if (i) + { + extra_defaults.filename = myargv[i+1]; + printf(" extra configuration file: %s\n", + extra_defaults.filename); + } + else + { + extra_defaults.filename + = M_StringJoin(configdir, default_extra_config, NULL); + } + + LoadDefaultCollection(&doom_defaults); + LoadDefaultCollection(&extra_defaults); +} + +// Get a configuration file variable by its name + +static default_t *GetDefaultForName(char *name) +{ + default_t *result; + + // Try the main list and the extras + + result = SearchCollection(&doom_defaults, name); + + if (result == NULL) + { + result = SearchCollection(&extra_defaults, name); + } + + // Not found? Internal error. + + if (result == NULL) + { + I_Error("Unknown configuration variable: '%s'", name); + } + + return result; +} + +// +// Bind a variable to a given configuration file variable, by name. +// + +void M_BindIntVariable(char *name, int *location) +{ + default_t *variable; + + variable = GetDefaultForName(name); + assert(variable->type == DEFAULT_INT + || variable->type == DEFAULT_INT_HEX + || variable->type == DEFAULT_KEY); + + variable->location.i = location; + variable->bound = true; +} + +void M_BindFloatVariable(char *name, float *location) +{ + default_t *variable; + + variable = GetDefaultForName(name); + assert(variable->type == DEFAULT_FLOAT); + + variable->location.f = location; + variable->bound = true; +} + +void M_BindStringVariable(char *name, char **location) +{ + default_t *variable; + + variable = GetDefaultForName(name); + assert(variable->type == DEFAULT_STRING); + + variable->location.s = location; + variable->bound = true; +} + +// Set the value of a particular variable; an API function for other +// parts of the program to assign values to config variables by name. + +boolean M_SetVariable(char *name, char *value) +{ + default_t *variable; + + variable = GetDefaultForName(name); + + if (variable == NULL || !variable->bound) + { + return false; + } + + SetVariable(variable, value); + + return true; +} + +// Get the value of a variable. + +int M_GetIntVariable(char *name) +{ + default_t *variable; + + variable = GetDefaultForName(name); + + if (variable == NULL || !variable->bound + || (variable->type != DEFAULT_INT && variable->type != DEFAULT_INT_HEX)) + { + return 0; + } + + return *variable->location.i; +} + +const char *M_GetStringVariable(char *name) +{ + default_t *variable; + + variable = GetDefaultForName(name); + + if (variable == NULL || !variable->bound + || variable->type != DEFAULT_STRING) + { + return NULL; + } + + return *variable->location.s; +} + +float M_GetFloatVariable(char *name) +{ + default_t *variable; + + variable = GetDefaultForName(name); + + if (variable == NULL || !variable->bound + || variable->type != DEFAULT_FLOAT) + { + return 0; + } + + return *variable->location.f; +} + +// Get the path to the default configuration dir to use, if NULL +// is passed to M_SetConfigDir. + +static char *GetDefaultConfigDir(void) +{ + // Configuration settings are stored in an OS-appropriate path + // determined by SDL. On typical Unix systems, this might be + // ~/.local/share/chocolate-doom. + char *result; + + result = SDL_GetPrefPath("", "chocolate-doom"); + return result; +} + +// +// SetConfigDir: +// +// Sets the location of the configuration directory, where configuration +// files are stored - default.cfg, chocolate-doom.cfg, savegames, etc. +// + +void M_SetConfigDir(char *dir) +{ + // Use the directory that was passed, or find the default. + + if (dir != NULL) + { + configdir = dir; + } + else + { + configdir = GetDefaultConfigDir(); + } + + if (strcmp(configdir, "") != 0) + { + printf("Using %s for configuration and saves\n", configdir); + } + + // Make the directory if it doesn't already exist: + + M_MakeDirectory(configdir); +} + +// +// Calculate the path to the directory to use to store save games. +// Creates the directory as necessary. +// + +char *M_GetSaveGameDir(char *iwadname) +{ + char *savegamedir; + char *topdir; + int p; + + //! + // @arg + // + // Specify a path from which to load and save games. If the directory + // does not exist then it will automatically be created. + // + + p = M_CheckParmWithArgs("-savedir", 1); + if (p) + { + savegamedir = myargv[p + 1]; + if (!M_FileExists(savegamedir)) + { + M_MakeDirectory(savegamedir); + } + + // add separator at end just in case + savegamedir = M_StringJoin(savegamedir, DIR_SEPARATOR_S, NULL); + + printf("Save directory changed to %s.\n", savegamedir); + } + else + { + // ~/.local/share/chocolate-doom/savegames + + topdir = M_StringJoin(configdir, "savegames", NULL); + M_MakeDirectory(topdir); + + // eg. ~/.local/share/chocolate-doom/savegames/doom2.wad/ + + savegamedir = M_StringJoin(topdir, DIR_SEPARATOR_S, iwadname, + DIR_SEPARATOR_S, NULL); + + M_MakeDirectory(savegamedir); + + free(topdir); + } + + return savegamedir; +} + diff --git a/client/src/m_config.h b/client/src/m_config.h new file mode 100644 index 0000000..43882a4 --- /dev/null +++ b/client/src/m_config.h @@ -0,0 +1,41 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Configuration file interface. +// + + +#ifndef __M_CONFIG__ +#define __M_CONFIG__ + +#include "doomtype.h" + +void M_LoadDefaults(void); +void M_SaveDefaults(void); +void M_SaveDefaultsAlternate(char *main, char *extra); +void M_SetConfigDir(char *dir); +void M_BindIntVariable(char *name, int *variable); +void M_BindFloatVariable(char *name, float *variable); +void M_BindStringVariable(char *name, char **variable); +boolean M_SetVariable(char *name, char *value); +int M_GetIntVariable(char *name); +const char *M_GetStringVariable(char *name); +float M_GetFloatVariable(char *name); +void M_SetConfigFilenames(char *main_config, char *extra_config); +char *M_GetSaveGameDir(char *iwadname); + +extern char *configdir; + +#endif diff --git a/client/src/m_controls.c b/client/src/m_controls.c new file mode 100644 index 0000000..993ec5e --- /dev/null +++ b/client/src/m_controls.c @@ -0,0 +1,397 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// + +#include "doomkeys.h" +#include "m_config.h" +#include "m_misc.h" + +// +// Keyboard controls +// + +int key_right = KEY_RIGHTARROW; +int key_left = KEY_LEFTARROW; + +int key_up = KEY_UPARROW; +int key_down = KEY_DOWNARROW; +int key_strafeleft = ','; +int key_straferight = '.'; +int key_fire = KEY_RCTRL; +int key_use = ' '; +int key_strafe = KEY_RALT; +int key_speed = KEY_RSHIFT; + +// +// Heretic keyboard controls +// + +int key_flyup = KEY_PGUP; +int key_flydown = KEY_INS; +int key_flycenter = KEY_HOME; + +int key_lookup = KEY_PGDN; +int key_lookdown = KEY_DEL; +int key_lookcenter = KEY_END; + +int key_invleft = '['; +int key_invright = ']'; +int key_useartifact = KEY_ENTER; + +// +// Hexen key controls +// + +int key_jump = '/'; + +int key_arti_all = KEY_BACKSPACE; +int key_arti_health = '\\'; +int key_arti_poisonbag = '0'; +int key_arti_blastradius = '9'; +int key_arti_teleport = '8'; +int key_arti_teleportother = '7'; +int key_arti_egg = '6'; +int key_arti_invulnerability = '5'; + +// +// Strife key controls +// +// haleyjd 09/01/10 +// + +// Note: Strife also uses key_invleft, key_invright, key_jump, key_lookup, and +// key_lookdown, but with different default values. + +int key_usehealth = 'h'; +int key_invquery = 'q'; +int key_mission = 'w'; +int key_invpop = 'z'; +int key_invkey = 'k'; +int key_invhome = KEY_HOME; +int key_invend = KEY_END; +int key_invuse = KEY_ENTER; +int key_invdrop = KEY_BACKSPACE; + + +// +// Mouse controls +// + +int mousebfire = 0; +int mousebstrafe = 1; +int mousebforward = 2; + +int mousebjump = -1; + +int mousebstrafeleft = -1; +int mousebstraferight = -1; +int mousebbackward = -1; +int mousebuse = -1; + +int mousebprevweapon = -1; +int mousebnextweapon = -1; + + +int key_message_refresh = KEY_ENTER; +int key_pause = KEY_PAUSE; +int key_demo_quit = 'q'; +int key_spy = KEY_F12; + +// Multiplayer chat keys: + +int key_multi_msg = 't'; +int key_multi_msgplayer[8]; + +// Weapon selection keys: + +int key_weapon1 = '1'; +int key_weapon2 = '2'; +int key_weapon3 = '3'; +int key_weapon4 = '4'; +int key_weapon5 = '5'; +int key_weapon6 = '6'; +int key_weapon7 = '7'; +int key_weapon8 = '8'; +int key_prevweapon = 0; +int key_nextweapon = 0; + +// Map control keys: + +int key_map_north = KEY_UPARROW; +int key_map_south = KEY_DOWNARROW; +int key_map_east = KEY_RIGHTARROW; +int key_map_west = KEY_LEFTARROW; +int key_map_zoomin = '='; +int key_map_zoomout = '-'; +int key_map_toggle = KEY_TAB; +int key_map_maxzoom = '0'; +int key_map_follow = 'f'; +int key_map_grid = 'g'; +int key_map_mark = 'm'; +int key_map_clearmark = 'c'; + +// menu keys: + +int key_menu_activate = KEY_ESCAPE; +int key_menu_up = KEY_UPARROW; +int key_menu_down = KEY_DOWNARROW; +int key_menu_left = KEY_LEFTARROW; +int key_menu_right = KEY_RIGHTARROW; +int key_menu_back = KEY_BACKSPACE; +int key_menu_forward = KEY_ENTER; +int key_menu_confirm = 'y'; +int key_menu_abort = 'n'; + +int key_menu_help = KEY_F1; +int key_menu_save = KEY_F2; +int key_menu_load = KEY_F3; +int key_menu_volume = KEY_F4; +int key_menu_detail = KEY_F5; +int key_menu_qsave = KEY_F6; +int key_menu_endgame = KEY_F7; +int key_menu_messages = KEY_F8; +int key_menu_qload = KEY_F9; +int key_menu_quit = KEY_F10; +int key_menu_gamma = KEY_F11; + +int key_menu_incscreen = KEY_EQUALS; +int key_menu_decscreen = KEY_MINUS; +int key_menu_screenshot = 0; + +// +// Joystick controls +// + +int joybfire = 0; +int joybstrafe = 1; +int joybuse = 3; +int joybspeed = 2; + +int joybstrafeleft = -1; +int joybstraferight = -1; + +int joybjump = -1; + +int joybprevweapon = -1; +int joybnextweapon = -1; + +int joybmenu = -1; +int joybautomap = -1; + +// Control whether if a mouse button is double clicked, it acts like +// "use" has been pressed + +int dclick_use = 1; + +// +// Bind all of the common controls used by Doom and all other games. +// + +void M_BindBaseControls(void) +{ + M_BindIntVariable("key_right", &key_right); + M_BindIntVariable("key_left", &key_left); + M_BindIntVariable("key_up", &key_up); + M_BindIntVariable("key_down", &key_down); + M_BindIntVariable("key_strafeleft", &key_strafeleft); + M_BindIntVariable("key_straferight", &key_straferight); + M_BindIntVariable("key_fire", &key_fire); + M_BindIntVariable("key_use", &key_use); + M_BindIntVariable("key_strafe", &key_strafe); + M_BindIntVariable("key_speed", &key_speed); + + M_BindIntVariable("mouseb_fire", &mousebfire); + M_BindIntVariable("mouseb_strafe", &mousebstrafe); + M_BindIntVariable("mouseb_forward", &mousebforward); + + M_BindIntVariable("joyb_fire", &joybfire); + M_BindIntVariable("joyb_strafe", &joybstrafe); + M_BindIntVariable("joyb_use", &joybuse); + M_BindIntVariable("joyb_speed", &joybspeed); + + M_BindIntVariable("joyb_menu_activate", &joybmenu); + M_BindIntVariable("joyb_toggle_automap", &joybautomap); + + // Extra controls that are not in the Vanilla versions: + + M_BindIntVariable("joyb_strafeleft", &joybstrafeleft); + M_BindIntVariable("joyb_straferight", &joybstraferight); + M_BindIntVariable("mouseb_strafeleft", &mousebstrafeleft); + M_BindIntVariable("mouseb_straferight", &mousebstraferight); + M_BindIntVariable("mouseb_use", &mousebuse); + M_BindIntVariable("mouseb_backward", &mousebbackward); + M_BindIntVariable("dclick_use", &dclick_use); + M_BindIntVariable("key_pause", &key_pause); + M_BindIntVariable("key_message_refresh", &key_message_refresh); +} + +void M_BindHereticControls(void) +{ + M_BindIntVariable("key_flyup", &key_flyup); + M_BindIntVariable("key_flydown", &key_flydown); + M_BindIntVariable("key_flycenter", &key_flycenter); + + M_BindIntVariable("key_lookup", &key_lookup); + M_BindIntVariable("key_lookdown", &key_lookdown); + M_BindIntVariable("key_lookcenter", &key_lookcenter); + + M_BindIntVariable("key_invleft", &key_invleft); + M_BindIntVariable("key_invright", &key_invright); + M_BindIntVariable("key_useartifact", &key_useartifact); +} + +void M_BindHexenControls(void) +{ + M_BindIntVariable("key_jump", &key_jump); + M_BindIntVariable("mouseb_jump", &mousebjump); + M_BindIntVariable("joyb_jump", &joybjump); + + M_BindIntVariable("key_arti_all", &key_arti_all); + M_BindIntVariable("key_arti_health", &key_arti_health); + M_BindIntVariable("key_arti_poisonbag", &key_arti_poisonbag); + M_BindIntVariable("key_arti_blastradius", &key_arti_blastradius); + M_BindIntVariable("key_arti_teleport", &key_arti_teleport); + M_BindIntVariable("key_arti_teleportother", &key_arti_teleportother); + M_BindIntVariable("key_arti_egg", &key_arti_egg); + M_BindIntVariable("key_arti_invulnerability", &key_arti_invulnerability); +} + +void M_BindStrifeControls(void) +{ + // These are shared with all games, but have different defaults: + key_message_refresh = '/'; + + // These keys are shared with Heretic/Hexen but have different defaults: + key_jump = 'a'; + key_lookup = KEY_PGUP; + key_lookdown = KEY_PGDN; + key_invleft = KEY_INS; + key_invright = KEY_DEL; + + M_BindIntVariable("key_jump", &key_jump); + M_BindIntVariable("key_lookUp", &key_lookup); + M_BindIntVariable("key_lookDown", &key_lookdown); + M_BindIntVariable("key_invLeft", &key_invleft); + M_BindIntVariable("key_invRight", &key_invright); + + // Custom Strife-only Keys: + M_BindIntVariable("key_useHealth", &key_usehealth); + M_BindIntVariable("key_invquery", &key_invquery); + M_BindIntVariable("key_mission", &key_mission); + M_BindIntVariable("key_invPop", &key_invpop); + M_BindIntVariable("key_invKey", &key_invkey); + M_BindIntVariable("key_invHome", &key_invhome); + M_BindIntVariable("key_invEnd", &key_invend); + M_BindIntVariable("key_invUse", &key_invuse); + M_BindIntVariable("key_invDrop", &key_invdrop); + + // Strife also supports jump on mouse and joystick, and in the exact same + // manner as Hexen! + M_BindIntVariable("mouseb_jump", &mousebjump); + M_BindIntVariable("joyb_jump", &joybjump); +} + +void M_BindWeaponControls(void) +{ + M_BindIntVariable("key_weapon1", &key_weapon1); + M_BindIntVariable("key_weapon2", &key_weapon2); + M_BindIntVariable("key_weapon3", &key_weapon3); + M_BindIntVariable("key_weapon4", &key_weapon4); + M_BindIntVariable("key_weapon5", &key_weapon5); + M_BindIntVariable("key_weapon6", &key_weapon6); + M_BindIntVariable("key_weapon7", &key_weapon7); + M_BindIntVariable("key_weapon8", &key_weapon8); + + M_BindIntVariable("key_prevweapon", &key_prevweapon); + M_BindIntVariable("key_nextweapon", &key_nextweapon); + + M_BindIntVariable("joyb_prevweapon", &joybprevweapon); + M_BindIntVariable("joyb_nextweapon", &joybnextweapon); + + M_BindIntVariable("mouseb_prevweapon", &mousebprevweapon); + M_BindIntVariable("mouseb_nextweapon", &mousebnextweapon); +} + +void M_BindMapControls(void) +{ + M_BindIntVariable("key_map_north", &key_map_north); + M_BindIntVariable("key_map_south", &key_map_south); + M_BindIntVariable("key_map_east", &key_map_east); + M_BindIntVariable("key_map_west", &key_map_west); + M_BindIntVariable("key_map_zoomin", &key_map_zoomin); + M_BindIntVariable("key_map_zoomout", &key_map_zoomout); + M_BindIntVariable("key_map_toggle", &key_map_toggle); + M_BindIntVariable("key_map_maxzoom", &key_map_maxzoom); + M_BindIntVariable("key_map_follow", &key_map_follow); + M_BindIntVariable("key_map_grid", &key_map_grid); + M_BindIntVariable("key_map_mark", &key_map_mark); + M_BindIntVariable("key_map_clearmark", &key_map_clearmark); +} + +void M_BindMenuControls(void) +{ + M_BindIntVariable("key_menu_activate", &key_menu_activate); + M_BindIntVariable("key_menu_up", &key_menu_up); + M_BindIntVariable("key_menu_down", &key_menu_down); + M_BindIntVariable("key_menu_left", &key_menu_left); + M_BindIntVariable("key_menu_right", &key_menu_right); + M_BindIntVariable("key_menu_back", &key_menu_back); + M_BindIntVariable("key_menu_forward", &key_menu_forward); + M_BindIntVariable("key_menu_confirm", &key_menu_confirm); + M_BindIntVariable("key_menu_abort", &key_menu_abort); + + M_BindIntVariable("key_menu_help", &key_menu_help); + M_BindIntVariable("key_menu_save", &key_menu_save); + M_BindIntVariable("key_menu_load", &key_menu_load); + M_BindIntVariable("key_menu_volume", &key_menu_volume); + M_BindIntVariable("key_menu_detail", &key_menu_detail); + M_BindIntVariable("key_menu_qsave", &key_menu_qsave); + M_BindIntVariable("key_menu_endgame", &key_menu_endgame); + M_BindIntVariable("key_menu_messages", &key_menu_messages); + M_BindIntVariable("key_menu_qload", &key_menu_qload); + M_BindIntVariable("key_menu_quit", &key_menu_quit); + M_BindIntVariable("key_menu_gamma", &key_menu_gamma); + + M_BindIntVariable("key_menu_incscreen", &key_menu_incscreen); + M_BindIntVariable("key_menu_decscreen", &key_menu_decscreen); + M_BindIntVariable("key_menu_screenshot",&key_menu_screenshot); + M_BindIntVariable("key_demo_quit", &key_demo_quit); + M_BindIntVariable("key_spy", &key_spy); +} + +void M_BindChatControls(unsigned int num_players) +{ + char name[32]; // haleyjd: 20 not large enough - Thank you, come again! + unsigned int i; // haleyjd: signedness conflict + + M_BindIntVariable("key_multi_msg", &key_multi_msg); + + for (i=0; i +#include + +#include "m_fixed.h" +#include "stdlib.h" + + + + +// Fixme. __USE_C_FIXED__ or something. + +fixed_t +FixedMul +( fixed_t a, + fixed_t b ) +{ + return ((int64_t) a * (int64_t) b) >> FRACBITS; +} + + + +// +// FixedDiv, C version. +// + +fixed_t FixedDiv(fixed_t a, fixed_t b) +{ + if ((abs(a) >> 14) >= abs(b)) + { + return (a^b) < 0 ? INT_MIN : INT_MAX; + } + else + { + int64_t result; + + result = ((int64_t) a << FRACBITS) / b; + + return (fixed_t) result; + } +} + diff --git a/client/src/m_fixed.h b/client/src/m_fixed.h new file mode 100644 index 0000000..733b290 --- /dev/null +++ b/client/src/m_fixed.h @@ -0,0 +1,39 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Fixed point arithemtics, implementation. +// + + +#ifndef __M_FIXED__ +#define __M_FIXED__ + + + + +// +// Fixed point, 32bit as 16.16. +// +#define FRACBITS 16 +#define FRACUNIT (1< +#include +#include + +#include "d_englsh.h" +#include "d_loop.h" +#include "d_main.h" +#include "d_mode.h" +#include "d_player.h" +#include "doomdef.h" +#include "doomkeys.h" +#include "doomstat.h" +#include "dstrings.h" +#include "g_game.h" +#include "hu_stuff.h" +#include "i_input.h" +#include "i_swap.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" +#include "m_argv.h" +#include "m_controls.h" +#include "m_menu.h" +#include "m_misc.h" +#include "p_saveg.h" +#include "r_main.h" +#include "s_sound.h" +// Data. +#include "sounds.h" +#include "v_patch.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +extern patch_t *hu_font[HU_FONTSIZE]; +extern boolean message_dontfuckwithme; + +extern boolean chat_on; // in heads-up code + +// +// defaulted values +// +int mouseSensitivity = 5; + +// Show messages has default, 0 = off, 1 = on +int showMessages = 1; + +// Blocky mode, has default, 0 = high, 1 = normal +int detailLevel = 0; +int screenblocks = 9; + +// temp for screenblocks (0-9) +int screenSize; + +// -1 = no quicksave slot picked! +int quickSaveSlot; + +// 1 = message to be printed +int messageToPrint; +// ...and here is the message string! +char *messageString; + +// message x & y +int messx; +int messy; +int messageLastMenuActive; + +// timed message = no input from user +boolean messageNeedsInput; + +void (*messageRoutine)(int response); + +char gammamsg[5][26] = {GAMMALVL0, GAMMALVL1, GAMMALVL2, GAMMALVL3, GAMMALVL4}; + +// we are going to be entering a savegame string +int saveStringEnter; +int saveSlot; // which slot to save in +int saveCharIndex; // which char we're editing +static boolean joypadSave = false; // was the save action initiated by joypad? +// old save description before edit +char saveOldString[SAVESTRINGSIZE]; + +boolean inhelpscreens; +boolean menuactive; + +#define SKULLXOFF -32 +#define LINEHEIGHT 16 + +extern boolean sendpause; +char savegamestrings[10][SAVESTRINGSIZE]; + +char endstring[160]; + +static boolean opldev; + +// +// MENU TYPEDEFS +// +typedef struct { + // 0 = no cursor here, 1 = ok, 2 = arrows ok + short status; + + char name[10]; + + // choice = menu item #. + // if status = 2, + // choice=0:leftarrow,1:rightarrow + void (*routine)(int choice); + + // hotkey in menu + char alphaKey; +} menuitem_t; + +typedef struct menu_s { + short numitems; // # of menu items + struct menu_s *prevMenu; // previous menu + menuitem_t *menuitems; // menu items + void (*routine)(); // draw routine + short x; + short y; // x,y of menu + short lastOn; // last item user was on in menu +} menu_t; + +short itemOn; // menu item skull is on +short skullAnimCounter; // skull animation counter +short whichSkull; // which skull to draw + +// graphic name of skulls +// warning: initializer-string for array of chars is too long +char *skullName[2] = {"M_SKULL1", "M_SKULL2"}; + +// current menudef +menu_t *currentMenu; + +// +// PROTOTYPES +// +void M_NewGame(int choice); +void M_Episode(int choice); +void M_ChooseSkill(int choice); +void M_LoadGame(int choice); +void M_SaveGame(int choice); +void M_Options(int choice); +void M_EndGame(int choice); +void M_ReadThis(int choice); +void M_ReadThis2(int choice); +void M_QuitDOOM(int choice); + +void M_ChangeMessages(int choice); +void M_ChangeSensitivity(int choice); +void M_SfxVol(int choice); +void M_MusicVol(int choice); +void M_ChangeDetail(int choice); +void M_SizeDisplay(int choice); +void M_StartGame(int choice); +void M_Sound(int choice); + +void M_FinishReadThis(int choice); +void M_LoadSelect(int choice); +void M_SaveSelect(int choice); +void M_ReadSaveStrings(void); +void M_QuickSave(void); +void M_QuickLoad(void); + +void M_DrawMainMenu(void); +void M_DrawReadThis1(void); +void M_DrawReadThis2(void); +void M_DrawNewGame(void); +void M_DrawEpisode(void); +void M_DrawOptions(void); +void M_DrawSound(void); +void M_DrawLoad(void); +void M_DrawSave(void); + +void M_DrawSaveLoadBorder(int x, int y); +void M_SetupNextMenu(menu_t *menudef); +void M_DrawThermo(int x, int y, int thermWidth, int thermDot); +void M_DrawEmptyCell(menu_t *menu, int item); +void M_DrawSelCell(menu_t *menu, int item); +void M_WriteText(int x, int y, char *string); +int M_StringWidth(char *string); +int M_StringHeight(char *string); +void M_StartMessage(char *string, void *routine, boolean input); +void M_StopMessage(void); +void M_ClearMenus(void); + +// +// DOOM MENU +// +enum { + newgame = 0, + options, + loadgame, + savegame, + readthis, + quitdoom, + main_end +} main_e; + +menuitem_t MainMenu[] = {{1, "M_NGAME", M_NewGame, 'n'}, + {1, "M_OPTION", M_Options, 'o'}, + {1, "M_LOADG", M_LoadGame, 'l'}, + {1, "M_SAVEG", M_SaveGame, 's'}, + // Another hickup with Special edition. + {1, "M_RDTHIS", M_ReadThis, 'r'}, + {1, "M_QUITG", M_QuitDOOM, 'q'}}; + +menu_t MainDef = {main_end, NULL, MainMenu, M_DrawMainMenu, 97, 64, 0}; + +// +// EPISODE SELECT +// +enum { ep1, ep2, ep3, ep4, ep_end } episodes_e; + +menuitem_t EpisodeMenu[] = {{1, "M_EPI1", M_Episode, 'k'}, + {1, "M_EPI2", M_Episode, 't'}, + {1, "M_EPI3", M_Episode, 'i'}, + {1, "M_EPI4", M_Episode, 't'}}; + +menu_t EpiDef = { + ep_end, // # of menu items + &MainDef, // previous menu + EpisodeMenu, // menuitem_t -> + M_DrawEpisode, // drawing routine -> + 48, + 63, // x,y + ep1 // lastOn +}; + +// +// NEW GAME +// +enum { killthings, toorough, hurtme, violence, nightmare, newg_end } newgame_e; + +menuitem_t NewGameMenu[] = {{1, "M_JKILL", M_ChooseSkill, 'i'}, + {1, "M_ROUGH", M_ChooseSkill, 'h'}, + {1, "M_HURT", M_ChooseSkill, 'h'}, + {1, "M_ULTRA", M_ChooseSkill, 'u'}, + {1, "M_NMARE", M_ChooseSkill, 'n'}}; + +menu_t NewDef = { + newg_end, // # of menu items + &EpiDef, // previous menu + NewGameMenu, // menuitem_t -> + M_DrawNewGame, // drawing routine -> + 48, + 63, // x,y + hurtme // lastOn +}; + +// +// OPTIONS MENU +// +enum { + endgame, + messages, + detail, + scrnsize, + option_empty1, + mousesens, + option_empty2, + soundvol, + opt_end +} options_e; + +menuitem_t OptionsMenu[] = {{1, "M_ENDGAM", M_EndGame, 'e'}, + {1, "M_MESSG", M_ChangeMessages, 'm'}, + {1, "M_DETAIL", M_ChangeDetail, 'g'}, + {2, "M_SCRNSZ", M_SizeDisplay, 's'}, + {-1, "", 0, '\0'}, + {2, "M_MSENS", M_ChangeSensitivity, 'm'}, + {-1, "", 0, '\0'}, + {1, "M_SVOL", M_Sound, 's'}}; + +menu_t OptionsDef = {opt_end, &MainDef, OptionsMenu, M_DrawOptions, 60, 37, 0}; + +// +// Read This! MENU 1 & 2 +// +enum { rdthsempty1, read1_end } read_e; + +menuitem_t ReadMenu1[] = {{1, "", M_ReadThis2, 0}}; + +menu_t ReadDef1 = {read1_end, &MainDef, ReadMenu1, M_DrawReadThis1, + 280, 185, 0}; + +enum { rdthsempty2, read2_end } read_e2; + +menuitem_t ReadMenu2[] = {{1, "", M_FinishReadThis, 0}}; + +menu_t ReadDef2 = {read2_end, &ReadDef1, ReadMenu2, M_DrawReadThis2, + 330, 175, 0}; + +// +// SOUND VOLUME MENU +// +enum { sfx_vol, sfx_empty1, music_vol, sfx_empty2, sound_end } sound_e; + +menuitem_t SoundMenu[] = {{2, "M_SFXVOL", M_SfxVol, 's'}, + {-1, "", 0, '\0'}, + {2, "M_MUSVOL", M_MusicVol, 'm'}, + {-1, "", 0, '\0'}}; + +menu_t SoundDef = {sound_end, &OptionsDef, SoundMenu, M_DrawSound, 80, 64, 0}; + +// +// LOAD GAME MENU +// +enum { load1, load2, load3, load4, load5, load6, load_end } load_e; + +menuitem_t LoadMenu[] = { + {1, "", M_LoadSelect, '1'}, {1, "", M_LoadSelect, '2'}, + {1, "", M_LoadSelect, '3'}, {1, "", M_LoadSelect, '4'}, + {1, "", M_LoadSelect, '5'}, {1, "", M_LoadSelect, '6'}}; + +menu_t LoadDef = {load_end, &MainDef, LoadMenu, M_DrawLoad, 80, 54, 0}; + +// +// SAVE GAME MENU +// +menuitem_t SaveMenu[] = { + {1, "", M_SaveSelect, '1'}, {1, "", M_SaveSelect, '2'}, + {1, "", M_SaveSelect, '3'}, {1, "", M_SaveSelect, '4'}, + {1, "", M_SaveSelect, '5'}, {1, "", M_SaveSelect, '6'}}; + +menu_t SaveDef = {load_end, &MainDef, SaveMenu, M_DrawSave, 80, 54, 0}; + +// +// M_ReadSaveStrings +// read the strings from the savegame files +// +void M_ReadSaveStrings(void) { + FILE *handle; + int i; + char name[256]; + + for (i = 0; i < load_end; i++) { + M_StringCopy(name, P_SaveGameFile(i), sizeof(name)); + + handle = fopen(name, "rb"); + if (handle == NULL) { + M_StringCopy(savegamestrings[i], EMPTYSTRING, SAVESTRINGSIZE); + LoadMenu[i].status = 0; + continue; + } + size_t res = fread(&savegamestrings[i], 1, SAVESTRINGSIZE, handle); + (void) res; // explicitly ignore result + fclose(handle); + LoadMenu[i].status = 1; + } +} + +// +// M_LoadGame & Cie. +// +void M_DrawLoad(void) { + int i; + + V_DrawPatchDirect(72, 28, W_CacheLumpName(("M_LOADG"), PU_CACHE)); + + for (i = 0; i < load_end; i++) { + M_DrawSaveLoadBorder(LoadDef.x, LoadDef.y + LINEHEIGHT * i); + M_WriteText(LoadDef.x, LoadDef.y + LINEHEIGHT * i, savegamestrings[i]); + } +} + +// +// Draw border for the savegame description +// +void M_DrawSaveLoadBorder(int x, int y) { + int i; + + V_DrawPatchDirect(x - 8, y + 7, W_CacheLumpName(("M_LSLEFT"), PU_CACHE)); + + for (i = 0; i < 24; i++) { + V_DrawPatchDirect(x, y + 7, W_CacheLumpName(("M_LSCNTR"), PU_CACHE)); + x += 8; + } + + V_DrawPatchDirect(x, y + 7, W_CacheLumpName(("M_LSRGHT"), PU_CACHE)); +} + +// +// User wants to load this game +// +void M_LoadSelect(int choice) { + char name[256]; + + M_StringCopy(name, P_SaveGameFile(choice), sizeof(name)); + + G_LoadGame(name); + M_ClearMenus(); +} + +// +// Selected from DOOM menu +// +void M_LoadGame(int choice) { + if (netgame) { + M_StartMessage((LOADNET), NULL, false); + return; + } + + M_SetupNextMenu(&LoadDef); + M_ReadSaveStrings(); +} + +// +// M_SaveGame & Cie. +// +void M_DrawSave(void) { + int i; + + V_DrawPatchDirect(72, 28, W_CacheLumpName(("M_SAVEG"), PU_CACHE)); + for (i = 0; i < load_end; i++) { + M_DrawSaveLoadBorder(LoadDef.x, LoadDef.y + LINEHEIGHT * i); + M_WriteText(LoadDef.x, LoadDef.y + LINEHEIGHT * i, savegamestrings[i]); + } + + if (saveStringEnter) { + i = M_StringWidth(savegamestrings[saveSlot]); + M_WriteText(LoadDef.x + i, LoadDef.y + LINEHEIGHT * saveSlot, "_"); + } +} + +// +// M_Responder calls this when user is finished +// +void M_DoSave(int slot) { + G_SaveGame(slot, savegamestrings[slot]); + M_ClearMenus(); + + // PICK QUICKSAVE SLOT YET? + if (quickSaveSlot == -2) + quickSaveSlot = slot; +} + +// +// Generate a default save slot name when the user saves to +// an empty slot via the joypad. +// +static void SetDefaultSaveName(int slot) { + M_snprintf(savegamestrings[itemOn], SAVESTRINGSIZE - 1, "JOYSTICK SLOT %i", + itemOn + 1); + joypadSave = false; +} + +// +// User wants to save. Start string input for M_Responder +// +void M_SaveSelect(int choice) { + int x, y; + + // we are going to be intercepting all chars + saveStringEnter = 1; + + // We need to turn on text input: + x = LoadDef.x - 11; + y = LoadDef.y + choice * LINEHEIGHT - 4; + I_StartTextInput(x, y, x + 8 + 24 * 8 + 8, y + LINEHEIGHT - 2); + + saveSlot = choice; + M_StringCopy(saveOldString, savegamestrings[choice], SAVESTRINGSIZE); + if (!strcmp(savegamestrings[choice], EMPTYSTRING)) { + savegamestrings[choice][0] = 0; + + if (joypadSave) { + SetDefaultSaveName(choice); + } + } + saveCharIndex = strlen(savegamestrings[choice]); +} + +// +// Selected from DOOM menu +// +void M_SaveGame(int choice) { + if (!usergame) { + M_StartMessage((SAVEDEAD), NULL, false); + return; + } + + if (gamestate != GS_LEVEL) + return; + + M_SetupNextMenu(&SaveDef); + M_ReadSaveStrings(); +} + +// +// M_QuickSave +// +char tempstring[80]; + +void M_QuickSaveResponse(int key) { + if (key == key_menu_confirm) { + M_DoSave(quickSaveSlot); + S_StartSound(NULL, sfx_swtchx); + } +} + +void M_QuickSave(void) { + if (!usergame) { + S_StartSound(NULL, sfx_oof); + return; + } + + if (gamestate != GS_LEVEL) + return; + + if (quickSaveSlot < 0) { + M_StartControlPanel(); + M_ReadSaveStrings(); + M_SetupNextMenu(&SaveDef); + quickSaveSlot = -2; // means to pick a slot now + return; + } + M_snprintf(tempstring, 80, QSPROMPT, savegamestrings[quickSaveSlot]); + M_StartMessage(tempstring, M_QuickSaveResponse, true); +} + +// +// M_QuickLoad +// +void M_QuickLoadResponse(int key) { + if (key == key_menu_confirm) { + M_LoadSelect(quickSaveSlot); + S_StartSound(NULL, sfx_swtchx); + } +} + +void M_QuickLoad(void) { + if (netgame) { + M_StartMessage((QLOADNET), NULL, false); + return; + } + + if (quickSaveSlot < 0) { + M_StartMessage((QSAVESPOT), NULL, false); + return; + } + M_snprintf(tempstring, 80, QLPROMPT, savegamestrings[quickSaveSlot]); + M_StartMessage(tempstring, M_QuickLoadResponse, true); +} + +// +// Read This Menus +// Had a "quick hack to fix romero bug" +// +void M_DrawReadThis1(void) { + inhelpscreens = true; + + V_DrawPatchDirect(0, 0, W_CacheLumpName(("HELP2"), PU_CACHE)); +} + +// +// Read This Menus - optional second page. +// +void M_DrawReadThis2(void) { + inhelpscreens = true; + + // We only ever draw the second page if this is + // gameversion == exe_doom_1_9 and gamemode == registered + + V_DrawPatchDirect(0, 0, W_CacheLumpName(("HELP1"), PU_CACHE)); +} + +void M_DrawReadThisCommercial(void) { + inhelpscreens = true; + + V_DrawPatchDirect(0, 0, W_CacheLumpName(("HELP"), PU_CACHE)); +} + +// +// Change Sfx & Music volumes +// +void M_DrawSound(void) { + V_DrawPatchDirect(60, 38, W_CacheLumpName(("M_SVOL"), PU_CACHE)); + + M_DrawThermo(SoundDef.x, SoundDef.y + LINEHEIGHT * (sfx_vol + 1), 16, + sfxVolume); + + M_DrawThermo(SoundDef.x, SoundDef.y + LINEHEIGHT * (music_vol + 1), 16, + musicVolume); +} + +void M_Sound(int choice) { M_SetupNextMenu(&SoundDef); } + +void M_SfxVol(int choice) { + switch (choice) { + case 0: + if (sfxVolume) + sfxVolume--; + break; + case 1: + if (sfxVolume < 15) + sfxVolume++; + break; + } + + S_SetSfxVolume(sfxVolume * 8); +} + +void M_MusicVol(int choice) { + switch (choice) { + case 0: + if (musicVolume) + musicVolume--; + break; + case 1: + if (musicVolume < 15) + musicVolume++; + break; + } + + S_SetMusicVolume(musicVolume * 8); +} + +// +// M_DrawMainMenu +// +void M_DrawMainMenu(void) { + V_DrawPatchDirect(94, 2, W_CacheLumpName(("M_DOOM"), PU_CACHE)); +} + +// +// M_NewGame +// +void M_DrawNewGame(void) { + V_DrawPatchDirect(96, 14, W_CacheLumpName(("M_NEWG"), PU_CACHE)); + V_DrawPatchDirect(54, 38, W_CacheLumpName(("M_SKILL"), PU_CACHE)); +} + +void M_NewGame(int choice) { + if (netgame && !demoplayback) { + M_StartMessage((NEWGAME), NULL, false); + return; + } + + // Chex Quest disabled the episode select screen, as did Doom II. + + if (gamemode == commercial || gameversion == exe_chex) + M_SetupNextMenu(&NewDef); + else + M_SetupNextMenu(&EpiDef); +} + +// +// M_Episode +// +int epi; + +void M_DrawEpisode(void) { + V_DrawPatchDirect(54, 38, W_CacheLumpName(("M_EPISOD"), PU_CACHE)); +} + +void M_VerifyNightmare(int key) { + if (key != key_menu_confirm) + return; + + G_DeferedInitNew(nightmare, epi + 1, 1); + M_ClearMenus(); +} + +void M_ChooseSkill(int choice) { + if (choice == nightmare) { + M_StartMessage((NIGHTMARE), M_VerifyNightmare, true); + return; + } + + G_DeferedInitNew(choice, epi + 1, 1); + M_ClearMenus(); +} + +void M_Episode(int choice) { + if ((gamemode == shareware) && choice) { + M_StartMessage((SWSTRING), NULL, false); + M_SetupNextMenu(&ReadDef1); + return; + } + + epi = choice; + M_SetupNextMenu(&NewDef); +} + +// +// M_Options +// +static char *detailNames[2] = {"M_GDHIGH", "M_GDLOW"}; +static char *msgNames[2] = {"M_MSGOFF", "M_MSGON"}; + +void M_DrawOptions(void) { + V_DrawPatchDirect(108, 15, W_CacheLumpName(("M_OPTTTL"), PU_CACHE)); + + V_DrawPatchDirect(OptionsDef.x + 175, OptionsDef.y + LINEHEIGHT * detail, + W_CacheLumpName((detailNames[detailLevel]), PU_CACHE)); + + V_DrawPatchDirect(OptionsDef.x + 120, OptionsDef.y + LINEHEIGHT * messages, + W_CacheLumpName((msgNames[showMessages]), PU_CACHE)); + + M_DrawThermo(OptionsDef.x, OptionsDef.y + LINEHEIGHT * (mousesens + 1), 10, + mouseSensitivity); + + M_DrawThermo(OptionsDef.x, OptionsDef.y + LINEHEIGHT * (scrnsize + 1), 9, + screenSize); +} + +void M_Options(int choice) { M_SetupNextMenu(&OptionsDef); } + +// +// Toggle messages on/off +// +void M_ChangeMessages(int choice) { + // warning: unused parameter `int choice' + choice = 0; + showMessages = 1 - showMessages; + + if (!showMessages) + players[consoleplayer].message = (MSGOFF); + else + players[consoleplayer].message = (MSGON); + + message_dontfuckwithme = true; +} + +// +// M_EndGame +// +void M_EndGameResponse(int key) { + if (key != key_menu_confirm) + return; + + currentMenu->lastOn = itemOn; + M_ClearMenus(); + D_StartTitle(); +} + +void M_EndGame(int choice) { + choice = 0; + if (!usergame) { + S_StartSound(NULL, sfx_oof); + return; + } + + if (netgame) { + M_StartMessage((NETEND), NULL, false); + return; + } + + M_StartMessage((ENDGAME), M_EndGameResponse, true); +} + +// +// M_ReadThis +// +void M_ReadThis(int choice) { + choice = 0; + M_SetupNextMenu(&ReadDef1); +} + +void M_ReadThis2(int choice) { + choice = 0; + M_SetupNextMenu(&ReadDef2); +} + +void M_FinishReadThis(int choice) { + choice = 0; + M_SetupNextMenu(&MainDef); +} + +// +// M_QuitDOOM +// +int quitsounds[8] = {sfx_pldeth, sfx_dmpain, sfx_popain, sfx_slop, + sfx_telept, sfx_posit1, sfx_posit3, sfx_sgtatk}; + +int quitsounds2[8] = {sfx_vilact, sfx_getpow, sfx_boscub, sfx_slop, + sfx_skeswg, sfx_kntdth, sfx_bspact, sfx_sgtatk}; + +void M_QuitResponse(int key) { + if (key != key_menu_confirm) + return; + if (!netgame) { + if (gamemode == commercial) + S_StartSound(NULL, quitsounds2[(gametic >> 2) & 7]); + else + S_StartSound(NULL, quitsounds[(gametic >> 2) & 7]); + I_WaitVBL(105); + } + I_Quit(); +} + +static char *M_SelectEndMessage(void) { + char **endmsg; + + if (logical_gamemission == doom) { + // Doom 1 + + endmsg = doom1_endmsg; + } else { + // Doom 2 + + endmsg = doom2_endmsg; + } + + return endmsg[gametic % NUM_QUITMESSAGES]; +} + +void M_QuitDOOM(int choice) { + M_snprintf(endstring, sizeof(endstring), "%s\n\n" DOSY, + (M_SelectEndMessage())); + + M_StartMessage(endstring, M_QuitResponse, true); +} + +void M_ChangeSensitivity(int choice) { + switch (choice) { + case 0: + if (mouseSensitivity) + mouseSensitivity--; + break; + case 1: + if (mouseSensitivity < 9) + mouseSensitivity++; + break; + } +} + +void M_ChangeDetail(int choice) { + choice = 0; + detailLevel = 1 - detailLevel; + + R_SetViewSize(screenblocks, detailLevel); + + if (!detailLevel) + players[consoleplayer].message = (DETAILHI); + else + players[consoleplayer].message = (DETAILLO); +} + +void M_SizeDisplay(int choice) { + switch (choice) { + case 0: + if (screenSize > 0) { + screenblocks--; + screenSize--; + } + break; + case 1: + if (screenSize < 8) { + screenblocks++; + screenSize++; + } + break; + } + + R_SetViewSize(screenblocks, detailLevel); +} + +// +// Menu Functions +// +void M_DrawThermo(int x, int y, int thermWidth, int thermDot) { + int xx; + int i; + + xx = x; + V_DrawPatchDirect(xx, y, W_CacheLumpName(("M_THERML"), PU_CACHE)); + xx += 8; + for (i = 0; i < thermWidth; i++) { + V_DrawPatchDirect(xx, y, W_CacheLumpName(("M_THERMM"), PU_CACHE)); + xx += 8; + } + V_DrawPatchDirect(xx, y, W_CacheLumpName(("M_THERMR"), PU_CACHE)); + + V_DrawPatchDirect((x + 8) + thermDot * 8, y, + W_CacheLumpName(("M_THERMO"), PU_CACHE)); +} + +void M_DrawEmptyCell(menu_t *menu, int item) { + V_DrawPatchDirect(menu->x - 10, menu->y + item * LINEHEIGHT - 1, + W_CacheLumpName(("M_CELL1"), PU_CACHE)); +} + +void M_DrawSelCell(menu_t *menu, int item) { + V_DrawPatchDirect(menu->x - 10, menu->y + item * LINEHEIGHT - 1, + W_CacheLumpName(("M_CELL2"), PU_CACHE)); +} + +void M_StartMessage(char *string, void *routine, boolean input) { + messageLastMenuActive = menuactive; + messageToPrint = 1; + messageString = string; + messageRoutine = routine; + messageNeedsInput = input; + menuactive = true; + return; +} + +void M_StopMessage(void) { + menuactive = messageLastMenuActive; + messageToPrint = 0; +} + +// +// Find string width from hu_font chars +// +int M_StringWidth(char *string) { + size_t i; + int w = 0; + int c; + + for (i = 0; i < strlen(string); i++) { + c = toupper(string[i]) - HU_FONTSTART; + if (c < 0 || c >= HU_FONTSIZE) + w += 4; + else + w += SHORT(hu_font[c]->width); + } + + return w; +} + +// +// Find string height from hu_font chars +// +int M_StringHeight(char *string) { + size_t i; + int h; + int height = SHORT(hu_font[0]->height); + + h = height; + for (i = 0; i < strlen(string); i++) + if (string[i] == '\n') + h += height; + + return h; +} + +// +// Write a string using the hu_font +// +void M_WriteText(int x, int y, char *string) { + int w; + char *ch; + int c; + int cx; + int cy; + + ch = string; + cx = x; + cy = y; + + while (1) { + c = *ch++; + if (!c) + break; + if (c == '\n') { + cx = x; + cy += 12; + continue; + } + + c = toupper(c) - HU_FONTSTART; + if (c < 0 || c >= HU_FONTSIZE) { + cx += 4; + continue; + } + + w = SHORT(hu_font[c]->width); + if (cx + w > SCREENWIDTH) + break; + V_DrawPatchDirect(cx, cy, hu_font[c]); + cx += w; + } +} + +// These keys evaluate to a "null" key in Vanilla Doom that allows weird +// jumping in the menus. Preserve this behavior for accuracy. + +static boolean IsNullKey(int key) { + return key == KEY_PAUSE || key == KEY_CAPSLOCK || key == KEY_SCRLCK || + key == KEY_NUMLOCK; +} + +// +// CONTROL PANEL +// + +// +// M_Responder +// +boolean M_Responder(event_t *ev) { + int ch; + int key; + int i; + static int mousewait = 0; + static int mousey = 0; + static int lasty = 0; + static int mousex = 0; + static int lastx = 0; + + // In testcontrols mode, none of the function keys should do anything + // - the only key is escape to quit. + + if (testcontrols) { + if (ev->type == ev_quit || + (ev->type == ev_keydown && + (ev->data1 == key_menu_activate || ev->data1 == key_menu_quit))) { + I_Quit(); + return true; + } + + return false; + } + + // "close" button pressed on window? + if (ev->type == ev_quit) { + // First click on close button = bring up quit confirm message. + // Second click on close button = confirm quit + + if (menuactive && messageToPrint && messageRoutine == M_QuitResponse) { + M_QuitResponse(key_menu_confirm); + } else { + S_StartSound(NULL, sfx_swtchn); + M_QuitDOOM(0); + } + + return true; + } + + // key is the key pressed, ch is the actual character typed + + ch = 0; + key = -1; + + if (ev->type == ev_joystick) { + // Simulate key presses from joystick events to interact with the menu. + + if (ev->data3 < 0) { + key = key_menu_up; + joywait = I_GetTime() + 5; + } else if (ev->data3 > 0) { + key = key_menu_down; + joywait = I_GetTime() + 5; + } + + if (ev->data2 < 0) { + key = key_menu_left; + joywait = I_GetTime() + 2; + } else if (ev->data2 > 0) { + key = key_menu_right; + joywait = I_GetTime() + 2; + } + +#define JOY_BUTTON_MAPPED(x) ((x) >= 0) +#define JOY_BUTTON_PRESSED(x) \ + (JOY_BUTTON_MAPPED(x) && (ev->data1 & (1 << (x))) != 0) + + if (JOY_BUTTON_PRESSED(joybfire)) { + // Simulate a 'Y' keypress when Doom show a Y/N dialog with Fire button. + if (messageToPrint && messageNeedsInput) { + key = key_menu_confirm; + } + // Simulate pressing "Enter" when we are supplying a save slot name + else if (saveStringEnter) { + key = KEY_ENTER; + } else { + // if selecting a save slot via joypad, set a flag + if (currentMenu == &SaveDef) { + joypadSave = true; + } + key = key_menu_forward; + } + joywait = I_GetTime() + 5; + } + if (JOY_BUTTON_PRESSED(joybuse)) { + // Simulate a 'N' keypress when Doom show a Y/N dialog with Use button. + if (messageToPrint && messageNeedsInput) { + key = key_menu_abort; + } + // If user was entering a save name, back out + else if (saveStringEnter) { + key = KEY_ESCAPE; + } else { + key = key_menu_back; + } + joywait = I_GetTime() + 5; + } + if (JOY_BUTTON_PRESSED(joybmenu)) { + key = key_menu_activate; + joywait = I_GetTime() + 5; + } + } else { + if (ev->type == ev_mouse && mousewait < I_GetTime()) { + mousey += ev->data3; + if (mousey < lasty - 30) { + key = key_menu_down; + mousewait = I_GetTime() + 5; + mousey = lasty -= 30; + } else if (mousey > lasty + 30) { + key = key_menu_up; + mousewait = I_GetTime() + 5; + mousey = lasty += 30; + } + + mousex += ev->data2; + if (mousex < lastx - 30) { + key = key_menu_left; + mousewait = I_GetTime() + 5; + mousex = lastx -= 30; + } else if (mousex > lastx + 30) { + key = key_menu_right; + mousewait = I_GetTime() + 5; + mousex = lastx += 30; + } + + if (ev->data1 & 1) { + key = key_menu_forward; + mousewait = I_GetTime() + 15; + } + + if (ev->data1 & 2) { + key = key_menu_back; + mousewait = I_GetTime() + 15; + } + } else { + if (ev->type == ev_keydown) { + key = ev->data1; + ch = ev->data2; + } + } + } + + if (key == -1) + return false; + + // Save Game string input + if (saveStringEnter) { + switch (key) { + case KEY_BACKSPACE: + if (saveCharIndex > 0) { + saveCharIndex--; + savegamestrings[saveSlot][saveCharIndex] = 0; + } + break; + + case KEY_ESCAPE: + saveStringEnter = 0; + I_StopTextInput(); + M_StringCopy(savegamestrings[saveSlot], saveOldString, SAVESTRINGSIZE); + break; + + case KEY_ENTER: + saveStringEnter = 0; + I_StopTextInput(); + if (savegamestrings[saveSlot][0]) + M_DoSave(saveSlot); + break; + + default: + // Savegame name entry. This is complicated. + // Vanilla has a bug where the shift key is ignored when entering + // a savegame name. If vanilla_keyboard_mapping is on, we want + // to emulate this bug by using ev->data1. But if it's turned off, + // it implies the user doesn't care about Vanilla emulation: + // instead, use ev->data3 which gives the fully-translated and + // modified key input. + + if (ev->type != ev_keydown) { + break; + } + if (vanilla_keyboard_mapping) { + ch = ev->data1; + } else { + ch = ev->data3; + } + + ch = toupper(ch); + + if (ch != ' ' && + (ch - HU_FONTSTART < 0 || ch - HU_FONTSTART >= HU_FONTSIZE)) { + break; + } + + if (ch >= 32 && ch <= 127 && saveCharIndex < SAVESTRINGSIZE - 1 && + M_StringWidth(savegamestrings[saveSlot]) < (SAVESTRINGSIZE - 2) * 8) { + savegamestrings[saveSlot][saveCharIndex++] = ch; + savegamestrings[saveSlot][saveCharIndex] = 0; + } + break; + } + return true; + } + + // Take care of any messages that need input + if (messageToPrint) { + if (messageNeedsInput) { + if (key != ' ' && key != KEY_ESCAPE && key != key_menu_confirm && + key != key_menu_abort) { + return false; + } + } + + menuactive = messageLastMenuActive; + messageToPrint = 0; + if (messageRoutine) + messageRoutine(key); + + menuactive = false; + S_StartSound(NULL, sfx_swtchx); + return true; + } + + if ((devparm && key == key_menu_help) || + (key != 0 && key == key_menu_screenshot)) { + G_ScreenShot(); + return true; + } + + // F-Keys + if (!menuactive) { + if (key == key_menu_decscreen) // Screen size down + { + if (automapactive || chat_on) + return false; + M_SizeDisplay(0); + S_StartSound(NULL, sfx_stnmov); + return true; + } else if (key == key_menu_incscreen) // Screen size up + { + if (automapactive || chat_on) + return false; + M_SizeDisplay(1); + S_StartSound(NULL, sfx_stnmov); + return true; + } else if (key == key_menu_help) // Help key + { + M_StartControlPanel(); + + if (gameversion >= exe_ultimate) + currentMenu = &ReadDef2; + else + currentMenu = &ReadDef1; + + itemOn = 0; + S_StartSound(NULL, sfx_swtchn); + return true; + } else if (key == key_menu_save) // Save + { + M_StartControlPanel(); + S_StartSound(NULL, sfx_swtchn); + M_SaveGame(0); + return true; + } else if (key == key_menu_load) // Load + { + M_StartControlPanel(); + S_StartSound(NULL, sfx_swtchn); + M_LoadGame(0); + return true; + } else if (key == key_menu_volume) // Sound Volume + { + M_StartControlPanel(); + currentMenu = &SoundDef; + itemOn = sfx_vol; + S_StartSound(NULL, sfx_swtchn); + return true; + } else if (key == key_menu_detail) // Detail toggle + { + M_ChangeDetail(0); + S_StartSound(NULL, sfx_swtchn); + return true; + } else if (key == key_menu_qsave) // Quicksave + { + S_StartSound(NULL, sfx_swtchn); + M_QuickSave(); + return true; + } else if (key == key_menu_endgame) // End game + { + S_StartSound(NULL, sfx_swtchn); + M_EndGame(0); + return true; + } else if (key == key_menu_messages) // Toggle messages + { + M_ChangeMessages(0); + S_StartSound(NULL, sfx_swtchn); + return true; + } else if (key == key_menu_qload) // Quickload + { + S_StartSound(NULL, sfx_swtchn); + M_QuickLoad(); + return true; + } else if (key == key_menu_quit) // Quit DOOM + { + S_StartSound(NULL, sfx_swtchn); + M_QuitDOOM(0); + return true; + } else if (key == key_menu_gamma) // gamma toggle + { + usegamma++; + if (usegamma > 4) + usegamma = 0; + players[consoleplayer].message = (gammamsg[usegamma]); + I_SetPalette(W_CacheLumpName(("PLAYPAL"), PU_CACHE)); + return true; + } + } + + // Pop-up menu? + if (!menuactive) { + if (key == key_menu_activate) { + M_StartControlPanel(); + S_StartSound(NULL, sfx_swtchn); + return true; + } + return false; + } + + // Keys usable within menu + + if (key == key_menu_down) { + // Move down to next item + + do { + if (itemOn + 1 > currentMenu->numitems - 1) + itemOn = 0; + else + itemOn++; + S_StartSound(NULL, sfx_pstop); + } while (currentMenu->menuitems[itemOn].status == -1); + + return true; + } else if (key == key_menu_up) { + // Move back up to previous item + + do { + if (!itemOn) + itemOn = currentMenu->numitems - 1; + else + itemOn--; + S_StartSound(NULL, sfx_pstop); + } while (currentMenu->menuitems[itemOn].status == -1); + + return true; + } else if (key == key_menu_left) { + // Slide slider left + + if (currentMenu->menuitems[itemOn].routine && + currentMenu->menuitems[itemOn].status == 2) { + S_StartSound(NULL, sfx_stnmov); + currentMenu->menuitems[itemOn].routine(0); + } + return true; + } else if (key == key_menu_right) { + // Slide slider right + + if (currentMenu->menuitems[itemOn].routine && + currentMenu->menuitems[itemOn].status == 2) { + S_StartSound(NULL, sfx_stnmov); + currentMenu->menuitems[itemOn].routine(1); + } + return true; + } else if (key == key_menu_forward) { + // Activate menu item + + if (currentMenu->menuitems[itemOn].routine && + currentMenu->menuitems[itemOn].status) { + currentMenu->lastOn = itemOn; + if (currentMenu->menuitems[itemOn].status == 2) { + currentMenu->menuitems[itemOn].routine(1); // right arrow + S_StartSound(NULL, sfx_stnmov); + } else { + currentMenu->menuitems[itemOn].routine(itemOn); + S_StartSound(NULL, sfx_pistol); + } + } + return true; + } else if (key == key_menu_activate) { + // Deactivate menu + + currentMenu->lastOn = itemOn; + M_ClearMenus(); + S_StartSound(NULL, sfx_swtchx); + return true; + } else if (key == key_menu_back) { + // Go back to previous menu + + currentMenu->lastOn = itemOn; + if (currentMenu->prevMenu) { + currentMenu = currentMenu->prevMenu; + itemOn = currentMenu->lastOn; + S_StartSound(NULL, sfx_swtchn); + } + return true; + } + + // Keyboard shortcut? + // Vanilla Doom has a weird behavior where it jumps to the scroll bars + // when the certain keys are pressed, so emulate this. + + else if (ch != 0 || IsNullKey(key)) { + for (i = itemOn + 1; i < currentMenu->numitems; i++) { + if (currentMenu->menuitems[i].alphaKey == ch) { + itemOn = i; + S_StartSound(NULL, sfx_pstop); + return true; + } + } + + for (i = 0; i <= itemOn; i++) { + if (currentMenu->menuitems[i].alphaKey == ch) { + itemOn = i; + S_StartSound(NULL, sfx_pstop); + return true; + } + } + } + + return false; +} + +// +// M_StartControlPanel +// +void M_StartControlPanel(void) { + // intro might call this repeatedly + if (menuactive) + return; + + menuactive = 1; + currentMenu = &MainDef; // JDC + itemOn = currentMenu->lastOn; // JDC +} + +// +// M_Drawer +// Called after the view has been rendered, +// but before it has been blitted. +// +void M_Drawer(void) { + static short x; + static short y; + unsigned int i; + unsigned int max; + char string[80]; + char *name; + int start; + + inhelpscreens = false; + + // Horiz. & Vertically center string and print it. + if (messageToPrint) { + start = 0; + y = SCREENHEIGHT / 2 - M_StringHeight(messageString) / 2; + while (messageString[start] != '\0') { + int foundnewline = 0; + + for (i = 0; i < strlen(messageString + start); i++) { + if (messageString[start + i] == '\n') { + M_StringCopy(string, messageString + start, sizeof(string)); + if (i < sizeof(string)) { + string[i] = '\0'; + } + + foundnewline = 1; + start += i + 1; + break; + } + } + + if (!foundnewline) { + M_StringCopy(string, messageString + start, sizeof(string)); + start += strlen(string); + } + + x = SCREENWIDTH / 2 - M_StringWidth(string) / 2; + M_WriteText(x, y, string); + y += SHORT(hu_font[0]->height); + } + + return; + } + + if (!menuactive) + return; + + if (currentMenu->routine) + currentMenu->routine(); // call Draw routine + + // DRAW MENU + x = currentMenu->x; + y = currentMenu->y; + max = currentMenu->numitems; + + for (i = 0; i < max; i++) { + name = (currentMenu->menuitems[i].name); + + if (name[0]) { + V_DrawPatchDirect(x, y, W_CacheLumpName(name, PU_CACHE)); + } + y += LINEHEIGHT; + } + + // DRAW SKULL + V_DrawPatchDirect(x + SKULLXOFF, currentMenu->y - 5 + itemOn * LINEHEIGHT, + W_CacheLumpName((skullName[whichSkull]), PU_CACHE)); +} + +// +// M_ClearMenus +// +void M_ClearMenus(void) { + menuactive = 0; + // if (!netgame && usergame && paused) + // sendpause = true; +} + +// +// M_SetupNextMenu +// +void M_SetupNextMenu(menu_t *menudef) { + currentMenu = menudef; + itemOn = currentMenu->lastOn; +} + +// +// M_Ticker +// +void M_Ticker(void) { + if (--skullAnimCounter <= 0) { + whichSkull ^= 1; + skullAnimCounter = 8; + } +} + +// +// M_Init +// +void M_Init(void) { + currentMenu = &MainDef; + menuactive = 0; + itemOn = currentMenu->lastOn; + whichSkull = 0; + skullAnimCounter = 10; + screenSize = screenblocks - 3; + messageToPrint = 0; + messageString = NULL; + messageLastMenuActive = menuactive; + quickSaveSlot = -1; + + // Here we could catch other version dependencies, + // like HELP1/2, and four episodes. + + // The same hacks were used in the original Doom EXEs. + + if (gameversion >= exe_ultimate) { + MainMenu[readthis].routine = M_ReadThis2; + ReadDef2.prevMenu = NULL; + } + + if (gameversion >= exe_final && gameversion <= exe_final2) { + ReadDef2.routine = M_DrawReadThisCommercial; + } + + if (gamemode == commercial) { + MainMenu[readthis] = MainMenu[quitdoom]; + MainDef.numitems--; + MainDef.y += 8; + NewDef.prevMenu = &MainDef; + ReadDef1.routine = M_DrawReadThisCommercial; + ReadDef1.x = 330; + ReadDef1.y = 165; + ReadMenu1[rdthsempty1].routine = M_FinishReadThis; + } + + // Versions of doom.exe before the Ultimate Doom release only had + // three episodes; if we're emulating one of those then don't try + // to show episode four. If we are, then do show episode four + // (should crash if missing). + if (gameversion < exe_ultimate) { + EpiDef.numitems--; + } + // chex.exe shows only one episode. + else if (gameversion == exe_chex) { + EpiDef.numitems = 1; + } + + opldev = M_CheckParm("-opldev") > 0; +} diff --git a/client/src/m_menu.h b/client/src/m_menu.h new file mode 100644 index 0000000..af221ff --- /dev/null +++ b/client/src/m_menu.h @@ -0,0 +1,54 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Menu widget stuff, episode selection and such. +// + +#ifndef __M_MENU__ +#define __M_MENU__ + +#include "d_event.h" +#include "doomtype.h" + +// +// MENUS +// +// Called by main loop, +// saves config file and calls I_Quit when user exits. +// Even when the menu is not displayed, +// this can resize the view and change game parameters. +// Does all the real work of the menu interaction. +boolean M_Responder(event_t *ev); + +// Called by main loop, +// only used for menu (skull cursor) animation. +void M_Ticker(void); + +// Called by main loop, +// draws the menus directly into the screen buffer. +void M_Drawer(void); + +// Called by D_DoomMain, +// loads the config file. +void M_Init(void); + +// Called by intro code to force menu up upon a keypress, +// does nothing if menu is already up. +void M_StartControlPanel(void); + +extern int detailLevel; +extern int screenblocks; + +#endif diff --git a/client/src/m_misc.c b/client/src/m_misc.c new file mode 100644 index 0000000..2f5c403 --- /dev/null +++ b/client/src/m_misc.c @@ -0,0 +1,562 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Miscellaneous. +// + + +#include +#include +#include +#include +#include +#include +#include + +#include "doomtype.h" +#include "i_system.h" +#include "m_misc.h" +#include "z_zone.h" + +// +// Create a directory +// + +void M_MakeDirectory(char *path) +{ + mkdir(path, 0755); +} + +// Check if a file exists + +boolean M_FileExists(char *filename) +{ + FILE *fstream; + + fstream = fopen(filename, "r"); + + if (fstream != NULL) + { + fclose(fstream); + return true; + } + else + { + // If we can't open because the file is a directory, the + // "file" exists at least! + + return errno == EISDIR; + } +} + +// Check if a file exists by probing for common case variation of its filename. +// Returns a newly allocated string that the caller is responsible for freeing. + +char *M_FileCaseExists(char *path) +{ + char *path_dup, *filename, *ext; + + path_dup = M_StringDuplicate(path); + + // 0: actual path + if (M_FileExists(path_dup)) + { + return path_dup; + } + + filename = strrchr(path_dup, DIR_SEPARATOR); + if (filename != NULL) + { + filename++; + } + else + { + filename = path_dup; + } + + // 1: lowercase filename, e.g. doom2.wad + M_ForceLowercase(filename); + + if (M_FileExists(path_dup)) + { + return path_dup; + } + + // 2: uppercase filename, e.g. DOOM2.WAD + M_ForceUppercase(filename); + + if (M_FileExists(path_dup)) + { + return path_dup; + } + + // 3. uppercase basename with lowercase extension, e.g. DOOM2.wad + ext = strrchr(path_dup, '.'); + if (ext != NULL && ext > filename) + { + M_ForceLowercase(ext + 1); + + if (M_FileExists(path_dup)) + { + return path_dup; + } + } + + // 4. lowercase filename with uppercase first letter, e.g. Doom2.wad + if (strlen(filename) > 1) + { + M_ForceLowercase(filename + 1); + + if (M_FileExists(path_dup)) + { + return path_dup; + } + } + + // 5. no luck + free(path_dup); + return NULL; +} + +// +// Determine the length of an open file. +// + +long M_FileLength(FILE *handle) +{ + long savedpos; + long length; + + // save the current position in the file + savedpos = ftell(handle); + + // jump to the end and find the length + fseek(handle, 0, SEEK_END); + length = ftell(handle); + + // go back to the old location + fseek(handle, savedpos, SEEK_SET); + + return length; +} + +// +// M_WriteFile +// + +boolean M_WriteFile(char *name, void *source, int length) +{ + FILE *handle; + int count; + + handle = fopen(name, "wb"); + + if (handle == NULL) + return false; + + count = fwrite(source, 1, length, handle); + fclose(handle); + + if (count < length) + return false; + + return true; +} + + +// +// M_ReadFile +// + +int M_ReadFile(char *name, byte **buffer) +{ + FILE *handle; + int count, length; + byte *buf; + + handle = fopen(name, "rb"); + if (handle == NULL) + I_Error ("Couldn't read file %s", name); + + // find the size of the file by seeking to the end and + // reading the current position + + length = M_FileLength(handle); + + buf = Z_Malloc (length, PU_STATIC, NULL); + count = fread(buf, 1, length, handle); + fclose (handle); + + if (count < length) + I_Error ("Couldn't read file %s", name); + + *buffer = buf; + return length; +} + +// Returns the path to a temporary file of the given name, stored +// inside the system temporary directory. +// +// The returned value must be freed with Z_Free after use. + +char *M_TempFile(char *s) +{ + char *tempdir; + + tempdir = "/tmp"; + + return M_StringJoin(tempdir, DIR_SEPARATOR_S, s, NULL); +} + +boolean M_StrToInt(const char *str, int *result) +{ + return sscanf(str, " 0x%x", result) == 1 + || sscanf(str, " 0X%x", result) == 1 + || sscanf(str, " 0%o", result) == 1 + || sscanf(str, " %d", result) == 1; +} + +void M_ExtractFileBase(char *path, char *dest) +{ + char *src; + char *filename; + int length; + + src = path + strlen(path) - 1; + + // back up until a \ or the start + while (src != path && *(src - 1) != DIR_SEPARATOR) + { + src--; + } + + filename = src; + + // Copy up to eight characters + // Note: Vanilla Doom exits with an error if a filename is specified + // with a base of more than eight characters. To remove the 8.3 + // filename limit, instead we simply truncate the name. + + length = 0; + memset(dest, 0, 8); + + while (*src != '\0' && *src != '.') + { + if (length >= 8) + { + printf("Warning: Truncated '%s' lump name to '%.8s'.\n", + filename, dest); + break; + } + + dest[length++] = toupper((int)*src++); + } +} + +//--------------------------------------------------------------------------- +// +// PROC M_ForceUppercase +// +// Change string to uppercase. +// +//--------------------------------------------------------------------------- + +void M_ForceUppercase(char *text) +{ + char *p; + + for (p = text; *p != '\0'; ++p) + { + *p = toupper(*p); + } +} + +//--------------------------------------------------------------------------- +// +// PROC M_ForceLowercase +// +// Change string to lowercase. +// +//--------------------------------------------------------------------------- + +void M_ForceLowercase(char *text) +{ + char *p; + + for (p = text; *p != '\0'; ++p) + { + *p = tolower(*p); + } +} + +// +// M_StrCaseStr +// +// Case-insensitive version of strstr() +// + +char *M_StrCaseStr(char *haystack, char *needle) +{ + unsigned int haystack_len; + unsigned int needle_len; + unsigned int len; + unsigned int i; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (haystack_len < needle_len) + { + return NULL; + } + + len = haystack_len - needle_len; + + for (i = 0; i <= len; ++i) + { + if (!strncasecmp(haystack + i, needle, needle_len)) + { + return haystack + i; + } + } + + return NULL; +} + +// +// Safe version of strdup() that checks the string was successfully +// allocated. +// + +char *M_StringDuplicate(const char *orig) +{ + char *result; + + result = strdup(orig); + + if (result == NULL) + { + I_Error("Failed to duplicate string (length %i)\n", + strlen(orig)); + } + + return result; +} + +// +// String replace function. +// + +char *M_StringReplace(const char *haystack, const char *needle, + const char *replacement) +{ + char *result, *dst; + const char *p; + size_t needle_len = strlen(needle); + size_t result_len, dst_len; + + // Iterate through occurrences of 'needle' and calculate the size of + // the new string. + result_len = strlen(haystack) + 1; + p = haystack; + + for (;;) + { + p = strstr(p, needle); + if (p == NULL) + { + break; + } + + p += needle_len; + result_len += strlen(replacement) - needle_len; + } + + // Construct new string. + + result = malloc(result_len); + if (result == NULL) + { + I_Error("M_StringReplace: Failed to allocate new string"); + return NULL; + } + + dst = result; dst_len = result_len; + p = haystack; + + while (*p != '\0') + { + if (!strncmp(p, needle, needle_len)) + { + M_StringCopy(dst, replacement, dst_len); + p += needle_len; + dst += strlen(replacement); + dst_len -= strlen(replacement); + } + else + { + *dst = *p; + ++dst; --dst_len; + ++p; + } + } + + *dst = '\0'; + + return result; +} + +// Safe string copy function that works like OpenBSD's strlcpy(). +// Returns true if the string was not truncated. + +boolean M_StringCopy(char *dest, const char *src, size_t dest_size) +{ + size_t len; + + if (dest_size >= 1) + { + dest[dest_size - 1] = '\0'; + strncpy(dest, src, dest_size - 1); + } + else + { + return false; + } + + len = strlen(dest); + return src[len] == '\0'; +} + +// Safe string concat function that works like OpenBSD's strlcat(). +// Returns true if string not truncated. + +boolean M_StringConcat(char *dest, const char *src, size_t dest_size) +{ + size_t offset; + + offset = strlen(dest); + if (offset > dest_size) + { + offset = dest_size; + } + + return M_StringCopy(dest + offset, src, dest_size - offset); +} + +// Returns true if 's' begins with the specified prefix. + +boolean M_StringStartsWith(const char *s, const char *prefix) +{ + return strlen(s) > strlen(prefix) + && strncmp(s, prefix, strlen(prefix)) == 0; +} + +// Returns true if 's' ends with the specified suffix. + +boolean M_StringEndsWith(const char *s, const char *suffix) +{ + return strlen(s) >= strlen(suffix) + && strcmp(s + strlen(s) - strlen(suffix), suffix) == 0; +} + +// Return a newly-malloced string with all the strings given as arguments +// concatenated together. + +char *M_StringJoin(const char *s, ...) +{ + char *result; + const char *v; + va_list args; + size_t result_len; + + result_len = strlen(s) + 1; + + va_start(args, s); + for (;;) + { + v = va_arg(args, const char *); + if (v == NULL) + { + break; + } + + result_len += strlen(v); + } + va_end(args); + + result = malloc(result_len); + + if (result == NULL) + { + I_Error("M_StringJoin: Failed to allocate new string."); + return NULL; + } + + M_StringCopy(result, s, result_len); + + va_start(args, s); + for (;;) + { + v = va_arg(args, const char *); + if (v == NULL) + { + break; + } + + M_StringConcat(result, v, result_len); + } + va_end(args); + + return result; +} + +// Safe, portable vsnprintf(). +int M_vsnprintf(char *buf, size_t buf_len, const char *s, va_list args) +{ + int result; + + if (buf_len < 1) + { + return 0; + } + + // Windows (and other OSes?) has a vsnprintf() that doesn't always + // append a trailing \0. So we must do it, and write into a buffer + // that is one byte shorter; otherwise this function is unsafe. + result = vsnprintf(buf, buf_len, s, args); + + // If truncated, change the final char in the buffer to a \0. + // A negative result indicates a truncated buffer on Windows. + if (result < 0 || result >= buf_len) + { + buf[buf_len - 1] = '\0'; + result = buf_len - 1; + } + + return result; +} + +// Safe, portable snprintf(). +int M_snprintf(char *buf, size_t buf_len, const char *s, ...) +{ + va_list args; + int result; + va_start(args, s); + result = M_vsnprintf(buf, buf_len, s, args); + va_end(args); + return result; +} + diff --git a/client/src/m_misc.h b/client/src/m_misc.h new file mode 100644 index 0000000..b93bef0 --- /dev/null +++ b/client/src/m_misc.h @@ -0,0 +1,52 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Miscellaneous. +// + + +#ifndef __M_MISC__ +#define __M_MISC__ + +#include +#include + +#include "doomtype.h" + +boolean M_WriteFile(char *name, void *source, int length); +int M_ReadFile(char *name, byte **buffer); +void M_MakeDirectory(char *dir); +char *M_TempFile(char *s); +boolean M_FileExists(char *file); +char *M_FileCaseExists(char *file); +long M_FileLength(FILE *handle); +boolean M_StrToInt(const char *str, int *result); +void M_ExtractFileBase(char *path, char *dest); +void M_ForceUppercase(char *text); +void M_ForceLowercase(char *text); +char *M_StrCaseStr(char *haystack, char *needle); +char *M_StringDuplicate(const char *orig); +boolean M_StringCopy(char *dest, const char *src, size_t dest_size); +boolean M_StringConcat(char *dest, const char *src, size_t dest_size); +char *M_StringReplace(const char *haystack, const char *needle, + const char *replacement); +char *M_StringJoin(const char *s, ...); +boolean M_StringStartsWith(const char *s, const char *prefix); +boolean M_StringEndsWith(const char *s, const char *suffix); +int M_vsnprintf(char *buf, size_t buf_len, const char *s, va_list args); +int M_snprintf(char *buf, size_t buf_len, const char *s, ...); + +#endif + diff --git a/client/src/m_random.c b/client/src/m_random.c new file mode 100644 index 0000000..2676c2d --- /dev/null +++ b/client/src/m_random.c @@ -0,0 +1,64 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Random number LUT. +// + +// +// M_Random +// Returns a 0-255 number +// + +static const unsigned char rndtable[256] = { + 0, 8, 109, 220, 222, 241, 149, 107, 75, 248, 254, 140, 16, 66, 74, + 21, 211, 47, 80, 242, 154, 27, 205, 128, 161, 89, 77, 36, 95, 110, + 85, 48, 212, 140, 211, 249, 22, 79, 200, 50, 28, 188, 52, 140, 202, + 120, 68, 145, 62, 70, 184, 190, 91, 197, 152, 224, 149, 104, 25, 178, + 252, 182, 202, 182, 141, 197, 4, 81, 181, 242, 145, 42, 39, 227, 156, + 198, 225, 193, 219, 93, 122, 175, 249, 0, 175, 143, 70, 239, 46, 246, + 163, 53, 163, 109, 168, 135, 2, 235, 25, 92, 20, 145, 138, 77, 69, + 166, 78, 176, 173, 212, 166, 113, 94, 161, 41, 50, 239, 49, 111, 164, + 70, 60, 2, 37, 171, 75, 136, 156, 11, 56, 42, 146, 138, 229, 73, + 146, 77, 61, 98, 196, 135, 106, 63, 197, 195, 86, 96, 203, 113, 101, + 170, 247, 181, 113, 80, 250, 108, 7, 255, 237, 129, 226, 79, 107, 112, + 166, 103, 241, 24, 223, 239, 120, 198, 58, 60, 82, 128, 3, 184, 66, + 143, 224, 145, 224, 81, 206, 163, 45, 63, 90, 168, 114, 59, 33, 159, + 95, 28, 139, 123, 98, 125, 196, 15, 70, 194, 253, 54, 14, 109, 226, + 71, 17, 161, 93, 186, 87, 244, 138, 20, 52, 123, 251, 26, 36, 17, + 46, 52, 231, 232, 76, 31, 221, 84, 37, 216, 165, 212, 106, 197, 242, + 98, 43, 39, 175, 254, 145, 190, 84, 118, 222, 187, 136, 120, 163, 236, + 249}; + +int rndindex = 0; +int prndindex = 0; + +// Which one is deterministic? +int P_Random(void) { + prndindex = (prndindex + 1) & 0xff; + return rndtable[prndindex]; +} + +int M_Random(void) { + rndindex = (rndindex + 1) & 0xff; + return rndtable[rndindex]; +} + +void M_ClearRandom(void) { rndindex = prndindex = 0; } + +// inspired by the same routine in Eternity, thanks haleyjd +int P_SubRandom(void) { + int r = P_Random(); + return r - P_Random(); +} diff --git a/client/src/m_random.h b/client/src/m_random.h new file mode 100644 index 0000000..884fea2 --- /dev/null +++ b/client/src/m_random.h @@ -0,0 +1,37 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// + +#ifndef __M_RANDOM__ +#define __M_RANDOM__ + +#include "doomtype.h" + +// Returns a number from 0 to 255, +// from a lookup table. +int M_Random(void); + +// As M_Random, but used only by the play simulation. +int P_Random(void); + +// Fix randoms for demos. +void M_ClearRandom(void); + +// Defined version of P_Random() - P_Random() +int P_SubRandom(void); + +#endif diff --git a/client/src/memio.c b/client/src/memio.c new file mode 100644 index 0000000..8cf7547 --- /dev/null +++ b/client/src/memio.c @@ -0,0 +1,195 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// Emulates the IO functions in C stdio.h reading and writing to +// memory. +// + +#include +#include + +#include "memio.h" +#include "z_zone.h" + +typedef enum { + MODE_READ, + MODE_WRITE, +} memfile_mode_t; + +struct _MEMFILE { + unsigned char *buf; + size_t buflen; + size_t alloced; + unsigned int position; + memfile_mode_t mode; +}; + +// Open a memory area for reading + +MEMFILE *mem_fopen_read(void *buf, size_t buflen) +{ + MEMFILE *file; + + file = Z_Malloc(sizeof(MEMFILE), PU_STATIC, 0); + + file->buf = (unsigned char *) buf; + file->buflen = buflen; + file->position = 0; + file->mode = MODE_READ; + + return file; +} + +// Read bytes + +size_t mem_fread(void *buf, size_t size, size_t nmemb, MEMFILE *stream) +{ + size_t items; + + if (stream->mode != MODE_READ) + { + printf("not a read stream\n"); + return -1; + } + + // Trying to read more bytes than we have left? + + items = nmemb; + + if (items * size > stream->buflen - stream->position) + { + items = (stream->buflen - stream->position) / size; + } + + // Copy bytes to buffer + + memcpy(buf, stream->buf + stream->position, items * size); + + // Update position + + stream->position += items * size; + + return items; +} + +// Open a memory area for writing + +MEMFILE *mem_fopen_write(void) +{ + MEMFILE *file; + + file = Z_Malloc(sizeof(MEMFILE), PU_STATIC, 0); + + file->alloced = 1024; + file->buf = Z_Malloc(file->alloced, PU_STATIC, 0); + file->buflen = 0; + file->position = 0; + file->mode = MODE_WRITE; + + return file; +} + +// Write bytes to stream + +size_t mem_fwrite(const void *ptr, size_t size, size_t nmemb, MEMFILE *stream) +{ + size_t bytes; + + if (stream->mode != MODE_WRITE) + { + return -1; + } + + // More bytes than can fit in the buffer? + // If so, reallocate bigger. + + bytes = size * nmemb; + + while (bytes > stream->alloced - stream->position) + { + unsigned char *newbuf; + + newbuf = Z_Malloc(stream->alloced * 2, PU_STATIC, 0); + memcpy(newbuf, stream->buf, stream->alloced); + Z_Free(stream->buf); + stream->buf = newbuf; + stream->alloced *= 2; + } + + // Copy into buffer + + memcpy(stream->buf + stream->position, ptr, bytes); + stream->position += bytes; + + if (stream->position > stream->buflen) + stream->buflen = stream->position; + + return nmemb; +} + +void mem_get_buf(MEMFILE *stream, void **buf, size_t *buflen) +{ + *buf = stream->buf; + *buflen = stream->buflen; +} + +void mem_fclose(MEMFILE *stream) +{ + if (stream->mode == MODE_WRITE) + { + Z_Free(stream->buf); + } + + Z_Free(stream); +} + +long mem_ftell(MEMFILE *stream) +{ + return stream->position; +} + +int mem_fseek(MEMFILE *stream, signed long position, mem_rel_t whence) +{ + unsigned int newpos; + + switch (whence) + { + case MEM_SEEK_SET: + newpos = (int) position; + break; + + case MEM_SEEK_CUR: + newpos = (int) (stream->position + position); + break; + + case MEM_SEEK_END: + newpos = (int) (stream->buflen + position); + break; + default: + return -1; + } + + if (newpos < stream->buflen) + { + stream->position = newpos; + return 0; + } + else + { + printf("Error seeking to %i\n", newpos); + return -1; + } +} + + diff --git a/client/src/memio.h b/client/src/memio.h new file mode 100644 index 0000000..84e6a7a --- /dev/null +++ b/client/src/memio.h @@ -0,0 +1,40 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// + +#ifndef MEMIO_H +#define MEMIO_H + +#include + +typedef struct _MEMFILE MEMFILE; + +typedef enum +{ + MEM_SEEK_SET, + MEM_SEEK_CUR, + MEM_SEEK_END, +} mem_rel_t; + +MEMFILE *mem_fopen_read(void *buf, size_t buflen); +size_t mem_fread(void *buf, size_t size, size_t nmemb, MEMFILE *stream); +MEMFILE *mem_fopen_write(void); +size_t mem_fwrite(const void *ptr, size_t size, size_t nmemb, MEMFILE *stream); +void mem_get_buf(MEMFILE *stream, void **buf, size_t *buflen); +void mem_fclose(MEMFILE *stream); +long mem_ftell(MEMFILE *stream); +int mem_fseek(MEMFILE *stream, signed long offset, mem_rel_t whence); + +#endif /* #ifndef MEMIO_H */ + diff --git a/client/src/midifile.c b/client/src/midifile.c new file mode 100644 index 0000000..d621a17 --- /dev/null +++ b/client/src/midifile.c @@ -0,0 +1,819 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Reading of MIDI files. +// + +#include +#include +#include +#include + +#include "SDL2/SDL_endian.h" +#include "doomtype.h" +#include "midifile.h" + +#define HEADER_CHUNK_ID "MThd" +#define TRACK_CHUNK_ID "MTrk" +#define MAX_BUFFER_SIZE 0x10000 + +typedef PACKED_STRUCT ( +{ + byte chunk_id[4]; + unsigned int chunk_size; +}) chunk_header_t; + +typedef PACKED_STRUCT ( +{ + chunk_header_t chunk_header; + unsigned short format_type; + unsigned short num_tracks; + unsigned short time_division; +}) midi_header_t; + +typedef struct +{ + // Length in bytes: + + unsigned int data_len; + + // Events in this track: + + midi_event_t *events; + int num_events; +} midi_track_t; + +struct midi_track_iter_s +{ + midi_track_t *track; + unsigned int position; +}; + +struct midi_file_s +{ + midi_header_t header; + + // All tracks in this file: + midi_track_t *tracks; + unsigned int num_tracks; + + // Data buffer used to store data read for SysEx or meta events: + byte *buffer; + unsigned int buffer_size; +}; + +// Check the header of a chunk: + +static boolean CheckChunkHeader(chunk_header_t *chunk, + char *expected_id) +{ + boolean result; + + result = (memcmp((char *) chunk->chunk_id, expected_id, 4) == 0); + + if (!result) + { + fprintf(stderr, "CheckChunkHeader: Expected '%s' chunk header, " + "got '%c%c%c%c'\n", + expected_id, + chunk->chunk_id[0], chunk->chunk_id[1], + chunk->chunk_id[2], chunk->chunk_id[3]); + } + + return result; +} + +// Read a single byte. Returns false on error. + +static boolean ReadByte(byte *result, FILE *stream) +{ + int c; + + c = fgetc(stream); + + if (c == EOF) + { + fprintf(stderr, "ReadByte: Unexpected end of file\n"); + return false; + } + else + { + *result = (byte) c; + + return true; + } +} + +// Read a variable-length value. + +static boolean ReadVariableLength(unsigned int *result, FILE *stream) +{ + int i; + byte b = 0; + + *result = 0; + + for (i=0; i<4; ++i) + { + if (!ReadByte(&b, stream)) + { + fprintf(stderr, "ReadVariableLength: Error while reading " + "variable-length value\n"); + return false; + } + + // Insert the bottom seven bits from this byte. + + *result <<= 7; + *result |= b & 0x7f; + + // If the top bit is not set, this is the end. + + if ((b & 0x80) == 0) + { + return true; + } + } + + fprintf(stderr, "ReadVariableLength: Variable-length value too " + "long: maximum of four bytes\n"); + return false; +} + +// Read a byte sequence into the data buffer. + +static void *ReadByteSequence(unsigned int num_bytes, FILE *stream) +{ + unsigned int i; + byte *result; + + // Allocate a buffer. Allocate one extra byte, as malloc(0) is + // non-portable. + + result = malloc(num_bytes + 1); + + if (result == NULL) + { + fprintf(stderr, "ReadByteSequence: Failed to allocate buffer\n"); + return NULL; + } + + // Read the data: + + for (i=0; ievent_type = event_type & 0xf0; + event->data.channel.channel = event_type & 0x0f; + + // Read parameters: + + if (!ReadByte(&b, stream)) + { + fprintf(stderr, "ReadChannelEvent: Error while reading channel " + "event parameters\n"); + return false; + } + + event->data.channel.param1 = b; + + // Second parameter: + + if (two_param) + { + if (!ReadByte(&b, stream)) + { + fprintf(stderr, "ReadChannelEvent: Error while reading channel " + "event parameters\n"); + return false; + } + + event->data.channel.param2 = b; + } + + return true; +} + +// Read sysex event: + +static boolean ReadSysExEvent(midi_event_t *event, int event_type, + FILE *stream) +{ + event->event_type = event_type; + + if (!ReadVariableLength(&event->data.sysex.length, stream)) + { + fprintf(stderr, "ReadSysExEvent: Failed to read length of " + "SysEx block\n"); + return false; + } + + // Read the byte sequence: + + event->data.sysex.data = ReadByteSequence(event->data.sysex.length, stream); + + if (event->data.sysex.data == NULL) + { + fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); + return false; + } + + return true; +} + +// Read meta event: + +static boolean ReadMetaEvent(midi_event_t *event, FILE *stream) +{ + byte b = 0; + + event->event_type = MIDI_EVENT_META; + + // Read meta event type: + + if (!ReadByte(&b, stream)) + { + fprintf(stderr, "ReadMetaEvent: Failed to read meta event type\n"); + return false; + } + + event->data.meta.type = b; + + // Read length of meta event data: + + if (!ReadVariableLength(&event->data.meta.length, stream)) + { + fprintf(stderr, "ReadSysExEvent: Failed to read length of " + "SysEx block\n"); + return false; + } + + // Read the byte sequence: + + event->data.meta.data = ReadByteSequence(event->data.meta.length, stream); + + if (event->data.meta.data == NULL) + { + fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); + return false; + } + + return true; +} + +static boolean ReadEvent(midi_event_t *event, unsigned int *last_event_type, + FILE *stream) +{ + byte event_type = 0; + + if (!ReadVariableLength(&event->delta_time, stream)) + { + fprintf(stderr, "ReadEvent: Failed to read event timestamp\n"); + return false; + } + + if (!ReadByte(&event_type, stream)) + { + fprintf(stderr, "ReadEvent: Failed to read event type\n"); + return false; + } + + // All event types have their top bit set. Therefore, if + // the top bit is not set, it is because we are using the "same + // as previous event type" shortcut to save a byte. Skip back + // a byte so that we read this byte again. + + if ((event_type & 0x80) == 0) + { + event_type = *last_event_type; + + if (fseek(stream, -1, SEEK_CUR) < 0) + { + fprintf(stderr, "ReadEvent: Unable to seek in stream\n"); + return false; + } + } + else + { + *last_event_type = event_type; + } + + // Check event type: + + switch (event_type & 0xf0) + { + // Two parameter channel events: + + case MIDI_EVENT_NOTE_OFF: + case MIDI_EVENT_NOTE_ON: + case MIDI_EVENT_AFTERTOUCH: + case MIDI_EVENT_CONTROLLER: + case MIDI_EVENT_PITCH_BEND: + return ReadChannelEvent(event, event_type, true, stream); + + // Single parameter channel events: + + case MIDI_EVENT_PROGRAM_CHANGE: + case MIDI_EVENT_CHAN_AFTERTOUCH: + return ReadChannelEvent(event, event_type, false, stream); + + default: + break; + } + + // Specific value? + + switch (event_type) + { + case MIDI_EVENT_SYSEX: + case MIDI_EVENT_SYSEX_SPLIT: + return ReadSysExEvent(event, event_type, stream); + + case MIDI_EVENT_META: + return ReadMetaEvent(event, stream); + + default: + break; + } + + fprintf(stderr, "ReadEvent: Unknown MIDI event type: 0x%x\n", event_type); + return false; +} + +// Free an event: + +static void FreeEvent(midi_event_t *event) +{ + // Some event types have dynamically allocated buffers assigned + // to them that must be freed. + + switch (event->event_type) + { + case MIDI_EVENT_SYSEX: + case MIDI_EVENT_SYSEX_SPLIT: + free(event->data.sysex.data); + break; + + case MIDI_EVENT_META: + free(event->data.meta.data); + break; + + default: + // Nothing to do. + break; + } +} + +// Read and check the track chunk header + +static boolean ReadTrackHeader(midi_track_t *track, FILE *stream) +{ + size_t records_read; + chunk_header_t chunk_header; + + records_read = fread(&chunk_header, sizeof(chunk_header_t), 1, stream); + + if (records_read < 1) + { + return false; + } + + if (!CheckChunkHeader(&chunk_header, TRACK_CHUNK_ID)) + { + return false; + } + + track->data_len = SDL_SwapBE32(chunk_header.chunk_size); + + return true; +} + +static boolean ReadTrack(midi_track_t *track, FILE *stream) +{ + midi_event_t *new_events; + midi_event_t *event; + unsigned int last_event_type; + + track->num_events = 0; + track->events = NULL; + + // Read the header: + + if (!ReadTrackHeader(track, stream)) + { + return false; + } + + // Then the events: + + last_event_type = 0; + + for (;;) + { + // Resize the track slightly larger to hold another event: + + new_events = realloc(track->events, + sizeof(midi_event_t) * (track->num_events + 1)); + + if (new_events == NULL) + { + return false; + } + + track->events = new_events; + + // Read the next event: + + event = &track->events[track->num_events]; + if (!ReadEvent(event, &last_event_type, stream)) + { + return false; + } + + ++track->num_events; + + // End of track? + + if (event->event_type == MIDI_EVENT_META + && event->data.meta.type == MIDI_META_END_OF_TRACK) + { + break; + } + } + + return true; +} + +// Free a track: + +static void FreeTrack(midi_track_t *track) +{ + unsigned int i; + + for (i=0; inum_events; ++i) + { + FreeEvent(&track->events[i]); + } + + free(track->events); +} + +static boolean ReadAllTracks(midi_file_t *file, FILE *stream) +{ + unsigned int i; + + // Allocate list of tracks and read each track: + + file->tracks = malloc(sizeof(midi_track_t) * file->num_tracks); + + if (file->tracks == NULL) + { + return false; + } + + memset(file->tracks, 0, sizeof(midi_track_t) * file->num_tracks); + + // Read each track: + + for (i=0; inum_tracks; ++i) + { + if (!ReadTrack(&file->tracks[i], stream)) + { + return false; + } + } + + return true; +} + +// Read and check the header chunk. + +static boolean ReadFileHeader(midi_file_t *file, FILE *stream) +{ + size_t records_read; + unsigned int format_type; + + records_read = fread(&file->header, sizeof(midi_header_t), 1, stream); + + if (records_read < 1) + { + return false; + } + + if (!CheckChunkHeader(&file->header.chunk_header, HEADER_CHUNK_ID) + || SDL_SwapBE32(file->header.chunk_header.chunk_size) != 6) + { + fprintf(stderr, "ReadFileHeader: Invalid MIDI chunk header! " + "chunk_size=%i\n", + SDL_SwapBE32(file->header.chunk_header.chunk_size)); + return false; + } + + format_type = SDL_SwapBE16(file->header.format_type); + file->num_tracks = SDL_SwapBE16(file->header.num_tracks); + + if ((format_type != 0 && format_type != 1) + || file->num_tracks < 1) + { + fprintf(stderr, "ReadFileHeader: Only type 0/1 " + "MIDI files supported!\n"); + return false; + } + + return true; +} + +void MIDI_FreeFile(midi_file_t *file) +{ + int i; + + if (file->tracks != NULL) + { + for (i=0; inum_tracks; ++i) + { + FreeTrack(&file->tracks[i]); + } + + free(file->tracks); + } + + free(file); +} + +midi_file_t *MIDI_LoadFile(char *filename) +{ + midi_file_t *file; + FILE *stream; + + file = malloc(sizeof(midi_file_t)); + + if (file == NULL) + { + return NULL; + } + + file->tracks = NULL; + file->num_tracks = 0; + file->buffer = NULL; + file->buffer_size = 0; + + // Open file + + stream = fopen(filename, "rb"); + + if (stream == NULL) + { + fprintf(stderr, "MIDI_LoadFile: Failed to open '%s'\n", filename); + MIDI_FreeFile(file); + return NULL; + } + + // Read MIDI file header + + if (!ReadFileHeader(file, stream)) + { + fclose(stream); + MIDI_FreeFile(file); + return NULL; + } + + // Read all tracks: + + if (!ReadAllTracks(file, stream)) + { + fclose(stream); + MIDI_FreeFile(file); + return NULL; + } + + fclose(stream); + + return file; +} + +// Get the number of tracks in a MIDI file. + +unsigned int MIDI_NumTracks(midi_file_t *file) +{ + return file->num_tracks; +} + +// Start iterating over the events in a track. + +midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track) +{ + midi_track_iter_t *iter; + + assert(track < file->num_tracks); + + iter = malloc(sizeof(*iter)); + iter->track = &file->tracks[track]; + iter->position = 0; + + return iter; +} + +void MIDI_FreeIterator(midi_track_iter_t *iter) +{ + free(iter); +} + +// Get the time until the next MIDI event in a track. + +unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter) +{ + if (iter->position < iter->track->num_events) + { + midi_event_t *next_event; + + next_event = &iter->track->events[iter->position]; + + return next_event->delta_time; + } + else + { + return 0; + } +} + +// Get a pointer to the next MIDI event. + +int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event) +{ + if (iter->position < iter->track->num_events) + { + *event = &iter->track->events[iter->position]; + ++iter->position; + + return 1; + } + else + { + return 0; + } +} + +unsigned int MIDI_GetFileTimeDivision(midi_file_t *file) +{ + short result = SDL_SwapBE16(file->header.time_division); + + // Negative time division indicates SMPTE time and must be handled + // differently. + if (result < 0) + { + return (signed int)(-(result/256)) + * (signed int)(result & 0xFF); + } + else + { + return result; + } +} + +void MIDI_RestartIterator(midi_track_iter_t *iter) +{ + iter->position = 0; +} + +#ifdef TEST + +static char *MIDI_EventTypeToString(midi_event_type_t event_type) +{ + switch (event_type) + { + case MIDI_EVENT_NOTE_OFF: + return "MIDI_EVENT_NOTE_OFF"; + case MIDI_EVENT_NOTE_ON: + return "MIDI_EVENT_NOTE_ON"; + case MIDI_EVENT_AFTERTOUCH: + return "MIDI_EVENT_AFTERTOUCH"; + case MIDI_EVENT_CONTROLLER: + return "MIDI_EVENT_CONTROLLER"; + case MIDI_EVENT_PROGRAM_CHANGE: + return "MIDI_EVENT_PROGRAM_CHANGE"; + case MIDI_EVENT_CHAN_AFTERTOUCH: + return "MIDI_EVENT_CHAN_AFTERTOUCH"; + case MIDI_EVENT_PITCH_BEND: + return "MIDI_EVENT_PITCH_BEND"; + case MIDI_EVENT_SYSEX: + return "MIDI_EVENT_SYSEX"; + case MIDI_EVENT_SYSEX_SPLIT: + return "MIDI_EVENT_SYSEX_SPLIT"; + case MIDI_EVENT_META: + return "MIDI_EVENT_META"; + + default: + return "(unknown)"; + } +} + +void PrintTrack(midi_track_t *track) +{ + midi_event_t *event; + unsigned int i; + + for (i=0; inum_events; ++i) + { + event = &track->events[i]; + + if (event->delta_time > 0) + { + printf("Delay: %i ticks\n", event->delta_time); + } + + printf("Event type: %s (%i)\n", + MIDI_EventTypeToString(event->event_type), + event->event_type); + + switch(event->event_type) + { + case MIDI_EVENT_NOTE_OFF: + case MIDI_EVENT_NOTE_ON: + case MIDI_EVENT_AFTERTOUCH: + case MIDI_EVENT_CONTROLLER: + case MIDI_EVENT_PROGRAM_CHANGE: + case MIDI_EVENT_CHAN_AFTERTOUCH: + case MIDI_EVENT_PITCH_BEND: + printf("\tChannel: %i\n", event->data.channel.channel); + printf("\tParameter 1: %i\n", event->data.channel.param1); + printf("\tParameter 2: %i\n", event->data.channel.param2); + break; + + case MIDI_EVENT_SYSEX: + case MIDI_EVENT_SYSEX_SPLIT: + printf("\tLength: %i\n", event->data.sysex.length); + break; + + case MIDI_EVENT_META: + printf("\tMeta type: %i\n", event->data.meta.type); + printf("\tLength: %i\n", event->data.meta.length); + break; + } + } +} + +int main(int argc, char *argv[]) +{ + midi_file_t *file; + unsigned int i; + + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + exit(1); + } + + file = MIDI_LoadFile(argv[1]); + + if (file == NULL) + { + fprintf(stderr, "Failed to open %s\n", argv[1]); + exit(1); + } + + for (i=0; inum_tracks; ++i) + { + printf("\n== Track %i ==\n\n", i); + + PrintTrack(&file->tracks[i]); + } + + return 0; +} + +#endif + diff --git a/client/src/midifile.h b/client/src/midifile.h new file mode 100644 index 0000000..6f8c801 --- /dev/null +++ b/client/src/midifile.h @@ -0,0 +1,171 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// MIDI file parsing. +// + +#ifndef MIDIFILE_H +#define MIDIFILE_H + +#include "doomtype.h" + +typedef struct midi_file_s midi_file_t; +typedef struct midi_track_iter_s midi_track_iter_t; + +#define MIDI_CHANNELS_PER_TRACK 16 + +typedef enum +{ + MIDI_EVENT_NOTE_OFF = 0x80, + MIDI_EVENT_NOTE_ON = 0x90, + MIDI_EVENT_AFTERTOUCH = 0xa0, + MIDI_EVENT_CONTROLLER = 0xb0, + MIDI_EVENT_PROGRAM_CHANGE = 0xc0, + MIDI_EVENT_CHAN_AFTERTOUCH = 0xd0, + MIDI_EVENT_PITCH_BEND = 0xe0, + + MIDI_EVENT_SYSEX = 0xf0, + MIDI_EVENT_SYSEX_SPLIT = 0xf7, + MIDI_EVENT_META = 0xff, +} midi_event_type_t; + +typedef enum +{ + MIDI_CONTROLLER_BANK_SELECT = 0x0, + MIDI_CONTROLLER_MODULATION = 0x1, + MIDI_CONTROLLER_BREATH_CONTROL = 0x2, + MIDI_CONTROLLER_FOOT_CONTROL = 0x3, + MIDI_CONTROLLER_PORTAMENTO = 0x4, + MIDI_CONTROLLER_DATA_ENTRY = 0x5, + + MIDI_CONTROLLER_MAIN_VOLUME = 0x7, + MIDI_CONTROLLER_PAN = 0xa, + + MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7b, +} midi_controller_t; + +typedef enum +{ + MIDI_META_SEQUENCE_NUMBER = 0x0, + + MIDI_META_TEXT = 0x1, + MIDI_META_COPYRIGHT = 0x2, + MIDI_META_TRACK_NAME = 0x3, + MIDI_META_INSTR_NAME = 0x4, + MIDI_META_LYRICS = 0x5, + MIDI_META_MARKER = 0x6, + MIDI_META_CUE_POINT = 0x7, + + MIDI_META_CHANNEL_PREFIX = 0x20, + MIDI_META_END_OF_TRACK = 0x2f, + + MIDI_META_SET_TEMPO = 0x51, + MIDI_META_SMPTE_OFFSET = 0x54, + MIDI_META_TIME_SIGNATURE = 0x58, + MIDI_META_KEY_SIGNATURE = 0x59, + MIDI_META_SEQUENCER_SPECIFIC = 0x7f, +} midi_meta_event_type_t; + +typedef struct +{ + // Meta event type: + + unsigned int type; + + // Length: + + unsigned int length; + + // Meta event data: + + byte *data; +} midi_meta_event_data_t; + +typedef struct +{ + // Length: + + unsigned int length; + + // Event data: + + byte *data; +} midi_sysex_event_data_t; + +typedef struct +{ + // The channel number to which this applies: + + unsigned int channel; + + // Extra parameters: + + unsigned int param1; + unsigned int param2; +} midi_channel_event_data_t; + +typedef struct +{ + // Time between the previous event and this event. + unsigned int delta_time; + + // Type of event: + midi_event_type_t event_type; + + union + { + midi_channel_event_data_t channel; + midi_meta_event_data_t meta; + midi_sysex_event_data_t sysex; + } data; +} midi_event_t; + +// Load a MIDI file. + +midi_file_t *MIDI_LoadFile(char *filename); + +// Free a MIDI file. + +void MIDI_FreeFile(midi_file_t *file); + +// Get the time division value from the MIDI header. + +unsigned int MIDI_GetFileTimeDivision(midi_file_t *file); + +// Get the number of tracks in a MIDI file. + +unsigned int MIDI_NumTracks(midi_file_t *file); + +// Start iterating over the events in a track. + +midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track_num); + +// Free an iterator. + +void MIDI_FreeIterator(midi_track_iter_t *iter); + +// Get the time until the next MIDI event in a track. + +unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter); + +// Get a pointer to the next MIDI event. + +int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event); + +// Reset an iterator to the beginning of a track. + +void MIDI_RestartIterator(midi_track_iter_t *iter); + +#endif /* #ifndef MIDIFILE_H */ + diff --git a/client/src/mus2mid.c b/client/src/mus2mid.c new file mode 100644 index 0000000..83c7a70 --- /dev/null +++ b/client/src/mus2mid.c @@ -0,0 +1,732 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2006 Ben Ryves 2006 +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// mus2mid.c - Ben Ryves 2006 - http://benryves.com - benryves@benryves.com +// Use to convert a MUS file into a single track, type 0 MIDI file. + +#include "doomtype.h" +#include "i_swap.h" +#include "memio.h" +#include "mus2mid.h" + +#define NUM_CHANNELS 16 + +#define MIDI_PERCUSSION_CHAN 9 +#define MUS_PERCUSSION_CHAN 15 + +// MUS event codes +typedef enum +{ + mus_releasekey = 0x00, + mus_presskey = 0x10, + mus_pitchwheel = 0x20, + mus_systemevent = 0x30, + mus_changecontroller = 0x40, + mus_scoreend = 0x60 +} musevent; + +// MIDI event codes +typedef enum +{ + midi_releasekey = 0x80, + midi_presskey = 0x90, + midi_aftertouchkey = 0xA0, + midi_changecontroller = 0xB0, + midi_changepatch = 0xC0, + midi_aftertouchchannel = 0xD0, + midi_pitchwheel = 0xE0 +} midievent; + +// Structure to hold MUS file header +typedef PACKED_STRUCT ( +{ + byte id[4]; + unsigned short scorelength; + unsigned short scorestart; + unsigned short primarychannels; + unsigned short secondarychannels; + unsigned short instrumentcount; +}) musheader; + +// Standard MIDI type 0 header + track header +static const byte midiheader[] = +{ + 'M', 'T', 'h', 'd', // Main header + 0x00, 0x00, 0x00, 0x06, // Header size + 0x00, 0x00, // MIDI type (0) + 0x00, 0x01, // Number of tracks + 0x00, 0x46, // Resolution + 'M', 'T', 'r', 'k', // Start of track + 0x00, 0x00, 0x00, 0x00 // Placeholder for track length +}; + +// Cached channel velocities +static byte channelvelocities[] = +{ + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127 +}; + +// Timestamps between sequences of MUS events + +static unsigned int queuedtime = 0; + +// Counter for the length of the track + +static unsigned int tracksize; + +static const byte controller_map[] = +{ + 0x00, 0x20, 0x01, 0x07, 0x0A, 0x0B, 0x5B, 0x5D, + 0x40, 0x43, 0x78, 0x7B, 0x7E, 0x7F, 0x79 +}; + +static int channel_map[NUM_CHANNELS]; + +// Write timestamp to a MIDI file. + +static boolean WriteTime(unsigned int time, MEMFILE *midioutput) +{ + unsigned int buffer = time & 0x7F; + byte writeval; + + while ((time >>= 7) != 0) + { + buffer <<= 8; + buffer |= ((time & 0x7F) | 0x80); + } + + for (;;) + { + writeval = (byte)(buffer & 0xFF); + + if (mem_fwrite(&writeval, 1, 1, midioutput) != 1) + { + return true; + } + + ++tracksize; + + if ((buffer & 0x80) != 0) + { + buffer >>= 8; + } + else + { + queuedtime = 0; + return false; + } + } +} + + +// Write the end of track marker +static boolean WriteEndTrack(MEMFILE *midioutput) +{ + byte endtrack[] = {0xFF, 0x2F, 0x00}; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(endtrack, 1, 3, midioutput) != 3) + { + return true; + } + + tracksize += 3; + return false; +} + +// Write a key press event +static boolean WritePressKey(byte channel, byte key, + byte velocity, MEMFILE *midioutput) +{ + byte working = midi_presskey | channel; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = key & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = velocity & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + tracksize += 3; + + return false; +} + +// Write a key release event +static boolean WriteReleaseKey(byte channel, byte key, + MEMFILE *midioutput) +{ + byte working = midi_releasekey | channel; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = key & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = 0; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + tracksize += 3; + + return false; +} + +// Write a pitch wheel/bend event +static boolean WritePitchWheel(byte channel, short wheel, + MEMFILE *midioutput) +{ + byte working = midi_pitchwheel | channel; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = wheel & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = (wheel >> 7) & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + tracksize += 3; + return false; +} + +// Write a patch change event +static boolean WriteChangePatch(byte channel, byte patch, + MEMFILE *midioutput) +{ + byte working = midi_changepatch | channel; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = patch & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + tracksize += 2; + + return false; +} + +// Write a valued controller change event + +static boolean WriteChangeController_Valued(byte channel, + byte control, + byte value, + MEMFILE *midioutput) +{ + byte working = midi_changecontroller | channel; + + if (WriteTime(queuedtime, midioutput)) + { + return true; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + working = control & 0x7F; + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + // Quirk in vanilla DOOM? MUS controller values should be + // 7-bit, not 8-bit. + + working = value;// & 0x7F; + + // Fix on said quirk to stop MIDI players from complaining that + // the value is out of range: + + if (working & 0x80) + { + working = 0x7F; + } + + if (mem_fwrite(&working, 1, 1, midioutput) != 1) + { + return true; + } + + tracksize += 3; + + return false; +} + +// Write a valueless controller change event +static boolean WriteChangeController_Valueless(byte channel, + byte control, + MEMFILE *midioutput) +{ + return WriteChangeController_Valued(channel, control, 0, + midioutput); +} + +// Allocate a free MIDI channel. + +static int AllocateMIDIChannel(void) +{ + int result; + int max; + int i; + + // Find the current highest-allocated channel. + + max = -1; + + for (i=0; i max) + { + max = channel_map[i]; + } + } + + // max is now equal to the highest-allocated MIDI channel. We can + // now allocate the next available channel. This also works if + // no channels are currently allocated (max=-1) + + result = max + 1; + + // Don't allocate the MIDI percussion channel! + + if (result == MIDI_PERCUSSION_CHAN) + { + ++result; + } + + return result; +} + +// Given a MUS channel number, get the MIDI channel number to use +// in the outputted file. + +static int GetMIDIChannel(int mus_channel, MEMFILE *midioutput) +{ + // Find the MIDI channel to use for this MUS channel. + // MUS channel 15 is the percusssion channel. + + if (mus_channel == MUS_PERCUSSION_CHAN) + { + return MIDI_PERCUSSION_CHAN; + } + else + { + // If a MIDI channel hasn't been allocated for this MUS channel + // yet, allocate the next free MIDI channel. + + if (channel_map[mus_channel] == -1) + { + channel_map[mus_channel] = AllocateMIDIChannel(); + + // First time using the channel, send an "all notes off" + // event. This fixes "The D_DDTBLU disease" described here: + // https://www.doomworld.com/vb/source-ports/66802-the + WriteChangeController_Valueless(channel_map[mus_channel], 0x7b, + midioutput); + } + + return channel_map[mus_channel]; + } +} + +static boolean ReadMusHeader(MEMFILE *file, musheader *header) +{ + boolean result; + + result = mem_fread(&header->id, sizeof(byte), 4, file) == 4 + && mem_fread(&header->scorelength, sizeof(short), 1, file) == 1 + && mem_fread(&header->scorestart, sizeof(short), 1, file) == 1 + && mem_fread(&header->primarychannels, sizeof(short), 1, file) == 1 + && mem_fread(&header->secondarychannels, sizeof(short), 1, file) == 1 + && mem_fread(&header->instrumentcount, sizeof(short), 1, file) == 1; + + if (result) + { + header->scorelength = SHORT(header->scorelength); + header->scorestart = SHORT(header->scorestart); + header->primarychannels = SHORT(header->primarychannels); + header->secondarychannels = SHORT(header->secondarychannels); + header->instrumentcount = SHORT(header->instrumentcount); + } + + return result; +} + + +// Read a MUS file from a stream (musinput) and output a MIDI file to +// a stream (midioutput). +// +// Returns 0 on success or 1 on failure. + +boolean mus2mid(MEMFILE *musinput, MEMFILE *midioutput) +{ + // Header for the MUS file + musheader musfileheader; + + // Descriptor for the current MUS event + byte eventdescriptor; + int channel; // Channel number + musevent event; + + + // Bunch of vars read from MUS lump + byte key; + byte controllernumber; + byte controllervalue; + + // Buffer used for MIDI track size record + byte tracksizebuffer[4]; + + // Flag for when the score end marker is hit. + int hitscoreend = 0; + + // Temp working byte + byte working; + // Used in building up time delays + unsigned int timedelay; + + // Initialise channel map to mark all channels as unused. + + for (channel=0; channel 14) + { + return true; + } + + if (WriteChangeController_Valueless(channel, + controller_map[controllernumber], + midioutput)) + { + return true; + } + + break; + + case mus_changecontroller: + if (mem_fread(&controllernumber, 1, 1, musinput) != 1) + { + return true; + } + + if (mem_fread(&controllervalue, 1, 1, musinput) != 1) + { + return true; + } + + if (controllernumber == 0) + { + if (WriteChangePatch(channel, controllervalue, + midioutput)) + { + return true; + } + } + else + { + if (controllernumber < 1 || controllernumber > 9) + { + return true; + } + + if (WriteChangeController_Valued(channel, + controller_map[controllernumber], + controllervalue, + midioutput)) + { + return true; + } + } + + break; + + case mus_scoreend: + hitscoreend = 1; + break; + + default: + return true; + break; + } + + if (eventdescriptor & 0x80) + { + break; + } + } + // Now we need to read the time code: + if (!hitscoreend) + { + timedelay = 0; + for (;;) + { + if (mem_fread(&working, 1, 1, musinput) != 1) + { + return true; + } + + timedelay = timedelay * 128 + (working & 0x7F); + if ((working & 0x80) == 0) + { + break; + } + } + queuedtime += timedelay; + } + } + + // End of track + if (WriteEndTrack(midioutput)) + { + return true; + } + + // Write the track size into the stream + if (mem_fseek(midioutput, 18, MEM_SEEK_SET)) + { + return true; + } + + tracksizebuffer[0] = (tracksize >> 24) & 0xff; + tracksizebuffer[1] = (tracksize >> 16) & 0xff; + tracksizebuffer[2] = (tracksize >> 8) & 0xff; + tracksizebuffer[3] = tracksize & 0xff; + + if (mem_fwrite(tracksizebuffer, 1, 4, midioutput) != 4) + { + return true; + } + + return false; +} + +#ifdef STANDALONE + +#include "m_misc.h" +#include "z_zone.h" + +int main(int argc, char *argv[]) +{ + MEMFILE *src, *dst; + byte *infile; + long infile_len; + void *outfile; + size_t outfile_len; + + if (argc != 3) + { + printf("Usage: %s \n", argv[0]); + exit(-1); + } + + Z_Init(); + + infile_len = M_ReadFile(argv[1], &infile); + + src = mem_fopen_read(infile, infile_len); + dst = mem_fopen_write(); + + if (mus2mid(src, dst)) + { + fprintf(stderr, "mus2mid() failed\n"); + exit(-1); + } + + // Write result to output file: + + mem_get_buf(dst, &outfile, &outfile_len); + + M_WriteFile(argv[2], outfile, outfile_len); + + return 0; +} + +#endif + diff --git a/client/src/mus2mid.h b/client/src/mus2mid.h new file mode 100644 index 0000000..324025a --- /dev/null +++ b/client/src/mus2mid.h @@ -0,0 +1,29 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2006 Ben Ryves 2006 +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// mus2mid.h - Ben Ryves 2006 - http://benryves.com - benryves@benryves.com +// Use to convert a MUS file into a single track, type 0 MIDI file. + +#ifndef MUS2MID_H +#define MUS2MID_H + +#include "doomtype.h" +#include "memio.h" + +boolean mus2mid(MEMFILE *musinput, MEMFILE *midioutput); + +#endif /* #ifndef MUS2MID_H */ + diff --git a/client/src/net_client.c b/client/src/net_client.c new file mode 100644 index 0000000..ef53bd9 --- /dev/null +++ b/client/src/net_client.c @@ -0,0 +1,1084 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// Network client code +// + +#include +#include +#include + +#include "doomtype.h" +#include "i_timer.h" +#include "m_config.h" +#include "m_fixed.h" +#include "net_client.h" +#include "net_common.h" +#include "net_defs.h" +#include "net_io.h" +#include "net_packet.h" +#include "net_server.h" +#include "net_structrw.h" + +extern void D_ReceiveTic(ticcmd_t *ticcmds, boolean *playeringame); + +typedef enum +{ + // waiting for the game to launch + + CLIENT_STATE_WAITING_LAUNCH, + + // waiting for the game to start + + CLIENT_STATE_WAITING_START, + + // in game + + CLIENT_STATE_IN_GAME, + +} net_clientstate_t; + +// Type of structure used in the receive window + +typedef struct +{ + // Whether this tic has been received yet + + boolean active; + + // Last time we sent a resend request for this tic + + unsigned int resend_time; + + // Tic data from server + + net_full_ticcmd_t cmd; + +} net_server_recv_t; + +// Type of structure used in the send window + +typedef struct +{ + // Whether this slot is active yet + + boolean active; + + // The tic number + + unsigned int seq; + + // Time the command was generated + + unsigned int time; + + // Ticcmd diff + + net_ticdiff_t cmd; +} net_server_send_t; + +extern fixed_t offsetms; + +static net_connection_t client_connection; +static net_clientstate_t client_state; +static net_addr_t *server_addr; +static net_context_t *client_context; + +// game settings, as received from the server when the game started + +static net_gamesettings_t settings; + +// true if the client code is in use + +boolean net_client_connected; + +// true if we have received waiting data from the server, +// and the wait data that was received. + +boolean net_client_received_wait_data; +net_waitdata_t net_client_wait_data; + +// Waiting at the initial wait screen for the game to be launched? + +boolean net_waiting_for_launch = false; + +// Name that we send to the server + +char *net_player_name = NULL; + +// Connected but not participating in the game (observer) + +boolean drone = false; + +// The last ticcmd constructed + +static ticcmd_t last_ticcmd; + +// Buffer of ticcmd diffs being sent to the server + +static net_server_send_t send_queue[BACKUPTICS]; + +// Receive window + +static ticcmd_t recvwindow_cmd_base[NET_MAXPLAYERS]; +static int recvwindow_start; +static net_server_recv_t recvwindow[BACKUPTICS]; + +// Whether we need to send an acknowledgement and +// when gamedata was last received. + +static boolean need_to_acknowledge; +static unsigned int gamedata_recv_time; + +// Hash checksums of our wad directory + +sha1_digest_t net_local_wad_sha1sum; + +// Are we playing with the freedoom IWAD? + +unsigned int net_local_is_freedoom; + +// Average time between sending our ticcmd and receiving from the server + +static fixed_t average_latency; + +#define NET_CL_ExpandTicNum(b) NET_ExpandTicNum(recvwindow_start, (b)) + +// Called when we become disconnected from the server + +static void NET_CL_Disconnected(void) +{ + D_ReceiveTic(NULL, NULL); +} + +// Expand a net_full_ticcmd_t, applying the diffs in cmd->cmds as +// patches against recvwindow_cmd_base. Place the results into +// the d_net.c structures (netcmds/nettics) and save the new ticcmd +// back into recvwindow_cmd_base. + +static void NET_CL_ExpandFullTiccmd(net_full_ticcmd_t *cmd, unsigned int seq, + ticcmd_t *ticcmds) +{ + int latency; + fixed_t adjustment; + int i; + + // Update average_latency + + if (seq == send_queue[seq % BACKUPTICS].seq) + { + latency = I_GetTimeMS() - send_queue[seq % BACKUPTICS].time; + } + else if (seq > send_queue[seq % BACKUPTICS].seq) + { + // We have received the ticcmd from the server before we have + // even sent ours + + latency = 0; + } + else + { + latency = -1; + } + + if (latency >= 0) + { + if (seq <= 20) + { + average_latency = latency * FRACUNIT; + } + else + { + // Low level filter + + average_latency = (fixed_t)((average_latency * 0.9) + + (latency * FRACUNIT * 0.1)); + } + } + + //printf("latency: %i\tremote:%i\n", average_latency / FRACUNIT, + // cmd->latency); + + // Possibly adjust offsetms in d_net.c, try to make players all have + // the same lag. Don't adjust in the first few tics of play, as + // we don't have an accurate value for average_latency yet. + + if (seq > TICRATE) + { + adjustment = (cmd->latency * FRACUNIT) - average_latency; + + // Only adjust very slightly; the cumulative effect over + // multiple tics will sort it out. + + adjustment = adjustment / 100; + + offsetms += adjustment; + } + + // Expand tic diffs for all players + + for (i=0; iplayeringame[i]) + { + net_ticdiff_t *diff; + + diff = &cmd->cmds[i]; + + // Use the ticcmd diff to patch the previous ticcmd to + // the new ticcmd + + NET_TiccmdPatch(&recvwindow_cmd_base[i], diff, &ticcmds[i]); + + // Store a copy for next time + + recvwindow_cmd_base[i] = ticcmds[i]; + } + } +} + +// Advance the receive window + +static void NET_CL_AdvanceWindow(void) +{ + ticcmd_t ticcmds[NET_MAXPLAYERS]; + + while (recvwindow[0].active) + { + // Expand tic diff data into d_net.c structures + + NET_CL_ExpandFullTiccmd(&recvwindow[0].cmd, recvwindow_start, + ticcmds); + D_ReceiveTic(ticcmds, recvwindow[0].cmd.playeringame); + + // Advance the window + + memmove(recvwindow, recvwindow + 1, + sizeof(net_server_recv_t) * (BACKUPTICS - 1)); + memset(&recvwindow[BACKUPTICS-1], 0, sizeof(net_server_recv_t)); + + ++recvwindow_start; + + //printf("CL: advanced to %i\n", recvwindow_start); + } +} + +// Shut down the client code, etc. Invoked after a disconnect. + +static void NET_CL_Shutdown(void) +{ + if (net_client_connected) + { + net_client_connected = false; + + NET_FreeAddress(server_addr); + + // Shut down network module, etc. To do. + } +} + +void NET_CL_LaunchGame(void) +{ + NET_Conn_NewReliable(&client_connection, NET_PACKET_TYPE_LAUNCH); +} + +void NET_CL_StartGame(net_gamesettings_t *settings) +{ + net_packet_t *packet; + + // Start from a ticcmd of all zeros + + memset(&last_ticcmd, 0, sizeof(ticcmd_t)); + + // Send packet + + packet = NET_Conn_NewReliable(&client_connection, + NET_PACKET_TYPE_GAMESTART); + + NET_WriteSettings(packet, settings); +} + +static void NET_CL_SendGameDataACK(void) +{ + net_packet_t *packet; + + packet = NET_NewPacket(10); + + NET_WriteInt16(packet, NET_PACKET_TYPE_GAMEDATA_ACK); + NET_WriteInt8(packet, recvwindow_start & 0xff); + + NET_Conn_SendPacket(&client_connection, packet); + + NET_FreePacket(packet); + + need_to_acknowledge = false; +} + +static void NET_CL_SendTics(int start, int end) +{ + net_packet_t *packet; + int i; + + if (!net_client_connected) + { + // Disconnected from server + + return; + } + + if (start < 0) + start = 0; + + // Build a new packet to send to the server + + packet = NET_NewPacket(512); + NET_WriteInt16(packet, NET_PACKET_TYPE_GAMEDATA); + + // Write the start tic and number of tics. Send only the low byte + // of start - it can be inferred by the server. + + NET_WriteInt8(packet, recvwindow_start & 0xff); + NET_WriteInt8(packet, start & 0xff); + NET_WriteInt8(packet, end - start + 1); + + // Add the tics. + + for (i=start; i<=end; ++i) + { + net_server_send_t *sendobj; + + sendobj = &send_queue[i % BACKUPTICS]; + + NET_WriteInt16(packet, average_latency / FRACUNIT); + + NET_WriteTiccmdDiff(packet, &sendobj->cmd, settings.lowres_turn); + } + + // Send the packet + + NET_Conn_SendPacket(&client_connection, packet); + + // All done! + + NET_FreePacket(packet); + + // Acknowledgement has been sent as part of the packet + + need_to_acknowledge = false; +} + +// Add a new ticcmd to the send queue + +void NET_CL_SendTiccmd(ticcmd_t *ticcmd, int maketic) +{ + net_ticdiff_t diff; + net_server_send_t *sendobj; + int starttic, endtic; + + // Calculate the difference to the last ticcmd + + NET_TiccmdDiff(&last_ticcmd, ticcmd, &diff); + + // Store in the send queue + + sendobj = &send_queue[maketic % BACKUPTICS]; + sendobj->active = true; + sendobj->seq = maketic; + sendobj->time = I_GetTimeMS(); + sendobj->cmd = diff; + + last_ticcmd = *ticcmd; + + // Send to server. + + starttic = maketic - settings.extratics; + endtic = maketic; + + if (starttic < 0) + starttic = 0; + + NET_CL_SendTics(starttic, endtic); +} + +// data received while we are waiting for the game to start + +static void NET_CL_ParseWaitingData(net_packet_t *packet) +{ + net_waitdata_t wait_data; + + if (!NET_ReadWaitData(packet, &wait_data)) + { + // Invalid packet? + return; + } + + if (wait_data.num_players > wait_data.max_players + || wait_data.ready_players > wait_data.num_players + || wait_data.max_players > NET_MAXPLAYERS) + { + // insane data + + return; + } + + if ((wait_data.consoleplayer >= 0 && drone) + || (wait_data.consoleplayer < 0 && !drone) + || (wait_data.consoleplayer >= wait_data.num_players)) + { + // Invalid player number + + return; + } + + memcpy(&net_client_wait_data, &wait_data, sizeof(net_waitdata_t)); + net_client_received_wait_data = true; +} + +static void NET_CL_ParseLaunch(net_packet_t *packet) +{ + unsigned int num_players; + + if (client_state != CLIENT_STATE_WAITING_LAUNCH) + { + return; + } + + // The launch packet contains the number of players that will be + // in the game when it starts, so that we can do the startup + // progress indicator (the wait data is unreliable). + + if (!NET_ReadInt8(packet, &num_players)) + { + return; + } + + net_client_wait_data.num_players = num_players; + client_state = CLIENT_STATE_WAITING_START; +} + +static void NET_CL_ParseGameStart(net_packet_t *packet) +{ + if (!NET_ReadSettings(packet, &settings)) + { + return; + } + + if (client_state != CLIENT_STATE_WAITING_START) + { + return; + } + + if (settings.num_players > NET_MAXPLAYERS + || settings.consoleplayer >= (signed int) settings.num_players) + { + // insane values + return; + } + + if ((drone && settings.consoleplayer >= 0) + || (!drone && settings.consoleplayer < 0)) + { + // Invalid player number: must be positive for real players, + // negative for drones + + return; + } + + client_state = CLIENT_STATE_IN_GAME; + + // Clear the receive window + + memset(recvwindow, 0, sizeof(recvwindow)); + recvwindow_start = 0; + memset(&recvwindow_cmd_base, 0, sizeof(recvwindow_cmd_base)); + + // Clear the send queue + + memset(&send_queue, 0x00, sizeof(send_queue)); +} + +static void NET_CL_SendResendRequest(int start, int end) +{ + net_packet_t *packet; + unsigned int nowtime; + int i; + + //printf("CL: Send resend %i-%i\n", start, end); + + packet = NET_NewPacket(64); + NET_WriteInt16(packet, NET_PACKET_TYPE_GAMEDATA_RESEND); + NET_WriteInt32(packet, start); + NET_WriteInt8(packet, end - start + 1); + NET_Conn_SendPacket(&client_connection, packet); + NET_FreePacket(packet); + + nowtime = I_GetTimeMS(); + + // Save the time we sent the resend request + + for (i=start; i<=end; ++i) + { + int index; + + index = i - recvwindow_start; + + if (index < 0 || index >= BACKUPTICS) + continue; + + recvwindow[index].resend_time = nowtime; + } +} + +// Check for expired resend requests + +static void NET_CL_CheckResends(void) +{ + int i; + int resend_start, resend_end; + unsigned int nowtime; + + nowtime = I_GetTimeMS(); + + resend_start = -1; + resend_end = -1; + + for (i=0; iactive + && recvobj->resend_time != 0 + && nowtime > recvobj->resend_time + 300; + + if (need_resend) + { + // Start a new run of resend tics? + + if (resend_start < 0) + { + resend_start = i; + } + + resend_end = i; + } + else + { + if (resend_start >= 0) + { + // End of a run of resend tics + + //printf("CL: resend request timed out: %i-%i\n", resend_start, resend_end); + NET_CL_SendResendRequest(recvwindow_start + resend_start, + recvwindow_start + resend_end); + + resend_start = -1; + } + } + } + + if (resend_start >= 0) + { + //printf("CL: resend request timed out: %i-%i\n", resend_start, resend_end); + NET_CL_SendResendRequest(recvwindow_start + resend_start, + recvwindow_start + resend_end); + } + + // We have received some data from the server and not acknowledged + // it yet. Normally this gets acknowledged when we send our game + // data, but if the client is a drone we need to do this. + + if (need_to_acknowledge && nowtime - gamedata_recv_time > 200) + { + NET_CL_SendGameDataACK(); + } +} + + +// Parsing of NET_PACKET_TYPE_GAMEDATA packets +// (packets containing the actual ticcmd data) + +static void NET_CL_ParseGameData(net_packet_t *packet) +{ + net_server_recv_t *recvobj; + unsigned int seq, num_tics; + unsigned int nowtime; + int resend_start, resend_end; + size_t i; + int index; + + // Read header + + if (!NET_ReadInt8(packet, &seq) + || !NET_ReadInt8(packet, &num_tics)) + { + return; + } + + nowtime = I_GetTimeMS(); + + // Whatever happens, we now need to send an acknowledgement of our + // current receive point. + + if (!need_to_acknowledge) + { + need_to_acknowledge = true; + gamedata_recv_time = nowtime; + } + + // Expand byte value into the full tic number + + seq = NET_CL_ExpandTicNum(seq); + + for (i=0; i= BACKUPTICS) + { + // Out of range of the recv window + + continue; + } + + // Store in the receive window + + recvobj = &recvwindow[index]; + + recvobj->active = true; + recvobj->cmd = cmd; + } + + // Has this been received out of sequence, ie. have we not received + // all tics before the first tic in this packet? If so, send a + // resend request. + + //printf("CL: %p: %i\n", client, seq); + + resend_end = seq - recvwindow_start; + + if (resend_end <= 0) + return; + + if (resend_end >= BACKUPTICS) + resend_end = BACKUPTICS - 1; + + index = resend_end - 1; + resend_start = resend_end; + + while (index >= 0) + { + recvobj = &recvwindow[index]; + + if (recvobj->active) + { + // ended our run of unreceived tics + + break; + } + + if (recvobj->resend_time != 0) + { + // Already sent a resend request for this tic + + break; + } + + resend_start = index; + --index; + } + + // Possibly send a resend request + + if (resend_start < resend_end) + { + NET_CL_SendResendRequest(recvwindow_start + resend_start, + recvwindow_start + resend_end - 1); + } +} + +// Parse a resend request from the server due to a dropped packet + +static void NET_CL_ParseResendRequest(net_packet_t *packet) +{ + static unsigned int start; + static unsigned int end; + static unsigned int num_tics; + + if (drone) + { + // Drones don't send gamedata. + + return; + } + + if (!NET_ReadInt32(packet, &start) + || !NET_ReadInt8(packet, &num_tics)) + { + return; + } + + end = start + num_tics - 1; + + //printf("requested resend %i-%i .. ", start, end); + + // Check we have the tics being requested. If not, reduce the + // window of tics to only what we have. + + while (start <= end + && (!send_queue[start % BACKUPTICS].active + || send_queue[start % BACKUPTICS].seq != start)) + { + ++start; + } + + while (start <= end + && (!send_queue[end % BACKUPTICS].active + || send_queue[end % BACKUPTICS].seq != end)) + { + --end; + } + + //printf("%i-%i\n", start, end); + + // Resend those tics + + if (start <= end) + { + //printf("CL: resend %i-%i\n", start, start+num_tics-1); + + NET_CL_SendTics(start, end); + } +} + +// Console message that the server wants the client to print + +static void NET_CL_ParseConsoleMessage(net_packet_t *packet) +{ + char *msg; + + msg = NET_ReadString(packet); + + if (msg == NULL) + { + return; + } + + printf("Message from server: "); + + NET_SafePuts(msg); +} + +// parse a received packet + +static void NET_CL_ParsePacket(net_packet_t *packet) +{ + unsigned int packet_type; + + if (!NET_ReadInt16(packet, &packet_type)) + { + return; + } + + if (NET_Conn_Packet(&client_connection, packet, &packet_type)) + { + // Packet eaten by the common connection code + } + else + { + switch (packet_type) + { + case NET_PACKET_TYPE_WAITING_DATA: + NET_CL_ParseWaitingData(packet); + break; + + case NET_PACKET_TYPE_LAUNCH: + NET_CL_ParseLaunch(packet); + break; + + case NET_PACKET_TYPE_GAMESTART: + NET_CL_ParseGameStart(packet); + break; + + case NET_PACKET_TYPE_GAMEDATA: + NET_CL_ParseGameData(packet); + break; + + case NET_PACKET_TYPE_GAMEDATA_RESEND: + NET_CL_ParseResendRequest(packet); + break; + + case NET_PACKET_TYPE_CONSOLE_MESSAGE: + NET_CL_ParseConsoleMessage(packet); + break; + + default: + break; + } + } +} + +// "Run" the client code: check for new packets, send packets as +// needed + +void NET_CL_Run(void) +{ + net_addr_t *addr; + net_packet_t *packet; + + if (!net_client_connected) + { + return; + } + + while (NET_RecvPacket(client_context, &addr, &packet)) + { + // only accept packets from the server + + if (addr == server_addr) + { + NET_CL_ParsePacket(packet); + } + else + { + NET_FreeAddress(addr); + } + + NET_FreePacket(packet); + } + + // Run the common connection code to send any packets as needed + + NET_Conn_Run(&client_connection); + + if (client_connection.state == NET_CONN_STATE_DISCONNECTED + || client_connection.state == NET_CONN_STATE_DISCONNECTED_SLEEP) + { + NET_CL_Disconnected(); + + NET_CL_Shutdown(); + } + + net_waiting_for_launch = + client_connection.state == NET_CONN_STATE_CONNECTED + && client_state == CLIENT_STATE_WAITING_LAUNCH; + + if (client_state == CLIENT_STATE_IN_GAME) + { + // Possibly advance the receive window + + NET_CL_AdvanceWindow(); + + // Check if our resend requests have timed out + + NET_CL_CheckResends(); + } +} + +static void NET_CL_SendSYN(net_connect_data_t *data) +{ + net_packet_t *packet; + + packet = NET_NewPacket(10); + NET_WriteInt16(packet, NET_PACKET_TYPE_SYN); + NET_WriteInt32(packet, NET_MAGIC_NUMBER); + NET_WriteString(packet, "Chocolate Doom 3"); + NET_WriteConnectData(packet, data); + NET_WriteString(packet, net_player_name); + NET_Conn_SendPacket(&client_connection, packet); + NET_FreePacket(packet); +} + +// connect to a server + +boolean NET_CL_Connect(net_addr_t *addr, net_connect_data_t *data) +{ + int start_time; + int last_send_time; + + server_addr = addr; + + memcpy(net_local_wad_sha1sum, data->wad_sha1sum, sizeof(sha1_digest_t)); + net_local_is_freedoom = data->is_freedoom; + + // create a new network I/O context and add just the + // necessary module + + client_context = NET_NewContext(); + + // initialize module for client mode + + if (!addr->module->InitClient()) + { + return false; + } + + NET_AddModule(client_context, addr->module); + + net_client_connected = true; + net_client_received_wait_data = false; + + // Initialize connection + + NET_Conn_InitClient(&client_connection, addr); + + // try to connect + + start_time = I_GetTimeMS(); + last_send_time = -1; + + while (client_connection.state == NET_CONN_STATE_CONNECTING) + { + int nowtime = I_GetTimeMS(); + + // Send a SYN packet every second. + + if (nowtime - last_send_time > 1000 || last_send_time < 0) + { + NET_CL_SendSYN(data); + last_send_time = nowtime; + } + + // time out after 5 seconds + + if (nowtime - start_time > 5000) + { + break; + } + + // run client code + + NET_CL_Run(); + + // run the server, just incase we are doing a loopback + // connect + + NET_SV_Run(); + + // Don't hog the CPU + + I_Sleep(1); + } + + if (client_connection.state == NET_CONN_STATE_CONNECTED) + { + // connected ok! + + client_state = CLIENT_STATE_WAITING_LAUNCH; + drone = data->drone; + + return true; + } + else + { + // failed to connect + + NET_CL_Shutdown(); + + return false; + } +} + +// read game settings received from server + +boolean NET_CL_GetSettings(net_gamesettings_t *_settings) +{ + if (client_state != CLIENT_STATE_IN_GAME) + { + return false; + } + + memcpy(_settings, &settings, sizeof(net_gamesettings_t)); + + return true; +} + +// disconnect from the server + +void NET_CL_Disconnect(void) +{ + int start_time; + + if (!net_client_connected) + { + return; + } + + NET_Conn_Disconnect(&client_connection); + + start_time = I_GetTimeMS(); + + while (client_connection.state != NET_CONN_STATE_DISCONNECTED + && client_connection.state != NET_CONN_STATE_DISCONNECTED_SLEEP) + { + if (I_GetTimeMS() - start_time > 5000) + { + // time out after 5 seconds + + client_state = CLIENT_STATE_WAITING_START; + + fprintf(stderr, "NET_CL_Disconnect: Timeout while disconnecting from server\n"); + break; + } + + NET_CL_Run(); + NET_SV_Run(); + + I_Sleep(1); + } + + // Finished sending disconnect packets, etc. + + NET_CL_Shutdown(); +} + +void NET_CL_Init(void) +{ + // Try to set from the USER and USERNAME environment variables + // Otherwise, fallback to "Player" + + if (net_player_name == NULL) + net_player_name = getenv("USER"); + if (net_player_name == NULL) + net_player_name = getenv("USERNAME"); + + if (net_player_name == NULL) + net_player_name = "Player"; +} + +void NET_Init(void) +{ + NET_CL_Init(); +} + +void NET_BindVariables(void) +{ + M_BindStringVariable("player_name", &net_player_name); +} diff --git a/client/src/net_client.h b/client/src/net_client.h new file mode 100644 index 0000000..96b8650 --- /dev/null +++ b/client/src/net_client.h @@ -0,0 +1,50 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// Network client code +// + +#ifndef NET_CLIENT_H +#define NET_CLIENT_H + +#include "doomtype.h" +#include "d_ticcmd.h" +#include "sha1.h" +#include "net_defs.h" + +boolean NET_CL_Connect(net_addr_t *addr, net_connect_data_t *data); +void NET_CL_Disconnect(void); +void NET_CL_Run(void); +void NET_CL_Init(void); +void NET_CL_LaunchGame(void); +void NET_CL_StartGame(net_gamesettings_t *settings); +void NET_CL_SendTiccmd(ticcmd_t *ticcmd, int maketic); +boolean NET_CL_GetSettings(net_gamesettings_t *_settings); +void NET_Init(void); + +void NET_BindVariables(void); + +extern boolean net_client_connected; +extern boolean net_client_received_wait_data; +extern net_waitdata_t net_client_wait_data; +extern boolean net_waiting_for_launch; +extern char *net_player_name; + +extern sha1_digest_t net_server_wad_sha1sum; +extern unsigned int net_server_is_freedoom; +extern sha1_digest_t net_local_wad_sha1sum; +extern unsigned int net_local_is_freedoom; + +extern boolean drone; + +#endif /* #ifndef NET_CLIENT_H */ diff --git a/client/src/net_common.c b/client/src/net_common.c new file mode 100644 index 0000000..7027d13 --- /dev/null +++ b/client/src/net_common.c @@ -0,0 +1,534 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// Common code shared between the client and server +// + +#include +#include + +#include "doomtype.h" +#include "d_mode.h" +#include "i_timer.h" + +#include "net_common.h" +#include "net_io.h" +#include "net_packet.h" +#include "net_structrw.h" + +// connections time out after 30 seconds + +#define CONNECTION_TIMEOUT_LEN 30 + +// maximum time between sending packets + +#define KEEPALIVE_PERIOD 1 + +// reliable packet that is guaranteed to reach its destination + +struct net_reliable_packet_s +{ + net_packet_t *packet; + int last_send_time; + int seq; + net_reliable_packet_t *next; +}; + +static void NET_Conn_Init(net_connection_t *conn, net_addr_t *addr) +{ + conn->last_send_time = -1; + conn->num_retries = 0; + conn->addr = addr; + conn->reliable_packets = NULL; + conn->reliable_send_seq = 0; + conn->reliable_recv_seq = 0; +} + +// Initialize as a client connection + +void NET_Conn_InitClient(net_connection_t *conn, net_addr_t *addr) +{ + NET_Conn_Init(conn, addr); + conn->state = NET_CONN_STATE_CONNECTING; +} + +// Initialize as a server connection + +void NET_Conn_InitServer(net_connection_t *conn, net_addr_t *addr) +{ + NET_Conn_Init(conn, addr); + conn->state = NET_CONN_STATE_WAITING_ACK; +} + +// Send a packet to a connection +// All packets should be sent through this interface, as it maintains the +// keepalive_send_time counter. + +void NET_Conn_SendPacket(net_connection_t *conn, net_packet_t *packet) +{ + conn->keepalive_send_time = I_GetTimeMS(); + NET_SendPacket(conn->addr, packet); +} + +// parse an ACK packet from a client + +static void NET_Conn_ParseACK(net_connection_t *conn, net_packet_t *packet) +{ + net_packet_t *reply; + + if (conn->state == NET_CONN_STATE_CONNECTING) + { + // We are a client + + // received a response from the server to our SYN + + conn->state = NET_CONN_STATE_CONNECTED; + + // We must send an ACK reply to the server's ACK + + reply = NET_NewPacket(10); + NET_WriteInt16(reply, NET_PACKET_TYPE_ACK); + NET_Conn_SendPacket(conn, reply); + NET_FreePacket(reply); + } + + if (conn->state == NET_CONN_STATE_WAITING_ACK) + { + // We are a server + + // Client is connected + + conn->state = NET_CONN_STATE_CONNECTED; + } +} + +static void NET_Conn_ParseDisconnect(net_connection_t *conn, net_packet_t *packet) +{ + net_packet_t *reply; + + // Other end wants to disconnect + // Send a DISCONNECT_ACK reply. + + reply = NET_NewPacket(10); + NET_WriteInt16(reply, NET_PACKET_TYPE_DISCONNECT_ACK); + NET_Conn_SendPacket(conn, reply); + NET_FreePacket(reply); + + conn->last_send_time = I_GetTimeMS(); + + conn->state = NET_CONN_STATE_DISCONNECTED_SLEEP; + conn->disconnect_reason = NET_DISCONNECT_REMOTE; +} + +// Parse a DISCONNECT_ACK packet + +static void NET_Conn_ParseDisconnectACK(net_connection_t *conn, + net_packet_t *packet) +{ + + if (conn->state == NET_CONN_STATE_DISCONNECTING) + { + // We have received an acknowledgement to our disconnect + // request. We have been disconnected successfully. + + conn->state = NET_CONN_STATE_DISCONNECTED; + conn->disconnect_reason = NET_DISCONNECT_LOCAL; + conn->last_send_time = -1; + } +} + +static void NET_Conn_ParseReject(net_connection_t *conn, net_packet_t *packet) +{ + char *msg; + + msg = NET_ReadString(packet); + + if (msg == NULL) + { + return; + } + + if (conn->state == NET_CONN_STATE_CONNECTING) + { + // rejected by server + + conn->state = NET_CONN_STATE_DISCONNECTED; + conn->disconnect_reason = NET_DISCONNECT_REMOTE; + + printf("Rejected by server: "); + NET_SafePuts(msg); + } +} + +static void NET_Conn_ParseReliableACK(net_connection_t *conn, net_packet_t *packet) +{ + unsigned int seq; + + if (!NET_ReadInt8(packet, &seq)) + { + return; + } + + if (conn->reliable_packets == NULL) + { + return; + } + + // Is this an acknowledgement for the first packet in the list? + + if (seq == (unsigned int)((conn->reliable_packets->seq + 1) & 0xff)) + { + net_reliable_packet_t *rp; + + // Discard it, then. + // Unlink from the list. + + rp = conn->reliable_packets; + conn->reliable_packets = rp->next; + + NET_FreePacket(rp->packet); + free(rp); + } +} + +// Process the header of a reliable packet +// +// Returns true if the packet should be discarded (incorrect sequence) + +static boolean NET_Conn_ReliablePacket(net_connection_t *conn, + net_packet_t *packet) +{ + unsigned int seq; + net_packet_t *reply; + boolean result; + + // Read the sequence number + + if (!NET_ReadInt8(packet, &seq)) + { + return true; + } + + if (seq != (unsigned int)(conn->reliable_recv_seq & 0xff)) + { + // This is not the next expected packet in the sequence! + // + // Discard the packet. If we were smart, we would use a proper + // sliding window protocol to do this, but I'm lazy. + + result = true; + } + else + { + // Now we can receive the next packet in the sequence. + + conn->reliable_recv_seq = (conn->reliable_recv_seq + 1) & 0xff; + + result = false; + } + + // Send an acknowledgement + + // Note: this is braindead. It would be much more sensible to + // include this in the next packet, rather than the overhead of + // sending a complete packet just for one byte of information. + + reply = NET_NewPacket(10); + + NET_WriteInt16(reply, NET_PACKET_TYPE_RELIABLE_ACK); + NET_WriteInt8(reply, conn->reliable_recv_seq & 0xff); + + NET_Conn_SendPacket(conn, reply); + + NET_FreePacket(reply); + + return result; +} + +// Process a packet received by the server +// +// Returns true if eaten by common code + +boolean NET_Conn_Packet(net_connection_t *conn, net_packet_t *packet, + unsigned int *packet_type) +{ + conn->keepalive_recv_time = I_GetTimeMS(); + + // Is this a reliable packet? + + if (*packet_type & NET_RELIABLE_PACKET) + { + if (NET_Conn_ReliablePacket(conn, packet)) + { + // Invalid packet: eat it. + + return true; + } + + // Remove the reliable bit + + *packet_type &= ~NET_RELIABLE_PACKET; + } + + switch (*packet_type) + { + case NET_PACKET_TYPE_ACK: + NET_Conn_ParseACK(conn, packet); + break; + case NET_PACKET_TYPE_DISCONNECT: + NET_Conn_ParseDisconnect(conn, packet); + break; + case NET_PACKET_TYPE_DISCONNECT_ACK: + NET_Conn_ParseDisconnectACK(conn, packet); + break; + case NET_PACKET_TYPE_KEEPALIVE: + // No special action needed. + break; + case NET_PACKET_TYPE_REJECTED: + NET_Conn_ParseReject(conn, packet); + break; + case NET_PACKET_TYPE_RELIABLE_ACK: + NET_Conn_ParseReliableACK(conn, packet); + break; + default: + // Not a common packet + + return false; + } + + // We found a packet that we found interesting, and ate it. + + return true; +} + +void NET_Conn_Disconnect(net_connection_t *conn) +{ + if (conn->state != NET_CONN_STATE_DISCONNECTED + && conn->state != NET_CONN_STATE_DISCONNECTING + && conn->state != NET_CONN_STATE_DISCONNECTED_SLEEP) + { + conn->state = NET_CONN_STATE_DISCONNECTING; + conn->disconnect_reason = NET_DISCONNECT_LOCAL; + conn->last_send_time = -1; + conn->num_retries = 0; + } +} + +void NET_Conn_Run(net_connection_t *conn) +{ + net_packet_t *packet; + unsigned int nowtime; + + nowtime = I_GetTimeMS(); + + if (conn->state == NET_CONN_STATE_CONNECTED) + { + // Check the keepalive counters + + if (nowtime - conn->keepalive_recv_time > CONNECTION_TIMEOUT_LEN * 1000) + { + // Haven't received any packets from the other end in a long + // time. Assume disconnected. + + conn->state = NET_CONN_STATE_DISCONNECTED; + conn->disconnect_reason = NET_DISCONNECT_TIMEOUT; + } + + if (nowtime - conn->keepalive_send_time > KEEPALIVE_PERIOD * 1000) + { + // We have not sent anything in a long time. + // Send a keepalive. + + packet = NET_NewPacket(10); + NET_WriteInt16(packet, NET_PACKET_TYPE_KEEPALIVE); + NET_Conn_SendPacket(conn, packet); + NET_FreePacket(packet); + } + + // Check the reliable packet list. Has the first packet in the + // list timed out? + // + // NB. This is braindead, we have a fixed time of one second. + + if (conn->reliable_packets != NULL + && (conn->reliable_packets->last_send_time < 0 + || nowtime - conn->reliable_packets->last_send_time > 1000)) + { + // Packet timed out, time to resend + + NET_Conn_SendPacket(conn, conn->reliable_packets->packet); + conn->reliable_packets->last_send_time = nowtime; + } + } + else if (conn->state == NET_CONN_STATE_WAITING_ACK) + { + if (conn->last_send_time < 0 + || nowtime - conn->last_send_time > 1000) + { + // it has been a second since the last ACK was sent, and + // still no reply. + + if (conn->num_retries < MAX_RETRIES) + { + // send another ACK + + packet = NET_NewPacket(10); + NET_WriteInt16(packet, NET_PACKET_TYPE_ACK); + NET_Conn_SendPacket(conn, packet); + NET_FreePacket(packet); + conn->last_send_time = nowtime; + + ++conn->num_retries; + } + else + { + // no more retries allowed. + + conn->state = NET_CONN_STATE_DISCONNECTED; + conn->disconnect_reason = NET_DISCONNECT_TIMEOUT; + } + } + } + else if (conn->state == NET_CONN_STATE_DISCONNECTING) + { + // Waiting for a reply to our DISCONNECT request. + + if (conn->last_send_time < 0 + || nowtime - conn->last_send_time > 1000) + { + // it has been a second since the last disconnect packet + // was sent, and still no reply. + + if (conn->num_retries < MAX_RETRIES) + { + // send another disconnect + + packet = NET_NewPacket(10); + NET_WriteInt16(packet, NET_PACKET_TYPE_DISCONNECT); + NET_Conn_SendPacket(conn, packet); + NET_FreePacket(packet); + conn->last_send_time = nowtime; + + ++conn->num_retries; + } + else + { + // No more retries allowed. + // Force disconnect. + + conn->state = NET_CONN_STATE_DISCONNECTED; + conn->disconnect_reason = NET_DISCONNECT_LOCAL; + } + } + } + else if (conn->state == NET_CONN_STATE_DISCONNECTED_SLEEP) + { + // We are disconnected, waiting in case we need to send + // a DISCONNECT_ACK to the server again. + + if (nowtime - conn->last_send_time > 5000) + { + // Idle for 5 seconds, switch state + + conn->state = NET_CONN_STATE_DISCONNECTED; + conn->disconnect_reason = NET_DISCONNECT_REMOTE; + } + } +} + +net_packet_t *NET_Conn_NewReliable(net_connection_t *conn, int packet_type) +{ + net_packet_t *packet; + net_reliable_packet_t *rp; + net_reliable_packet_t **listend; + + // Generate a packet with the right header + + packet = NET_NewPacket(100); + + NET_WriteInt16(packet, packet_type | NET_RELIABLE_PACKET); + + // write the low byte of the send sequence number + + NET_WriteInt8(packet, conn->reliable_send_seq & 0xff); + + // Add to the list of reliable packets + + rp = malloc(sizeof(net_reliable_packet_t)); + rp->packet = packet; + rp->next = NULL; + rp->seq = conn->reliable_send_seq; + rp->last_send_time = -1; + + for (listend = &conn->reliable_packets; + *listend != NULL; + listend = &((*listend)->next)); + + *listend = rp; + + // Count along the sequence + + conn->reliable_send_seq = (conn->reliable_send_seq + 1) & 0xff; + + // Finished + + return packet; +} + +// Used to expand the least significant byte of a tic number into +// the full tic number, from the current tic number + +unsigned int NET_ExpandTicNum(unsigned int relative, unsigned int b) +{ + unsigned int l, h; + unsigned int result; + + h = relative & ~0xff; + l = relative & 0xff; + + result = h | b; + + if (l < 0x40 && b > 0xb0) + result -= 0x100; + if (l > 0xb0 && b < 0x40) + result += 0x100; + + return result; +} + +// Check that game settings are valid + +boolean NET_ValidGameSettings(GameMode_t mode, GameMission_t mission, + net_gamesettings_t *settings) +{ + if (settings->ticdup <= 0) + return false; + + if (settings->extratics < 0) + return false; + + if (settings->deathmatch < 0 || settings->deathmatch > 2) + return false; + + if (settings->skill < sk_noitems || settings->skill > sk_nightmare) + return false; + + if (!D_ValidGameVersion(mission, settings->gameversion)) + return false; + + if (!D_ValidEpisodeMap(mission, mode, settings->episode, settings->map)) + return false; + + return true; +} + diff --git a/client/src/net_common.h b/client/src/net_common.h new file mode 100644 index 0000000..6151b48 --- /dev/null +++ b/client/src/net_common.h @@ -0,0 +1,111 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// Common code shared between the client and server +// + +#ifndef NET_COMMON_H +#define NET_COMMON_H + +#include "d_mode.h" +#include "doomtype.h" +#include "net_defs.h" + +typedef enum +{ + // sending syn packets, waiting for an ACK reply + // (client side) + + NET_CONN_STATE_CONNECTING, + + // received a syn, sent an ack, waiting for an ack reply + // (server side) + + NET_CONN_STATE_WAITING_ACK, + + // successfully connected + + NET_CONN_STATE_CONNECTED, + + // sent a DISCONNECT packet, waiting for a DISCONNECT_ACK reply + + NET_CONN_STATE_DISCONNECTING, + + // client successfully disconnected + + NET_CONN_STATE_DISCONNECTED, + + // We are disconnected, but in a sleep state, waiting for several + // seconds. This is in case the DISCONNECT_ACK we sent failed + // to arrive, and we need to send another one. We keep this as + // a valid connection for a few seconds until we are sure that + // the other end has successfully disconnected as well. + + NET_CONN_STATE_DISCONNECTED_SLEEP, + +} net_connstate_t; + +// Reason a connection was terminated + +typedef enum +{ + // As the result of a local disconnect request + + NET_DISCONNECT_LOCAL, + + // As the result of a remote disconnect request + + NET_DISCONNECT_REMOTE, + + // Timeout (no data received in a long time) + + NET_DISCONNECT_TIMEOUT, + +} net_disconnect_reason_t; + +#define MAX_RETRIES 5 + +typedef struct net_reliable_packet_s net_reliable_packet_t; + +typedef struct +{ + net_connstate_t state; + net_disconnect_reason_t disconnect_reason; + net_addr_t *addr; + int last_send_time; + int num_retries; + int keepalive_send_time; + int keepalive_recv_time; + net_reliable_packet_t *reliable_packets; + int reliable_send_seq; + int reliable_recv_seq; +} net_connection_t; + + +void NET_Conn_SendPacket(net_connection_t *conn, net_packet_t *packet); +void NET_Conn_InitClient(net_connection_t *conn, net_addr_t *addr); +void NET_Conn_InitServer(net_connection_t *conn, net_addr_t *addr); +boolean NET_Conn_Packet(net_connection_t *conn, net_packet_t *packet, + unsigned int *packet_type); +void NET_Conn_Disconnect(net_connection_t *conn); +void NET_Conn_Run(net_connection_t *conn); +net_packet_t *NET_Conn_NewReliable(net_connection_t *conn, int packet_type); + +// Other miscellaneous common functions + +unsigned int NET_ExpandTicNum(unsigned int relative, unsigned int b); +boolean NET_ValidGameSettings(GameMode_t mode, GameMission_t mission, + net_gamesettings_t *settings); + +#endif /* #ifndef NET_COMMON_H */ + diff --git a/client/src/net_dedicated.c b/client/src/net_dedicated.c new file mode 100644 index 0000000..8c94f23 --- /dev/null +++ b/client/src/net_dedicated.c @@ -0,0 +1,73 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// +// Dedicated server code. +// + +#include + +#include "doomtype.h" +#include "i_system.h" +#include "i_timer.h" +#include "m_argv.h" +#include "net_sdl.h" +#include "net_server.h" + +// +// People can become confused about how dedicated servers work. Game +// options are specified to the controlling player who is the first to +// join a game. Bomb out with an error message if game options are +// specified to a dedicated server. +// + +static char *not_dedicated_options[] = +{ + "-iwad", "-gameversion", "-nomonsters", "-respawn", + "-fast", "-altdeath", "-deathmatch", "-turbo", "-merge", "-af", "-as", + "-aa", "-file", "-wart", "-skill", "-episode", "-timer", "-avg", "-warp", + "-loadgame", "-longtics", "-extratics", "-dup", NULL, +}; + +static void CheckForClientOptions(void) +{ + int i; + + for (i=0; not_dedicated_options[i] != NULL; ++i) + { + if (M_CheckParm(not_dedicated_options[i]) > 0) + { + I_Error("The command line parameter '%s' was specified to a " + "dedicated server.\nGame parameters should be specified " + "to the first player to join a server, \nnot to the " + "server itself. ", + not_dedicated_options[i]); + } + } +} + +void NET_DedicatedServer(void) +{ + CheckForClientOptions(); + + NET_SV_Init(); + NET_SV_AddModule(&net_sdl_module); + NET_SV_RegisterWithMaster(); + + while (true) + { + NET_SV_Run(); + I_Sleep(10); + } +} + diff --git a/client/src/net_dedicated.h b/client/src/net_dedicated.h new file mode 100644 index 0000000..3d7387b --- /dev/null +++ b/client/src/net_dedicated.h @@ -0,0 +1,25 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// +// Dedicated server code. +// + +#ifndef NET_DEDICATED_H +#define NET_DEDICATED_H + +void NET_DedicatedServer(void); + +#endif /* #ifndef NET_DEDICATED_H */ + + diff --git a/client/src/net_defs.h b/client/src/net_defs.h new file mode 100644 index 0000000..ff295af --- /dev/null +++ b/client/src/net_defs.h @@ -0,0 +1,246 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Definitions for use in networking code. +// + +#ifndef NET_DEFS_H +#define NET_DEFS_H + +#include + +#include "doomtype.h" +#include "d_ticcmd.h" +#include "sha1.h" + +// Absolute maximum number of "nodes" in the game. This is different to +// NET_MAXPLAYERS, as there may be observers that are not participating +// (eg. left/right monitors) + +#define MAXNETNODES 16 + +// The maximum number of players, multiplayer/networking. +// This is the maximum supported by the networking code; individual games +// have their own values for MAXPLAYERS that can be smaller. + +#define NET_MAXPLAYERS 8 + +// Maximum length of a player's name. + +#define MAXPLAYERNAME 30 + +// Networking and tick handling related. + +#define BACKUPTICS 128 + +typedef struct _net_module_s net_module_t; +typedef struct _net_packet_s net_packet_t; +typedef struct _net_addr_s net_addr_t; +typedef struct _net_context_s net_context_t; + +struct _net_packet_s +{ + byte *data; + size_t len; + size_t alloced; + unsigned int pos; +}; + +struct _net_module_s +{ + // Initialize this module for use as a client + + boolean (*InitClient)(void); + + // Initialize this module for use as a server + + boolean (*InitServer)(void); + + // Send a packet + + void (*SendPacket)(net_addr_t *addr, net_packet_t *packet); + + // Check for new packets to receive + // + // Returns true if packet received + + boolean (*RecvPacket)(net_addr_t **addr, net_packet_t **packet); + + // Converts an address to a string + + void (*AddrToString)(net_addr_t *addr, char *buffer, int buffer_len); + + // Free back an address when no longer in use + + void (*FreeAddress)(net_addr_t *addr); + + // Try to resolve a name to an address + + net_addr_t *(*ResolveAddress)(char *addr); +}; + +// net_addr_t + +struct _net_addr_s +{ + net_module_t *module; + void *handle; +}; + +// magic number sent when connecting to check this is a valid client + +#define NET_MAGIC_NUMBER 3436803284U + +// header field value indicating that the packet is a reliable packet + +#define NET_RELIABLE_PACKET (1 << 15) + +// packet types + +typedef enum +{ + NET_PACKET_TYPE_SYN, + NET_PACKET_TYPE_ACK, + NET_PACKET_TYPE_REJECTED, + NET_PACKET_TYPE_KEEPALIVE, + NET_PACKET_TYPE_WAITING_DATA, + NET_PACKET_TYPE_GAMESTART, + NET_PACKET_TYPE_GAMEDATA, + NET_PACKET_TYPE_GAMEDATA_ACK, + NET_PACKET_TYPE_DISCONNECT, + NET_PACKET_TYPE_DISCONNECT_ACK, + NET_PACKET_TYPE_RELIABLE_ACK, + NET_PACKET_TYPE_GAMEDATA_RESEND, + NET_PACKET_TYPE_CONSOLE_MESSAGE, + NET_PACKET_TYPE_QUERY, + NET_PACKET_TYPE_QUERY_RESPONSE, + NET_PACKET_TYPE_LAUNCH, +} net_packet_type_t; + +typedef enum +{ + NET_MASTER_PACKET_TYPE_ADD, + NET_MASTER_PACKET_TYPE_ADD_RESPONSE, + NET_MASTER_PACKET_TYPE_QUERY, + NET_MASTER_PACKET_TYPE_QUERY_RESPONSE, + NET_MASTER_PACKET_TYPE_GET_METADATA, + NET_MASTER_PACKET_TYPE_GET_METADATA_RESPONSE, + NET_MASTER_PACKET_TYPE_SIGN_START, + NET_MASTER_PACKET_TYPE_SIGN_START_RESPONSE, + NET_MASTER_PACKET_TYPE_SIGN_END, + NET_MASTER_PACKET_TYPE_SIGN_END_RESPONSE, +} net_master_packet_type_t; + +// Settings specified when the client connects to the server. + +typedef struct +{ + int gamemode; + int gamemission; + int lowres_turn; + int drone; + int max_players; + int is_freedoom; + sha1_digest_t wad_sha1sum; + int player_class; +} net_connect_data_t; + +// Game settings sent by client to server when initiating game start, +// and received from the server by clients when the game starts. + +typedef struct +{ + int ticdup; + int extratics; + int deathmatch; + int episode; + int nomonsters; + int fast_monsters; + int respawn_monsters; + int map; + int skill; + int gameversion; + int lowres_turn; + int new_sync; + int timelimit; + int loadgame; + int random; // [Strife only] + + // These fields are only used by the server when sending a game + // start message: + + int num_players; + int consoleplayer; + + // Hexen player classes: + + int player_classes[NET_MAXPLAYERS]; + +} net_gamesettings_t; + +#define NET_TICDIFF_FORWARD (1 << 0) +#define NET_TICDIFF_SIDE (1 << 1) +#define NET_TICDIFF_TURN (1 << 2) +#define NET_TICDIFF_BUTTONS (1 << 3) +#define NET_TICDIFF_CONSISTANCY (1 << 4) +#define NET_TICDIFF_CHATCHAR (1 << 5) +#define NET_TICDIFF_RAVEN (1 << 6) +#define NET_TICDIFF_STRIFE (1 << 7) + +typedef struct +{ + unsigned int diff; + ticcmd_t cmd; +} net_ticdiff_t; + +// Complete set of ticcmds from all players + +typedef struct +{ + signed int latency; + unsigned int seq; + boolean playeringame[NET_MAXPLAYERS]; + net_ticdiff_t cmds[NET_MAXPLAYERS]; +} net_full_ticcmd_t; + +// Data sent in response to server queries + +typedef struct +{ + char *version; + int server_state; + int num_players; + int max_players; + int gamemode; + int gamemission; + char *description; +} net_querydata_t; + +// Data sent by the server while waiting for the game to start. + +typedef struct +{ + int num_players; + int num_drones; + int ready_players; + int max_players; + int is_controller; + int consoleplayer; + char player_names[NET_MAXPLAYERS][MAXPLAYERNAME]; + char player_addrs[NET_MAXPLAYERS][MAXPLAYERNAME]; + sha1_digest_t wad_sha1sum; + int is_freedoom; +} net_waitdata_t; + +#endif /* #ifndef NET_DEFS_H */ diff --git a/client/src/net_io.c b/client/src/net_io.c new file mode 100644 index 0000000..78f483a --- /dev/null +++ b/client/src/net_io.c @@ -0,0 +1,128 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Network packet I/O. Base layer for sending/receiving packets, +// through the network module system +// + +#include + +#include "i_system.h" +#include "net_defs.h" +#include "net_io.h" +#include "z_zone.h" + +#define MAX_MODULES 16 + +struct _net_context_s +{ + net_module_t *modules[MAX_MODULES]; + int num_modules; +}; + +net_addr_t net_broadcast_addr; + +net_context_t *NET_NewContext(void) +{ + net_context_t *context; + + context = Z_Malloc(sizeof(net_context_t), PU_STATIC, 0); + context->num_modules = 0; + + return context; +} + +void NET_AddModule(net_context_t *context, net_module_t *module) +{ + if (context->num_modules >= MAX_MODULES) + { + I_Error("NET_AddModule: No more modules for context"); + } + + context->modules[context->num_modules] = module; + ++context->num_modules; +} + +net_addr_t *NET_ResolveAddress(net_context_t *context, char *addr) +{ + int i; + net_addr_t *result; + + result = NULL; + + for (i=0; inum_modules; ++i) + { + result = context->modules[i]->ResolveAddress(addr); + + if (result != NULL) + { + break; + } + } + + return result; +} + +void NET_SendPacket(net_addr_t *addr, net_packet_t *packet) +{ + addr->module->SendPacket(addr, packet); +} + +void NET_SendBroadcast(net_context_t *context, net_packet_t *packet) +{ + int i; + + for (i=0; inum_modules; ++i) + { + context->modules[i]->SendPacket(&net_broadcast_addr, packet); + } +} + +boolean NET_RecvPacket(net_context_t *context, + net_addr_t **addr, + net_packet_t **packet) +{ + int i; + + // check all modules for new packets + + for (i=0; inum_modules; ++i) + { + if (context->modules[i]->RecvPacket(addr, packet)) + { + return true; + } + } + + return false; +} + +// Note: this prints into a static buffer, calling again overwrites +// the first result + +char *NET_AddrToString(net_addr_t *addr) +{ + static char buf[128]; + + addr->module->AddrToString(addr, buf, sizeof(buf) - 1); + + return buf; +} + +void NET_FreeAddress(net_addr_t *addr) +{ + addr->module->FreeAddress(addr); +} + + diff --git a/client/src/net_io.h b/client/src/net_io.h new file mode 100644 index 0000000..ab94a8f --- /dev/null +++ b/client/src/net_io.h @@ -0,0 +1,37 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Network packet manipulation (net_packet_t) +// + +#ifndef NET_IO_H +#define NET_IO_H + +#include "doomtype.h" +#include "net_defs.h" + +extern net_addr_t net_broadcast_addr; + +net_context_t *NET_NewContext(void); +void NET_AddModule(net_context_t *context, net_module_t *module); +void NET_SendPacket(net_addr_t *addr, net_packet_t *packet); +void NET_SendBroadcast(net_context_t *context, net_packet_t *packet); +boolean NET_RecvPacket(net_context_t *context, net_addr_t **addr, + net_packet_t **packet); +char *NET_AddrToString(net_addr_t *addr); +void NET_FreeAddress(net_addr_t *addr); +net_addr_t *NET_ResolveAddress(net_context_t *context, char *address); + +#endif /* #ifndef NET_IO_H */ + diff --git a/client/src/net_loop.c b/client/src/net_loop.c new file mode 100644 index 0000000..c03baf9 --- /dev/null +++ b/client/src/net_loop.c @@ -0,0 +1,230 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Loopback network module for server compiled into the client +// + +#include + +#include "doomtype.h" +#include "i_system.h" +#include "m_misc.h" +#include "net_defs.h" +#include "net_loop.h" +#include "net_packet.h" + +#define MAX_QUEUE_SIZE 16 + +typedef struct +{ + net_packet_t *packets[MAX_QUEUE_SIZE]; + int head, tail; +} packet_queue_t; + +static packet_queue_t client_queue; +static packet_queue_t server_queue; +static net_addr_t client_addr; +static net_addr_t server_addr; + +static void QueueInit(packet_queue_t *queue) +{ + queue->head = queue->tail = 0; +} + +static void QueuePush(packet_queue_t *queue, net_packet_t *packet) +{ + int new_tail; + + new_tail = (queue->tail + 1) % MAX_QUEUE_SIZE; + + if (new_tail == queue->head) + { + // queue is full + + return; + } + + queue->packets[queue->tail] = packet; + queue->tail = new_tail; +} + +static net_packet_t *QueuePop(packet_queue_t *queue) +{ + net_packet_t *packet; + + if (queue->tail == queue->head) + { + // queue empty + + return NULL; + } + + packet = queue->packets[queue->head]; + queue->head = (queue->head + 1) % MAX_QUEUE_SIZE; + + return packet; +} + +//----------------------------------------------------------------------------- +// +// Client end code +// +//----------------------------------------------------------------------------- + +static boolean NET_CL_InitClient(void) +{ + QueueInit(&client_queue); + + return true; +} + +static boolean NET_CL_InitServer(void) +{ + I_Error("NET_CL_InitServer: attempted to initialize client pipe end as a server!"); + return false; +} + +static void NET_CL_SendPacket(net_addr_t *addr, net_packet_t *packet) +{ + QueuePush(&server_queue, NET_PacketDup(packet)); +} + +static boolean NET_CL_RecvPacket(net_addr_t **addr, net_packet_t **packet) +{ + net_packet_t *popped; + + popped = QueuePop(&client_queue); + + if (popped != NULL) + { + *packet = popped; + *addr = &client_addr; + client_addr.module = &net_loop_client_module; + + return true; + } + + return false; +} + +static void NET_CL_AddrToString(net_addr_t *addr, char *buffer, int buffer_len) +{ + M_snprintf(buffer, buffer_len, "local server"); +} + +static void NET_CL_FreeAddress(net_addr_t *addr) +{ +} + +static net_addr_t *NET_CL_ResolveAddress(char *address) +{ + if (address == NULL) + { + client_addr.module = &net_loop_client_module; + + return &client_addr; + } + else + { + return NULL; + } +} + +net_module_t net_loop_client_module = +{ + NET_CL_InitClient, + NET_CL_InitServer, + NET_CL_SendPacket, + NET_CL_RecvPacket, + NET_CL_AddrToString, + NET_CL_FreeAddress, + NET_CL_ResolveAddress, +}; + +//----------------------------------------------------------------------------- +// +// Server end code +// +//----------------------------------------------------------------------------- + +static boolean NET_SV_InitClient(void) +{ + I_Error("NET_SV_InitClient: attempted to initialize server pipe end as a client!"); + return false; +} + +static boolean NET_SV_InitServer(void) +{ + QueueInit(&server_queue); + + return true; +} + +static void NET_SV_SendPacket(net_addr_t *addr, net_packet_t *packet) +{ + QueuePush(&client_queue, NET_PacketDup(packet)); +} + +static boolean NET_SV_RecvPacket(net_addr_t **addr, net_packet_t **packet) +{ + net_packet_t *popped; + + popped = QueuePop(&server_queue); + + if (popped != NULL) + { + *packet = popped; + *addr = &server_addr; + server_addr.module = &net_loop_server_module; + + return true; + } + + return false; +} + +static void NET_SV_AddrToString(net_addr_t *addr, char *buffer, int buffer_len) +{ + M_snprintf(buffer, buffer_len, "local client"); +} + +static void NET_SV_FreeAddress(net_addr_t *addr) +{ +} + +static net_addr_t *NET_SV_ResolveAddress(char *address) +{ + if (address == NULL) + { + server_addr.module = &net_loop_server_module; + return &server_addr; + } + else + { + return NULL; + } +} + +net_module_t net_loop_server_module = +{ + NET_SV_InitClient, + NET_SV_InitServer, + NET_SV_SendPacket, + NET_SV_RecvPacket, + NET_SV_AddrToString, + NET_SV_FreeAddress, + NET_SV_ResolveAddress, +}; + + diff --git a/client/src/net_loop.h b/client/src/net_loop.h new file mode 100644 index 0000000..5a2e58e --- /dev/null +++ b/client/src/net_loop.h @@ -0,0 +1,27 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Loopback network module for server compiled into the client +// + +#ifndef NET_LOOP_H +#define NET_LOOP_H + +#include "net_defs.h" + +extern net_module_t net_loop_client_module; +extern net_module_t net_loop_server_module; + +#endif /* #ifndef NET_LOOP_H */ + diff --git a/client/src/net_packet.c b/client/src/net_packet.c new file mode 100644 index 0000000..82a20fe --- /dev/null +++ b/client/src/net_packet.c @@ -0,0 +1,296 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Network packet manipulation (net_packet_t) +// + +#include +#include "m_misc.h" +#include "net_packet.h" +#include "z_zone.h" + +static int total_packet_memory = 0; + +net_packet_t *NET_NewPacket(int initial_size) +{ + net_packet_t *packet; + + packet = (net_packet_t *) Z_Malloc(sizeof(net_packet_t), PU_STATIC, 0); + + if (initial_size == 0) + initial_size = 256; + + packet->alloced = initial_size; + packet->data = Z_Malloc(initial_size, PU_STATIC, 0); + packet->len = 0; + packet->pos = 0; + + total_packet_memory += sizeof(net_packet_t) + initial_size; + + //printf("total packet memory: %i bytes\n", total_packet_memory); + //printf("%p: allocated\n", packet); + + return packet; +} + +// duplicates an existing packet + +net_packet_t *NET_PacketDup(net_packet_t *packet) +{ + net_packet_t *newpacket; + + newpacket = NET_NewPacket(packet->len); + memcpy(newpacket->data, packet->data, packet->len); + newpacket->len = packet->len; + + return newpacket; +} + +void NET_FreePacket(net_packet_t *packet) +{ + //printf("%p: destroyed\n", packet); + + total_packet_memory -= sizeof(net_packet_t) + packet->alloced; + Z_Free(packet->data); + Z_Free(packet); +} + +// Read a byte from the packet, returning true if read +// successfully + +boolean NET_ReadInt8(net_packet_t *packet, unsigned int *data) +{ + if (packet->pos + 1 > packet->len) + return false; + + *data = packet->data[packet->pos]; + + packet->pos += 1; + + return true; +} + +// Read a 16-bit integer from the packet, returning true if read +// successfully + +boolean NET_ReadInt16(net_packet_t *packet, unsigned int *data) +{ + byte *p; + + if (packet->pos + 2 > packet->len) + return false; + + p = packet->data + packet->pos; + + *data = (p[0] << 8) | p[1]; + packet->pos += 2; + + return true; +} + +// Read a 32-bit integer from the packet, returning true if read +// successfully + +boolean NET_ReadInt32(net_packet_t *packet, unsigned int *data) +{ + byte *p; + + if (packet->pos + 4 > packet->len) + return false; + + p = packet->data + packet->pos; + + *data = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + packet->pos += 4; + + return true; +} + +// Signed read functions + +boolean NET_ReadSInt8(net_packet_t *packet, signed int *data) +{ + if (NET_ReadInt8(packet,(unsigned int *) data)) + { + if (*data & (1 << 7)) + { + *data &= ~(1 << 7); + *data -= (1 << 7); + } + return true; + } + else + { + return false; + } +} + +boolean NET_ReadSInt16(net_packet_t *packet, signed int *data) +{ + if (NET_ReadInt16(packet, (unsigned int *) data)) + { + if (*data & (1 << 15)) + { + *data &= ~(1 << 15); + *data -= (1 << 15); + } + return true; + } + else + { + return false; + } +} + +boolean NET_ReadSInt32(net_packet_t *packet, signed int *data) +{ + if (NET_ReadInt32(packet, (unsigned int *) data)) + { + if (*data & (1 << 31)) + { + *data &= ~(1 << 31); + *data -= (1 << 31); + } + return true; + } + else + { + return false; + } +} + +// Read a string from the packet. Returns NULL if a terminating +// NUL character was not found before the end of the packet. + +char *NET_ReadString(net_packet_t *packet) +{ + char *start; + + start = (char *) packet->data + packet->pos; + + // Search forward for a NUL character + + while (packet->pos < packet->len && packet->data[packet->pos] != '\0') + { + ++packet->pos; + } + + if (packet->pos >= packet->len) + { + // Reached the end of the packet + + return NULL; + } + + // packet->data[packet->pos] == '\0': We have reached a terminating + // NULL. Skip past this NULL and continue reading immediately + // after it. + + ++packet->pos; + + return start; +} + +// Dynamically increases the size of a packet + +static void NET_IncreasePacket(net_packet_t *packet) +{ + byte *newdata; + + total_packet_memory -= packet->alloced; + + packet->alloced *= 2; + + newdata = Z_Malloc(packet->alloced, PU_STATIC, 0); + + memcpy(newdata, packet->data, packet->len); + + Z_Free(packet->data); + packet->data = newdata; + + total_packet_memory += packet->alloced; +} + +// Write a single byte to the packet + +void NET_WriteInt8(net_packet_t *packet, unsigned int i) +{ + if (packet->len + 1 > packet->alloced) + NET_IncreasePacket(packet); + + packet->data[packet->len] = i; + packet->len += 1; +} + +// Write a 16-bit integer to the packet + +void NET_WriteInt16(net_packet_t *packet, unsigned int i) +{ + byte *p; + + if (packet->len + 2 > packet->alloced) + NET_IncreasePacket(packet); + + p = packet->data + packet->len; + + p[0] = (i >> 8) & 0xff; + p[1] = i & 0xff; + + packet->len += 2; +} + + +// Write a single byte to the packet + +void NET_WriteInt32(net_packet_t *packet, unsigned int i) +{ + byte *p; + + if (packet->len + 4 > packet->alloced) + NET_IncreasePacket(packet); + + p = packet->data + packet->len; + + p[0] = (i >> 24) & 0xff; + p[1] = (i >> 16) & 0xff; + p[2] = (i >> 8) & 0xff; + p[3] = i & 0xff; + + packet->len += 4; +} + +void NET_WriteString(net_packet_t *packet, char *string) +{ + byte *p; + size_t string_size; + + string_size = strlen(string) + 1; + + // Increase the packet size until large enough to hold the string + + while (packet->len + string_size > packet->alloced) + { + NET_IncreasePacket(packet); + } + + p = packet->data + packet->len; + + M_StringCopy((char *) p, string, string_size); + + packet->len += string_size; +} + + + + diff --git a/client/src/net_packet.h b/client/src/net_packet.h new file mode 100644 index 0000000..cb170be --- /dev/null +++ b/client/src/net_packet.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Definitions for use in networking code. +// + +#ifndef NET_PACKET_H +#define NET_PACKET_H + +#include "doomtype.h" +#include "net_defs.h" + +net_packet_t *NET_NewPacket(int initial_size); +net_packet_t *NET_PacketDup(net_packet_t *packet); +void NET_FreePacket(net_packet_t *packet); + +boolean NET_ReadInt8(net_packet_t *packet, unsigned int *data); +boolean NET_ReadInt16(net_packet_t *packet, unsigned int *data); +boolean NET_ReadInt32(net_packet_t *packet, unsigned int *data); + +boolean NET_ReadSInt8(net_packet_t *packet, signed int *data); +boolean NET_ReadSInt16(net_packet_t *packet, signed int *data); +boolean NET_ReadSInt32(net_packet_t *packet, signed int *data); + +char *NET_ReadString(net_packet_t *packet); + +void NET_WriteInt8(net_packet_t *packet, unsigned int i); +void NET_WriteInt16(net_packet_t *packet, unsigned int i); +void NET_WriteInt32(net_packet_t *packet, unsigned int i); + +void NET_WriteString(net_packet_t *packet, char *string); + +#endif /* #ifndef NET_PACKET_H */ + diff --git a/client/src/net_query.c b/client/src/net_query.c new file mode 100644 index 0000000..fcad9e4 --- /dev/null +++ b/client/src/net_query.c @@ -0,0 +1,953 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Querying servers to find their current status. +// + +#include +#include +#include +#include + +#include "aes_prng.h" +#include "d_mode.h" +#include "i_system.h" +#include "i_timer.h" +#include "m_misc.h" +#include "net_defs.h" +#include "net_io.h" +#include "net_packet.h" +#include "net_query.h" +#include "net_sdl.h" +#include "net_structrw.h" +#include "sha1.h" + +// DNS address of the Internet master server. + +#define MASTER_SERVER_ADDRESS "master.chocolate-doom.org:2342" + +// Time to wait for a response before declaring a timeout. + +#define QUERY_TIMEOUT_SECS 2 + +// Time to wait for secure demo signatures before declaring a timeout. + +#define SIGNATURE_TIMEOUT_SECS 5 + +// Number of query attempts to make before giving up on a server. + +#define QUERY_MAX_ATTEMPTS 3 + +typedef enum +{ + QUERY_TARGET_SERVER, // Normal server target. + QUERY_TARGET_MASTER, // The master server. + QUERY_TARGET_BROADCAST // Send a broadcast query +} query_target_type_t; + +typedef enum +{ + QUERY_TARGET_QUEUED, // Query not yet sent + QUERY_TARGET_QUERIED, // Query sent, waiting response + QUERY_TARGET_RESPONDED, // Response received + QUERY_TARGET_NO_RESPONSE +} query_target_state_t; + +typedef struct +{ + query_target_type_t type; + query_target_state_t state; + net_addr_t *addr; + net_querydata_t data; + unsigned int ping_time; + unsigned int query_time; + unsigned int query_attempts; + boolean printed; +} query_target_t; + +static boolean registered_with_master = false; +static boolean got_master_response = false; + +static net_context_t *query_context; +static query_target_t *targets; +static int num_targets; + +static boolean query_loop_running = false; +static boolean printed_header = false; +static int last_query_time = 0; + +static char *securedemo_start_message = NULL; + +// Resolve the master server address. + +net_addr_t *NET_Query_ResolveMaster(net_context_t *context) +{ + net_addr_t *addr; + + addr = NET_ResolveAddress(context, MASTER_SERVER_ADDRESS); + + if (addr == NULL) + { + fprintf(stderr, "Warning: Failed to resolve address " + "for master server: %s\n", MASTER_SERVER_ADDRESS); + } + + return addr; +} + +// Send a registration packet to the master server to register +// ourselves with the global list. + +void NET_Query_AddToMaster(net_addr_t *master_addr) +{ + net_packet_t *packet; + + packet = NET_NewPacket(10); + NET_WriteInt16(packet, NET_MASTER_PACKET_TYPE_ADD); + NET_SendPacket(master_addr, packet); + NET_FreePacket(packet); +} + +// Process a packet received from the master server. + +void NET_Query_MasterResponse(net_packet_t *packet) +{ + unsigned int packet_type; + unsigned int result; + + if (!NET_ReadInt16(packet, &packet_type) + || !NET_ReadInt16(packet, &result)) + { + return; + } + + if (packet_type == NET_MASTER_PACKET_TYPE_ADD_RESPONSE) + { + if (result != 0) + { + // Only show the message once. + + if (!registered_with_master) + { + printf("Registered with master server at %s\n", + MASTER_SERVER_ADDRESS); + registered_with_master = true; + } + } + else + { + // Always show rejections. + + printf("Failed to register with master server at %s\n", + MASTER_SERVER_ADDRESS); + } + + got_master_response = true; + } +} + +boolean NET_Query_CheckAddedToMaster(boolean *result) +{ + // Got response from master yet? + + if (!got_master_response) + { + return false; + } + + *result = registered_with_master; + return true; +} + +// Send a query to the master server. + +static void NET_Query_SendMasterQuery(net_addr_t *addr) +{ + net_packet_t *packet; + + packet = NET_NewPacket(10); + NET_WriteInt16(packet, NET_MASTER_PACKET_TYPE_QUERY); + NET_SendPacket(addr, packet); + NET_FreePacket(packet); +} + +// Given the specified address, find the target associated. If no +// target is found, and 'create' is true, a new target is created. + +static query_target_t *GetTargetForAddr(net_addr_t *addr, boolean create) +{ + query_target_t *target; + int i; + + for (i=0; itype = QUERY_TARGET_SERVER; + target->state = QUERY_TARGET_QUEUED; + target->printed = false; + target->query_attempts = 0; + target->addr = addr; + ++num_targets; + + return target; +} + +// Transmit a query packet + +static void NET_Query_SendQuery(net_addr_t *addr) +{ + net_packet_t *request; + + request = NET_NewPacket(10); + NET_WriteInt16(request, NET_PACKET_TYPE_QUERY); + + if (addr == NULL) + { + NET_SendBroadcast(query_context, request); + } + else + { + NET_SendPacket(addr, request); + } + + NET_FreePacket(request); +} + +static void NET_Query_ParseResponse(net_addr_t *addr, net_packet_t *packet, + net_query_callback_t callback, + void *user_data) +{ + unsigned int packet_type; + net_querydata_t querydata; + query_target_t *target; + + // Read the header + + if (!NET_ReadInt16(packet, &packet_type) + || packet_type != NET_PACKET_TYPE_QUERY_RESPONSE) + { + return; + } + + // Read query data + + if (!NET_ReadQueryData(packet, &querydata)) + { + return; + } + + // Find the target that responded. + + target = GetTargetForAddr(addr, false); + + // If the target is not found, it may be because we are doing + // a LAN broadcast search, in which case we need to create a + // target for the new responder. + + if (target == NULL) + { + query_target_t *broadcast_target; + + broadcast_target = GetTargetForAddr(NULL, false); + + // Not in broadcast mode, unexpected response that came out + // of nowhere. Ignore. + + if (broadcast_target == NULL + || broadcast_target->state != QUERY_TARGET_QUERIED) + { + return; + } + + // Create new target. + + target = GetTargetForAddr(addr, true); + target->state = QUERY_TARGET_QUERIED; + target->query_time = broadcast_target->query_time; + } + + if (target->state != QUERY_TARGET_RESPONDED) + { + target->state = QUERY_TARGET_RESPONDED; + memcpy(&target->data, &querydata, sizeof(net_querydata_t)); + + // Calculate RTT. + + target->ping_time = I_GetTimeMS() - target->query_time; + + // Invoke callback to signal that we have a new address. + + callback(addr, &target->data, target->ping_time, user_data); + } +} + +// Parse a response packet from the master server. + +static void NET_Query_ParseMasterResponse(net_addr_t *master_addr, + net_packet_t *packet) +{ + unsigned int packet_type; + query_target_t *target; + char *addr_str; + net_addr_t *addr; + + // Read the header. We are only interested in query responses. + + if (!NET_ReadInt16(packet, &packet_type) + || packet_type != NET_MASTER_PACKET_TYPE_QUERY_RESPONSE) + { + return; + } + + // Read a list of strings containing the addresses of servers + // that the master knows about. + + for (;;) + { + addr_str = NET_ReadString(packet); + + if (addr_str == NULL) + { + break; + } + + // Resolve address and add to targets list if it is not already + // there. + + addr = NET_ResolveAddress(query_context, addr_str); + + if (addr != NULL) + { + GetTargetForAddr(addr, true); + } + } + + // Mark the master as having responded. + + target = GetTargetForAddr(master_addr, true); + target->state = QUERY_TARGET_RESPONDED; +} + +static void NET_Query_ParsePacket(net_addr_t *addr, net_packet_t *packet, + net_query_callback_t callback, + void *user_data) +{ + query_target_t *target; + + // This might be the master server responding. + + target = GetTargetForAddr(addr, false); + + if (target != NULL && target->type == QUERY_TARGET_MASTER) + { + NET_Query_ParseMasterResponse(addr, packet); + } + else + { + NET_Query_ParseResponse(addr, packet, callback, user_data); + } +} + +static void NET_Query_GetResponse(net_query_callback_t callback, + void *user_data) +{ + net_addr_t *addr; + net_packet_t *packet; + + if (NET_RecvPacket(query_context, &addr, &packet)) + { + NET_Query_ParsePacket(addr, packet, callback, user_data); + NET_FreePacket(packet); + } +} + +// Find a target we have not yet queried and send a query. + +static void SendOneQuery(void) +{ + unsigned int now; + unsigned int i; + + now = I_GetTimeMS(); + + // Rate limit - only send one query every 50ms. + + if (now - last_query_time < 50) + { + return; + } + + for (i = 0; i < num_targets; ++i) + { + // Not queried yet? + // Or last query timed out without a response? + + if (targets[i].state == QUERY_TARGET_QUEUED + || (targets[i].state == QUERY_TARGET_QUERIED + && now - targets[i].query_time > QUERY_TIMEOUT_SECS * 1000)) + { + break; + } + } + + if (i >= num_targets) + { + return; + } + + // Found a target to query. Send a query; how to do this depends on + // the target type. + + switch (targets[i].type) + { + case QUERY_TARGET_SERVER: + NET_Query_SendQuery(targets[i].addr); + break; + + case QUERY_TARGET_BROADCAST: + NET_Query_SendQuery(NULL); + break; + + case QUERY_TARGET_MASTER: + NET_Query_SendMasterQuery(targets[i].addr); + break; + } + + //printf("Queried %s\n", NET_AddrToString(targets[i].addr)); + targets[i].state = QUERY_TARGET_QUERIED; + targets[i].query_time = now; + ++targets[i].query_attempts; + + last_query_time = now; +} + +// Time out servers that have been queried and not responded. + +static void CheckTargetTimeouts(void) +{ + unsigned int i; + unsigned int now; + + now = I_GetTimeMS(); + + for (i = 0; i < num_targets; ++i) + { + /* + printf("target %i: state %i, queries %i, query time %i\n", + i, targets[i].state, targets[i].query_attempts, + now - targets[i].query_time); + */ + + // We declare a target to be "no response" when we've sent + // multiple query packets to it (QUERY_MAX_ATTEMPTS) and + // received no response to any of them. + + if (targets[i].state == QUERY_TARGET_QUERIED + && targets[i].query_attempts >= QUERY_MAX_ATTEMPTS + && now - targets[i].query_time > QUERY_TIMEOUT_SECS * 1000) + { + targets[i].state = QUERY_TARGET_NO_RESPONSE; + + if (targets[i].type == QUERY_TARGET_MASTER) + { + fprintf(stderr, "NET_MasterQuery: no response " + "from master server.\n"); + } + } + } +} + +// If all targets have responded or timed out, returns true. + +static boolean AllTargetsDone(void) +{ + unsigned int i; + + for (i = 0; i < num_targets; ++i) + { + if (targets[i].state != QUERY_TARGET_RESPONDED + && targets[i].state != QUERY_TARGET_NO_RESPONSE) + { + return false; + } + } + + return true; +} + +// Polling function, invoked periodically to send queries and +// interpret new responses received from remote servers. +// Returns zero when the query sequence has completed and all targets +// have returned responses or timed out. + +int NET_Query_Poll(net_query_callback_t callback, void *user_data) +{ + CheckTargetTimeouts(); + + // Send a query. This will only send a single query at once. + + SendOneQuery(); + + // Check for a response + + NET_Query_GetResponse(callback, user_data); + + return !AllTargetsDone(); +} + +// Stop the query loop + +static void NET_Query_ExitLoop(void) +{ + query_loop_running = false; +} + +// Loop waiting for responses. +// The specified callback is invoked when a new server responds. + +static void NET_Query_QueryLoop(net_query_callback_t callback, void *user_data) +{ + query_loop_running = true; + + while (query_loop_running && NET_Query_Poll(callback, user_data)) + { + // Don't thrash the CPU + + I_Sleep(1); + } +} + +void NET_Query_Init(void) +{ + if (query_context == NULL) + { + query_context = NET_NewContext(); + NET_AddModule(query_context, &net_sdl_module); + net_sdl_module.InitClient(); + } + + free(targets); + targets = NULL; + num_targets = 0; + + printed_header = false; +} + +// Callback that exits the query loop when the first server is found. + +static void NET_Query_ExitCallback(net_addr_t *addr, net_querydata_t *data, + unsigned int ping_time, void *user_data) +{ + NET_Query_ExitLoop(); +} + +// Search the targets list and find a target that has responded. +// If none have responded, returns NULL. + +static query_target_t *FindFirstResponder(void) +{ + unsigned int i; + + for (i = 0; i < num_targets; ++i) + { + if (targets[i].type == QUERY_TARGET_SERVER + && targets[i].state == QUERY_TARGET_RESPONDED) + { + return &targets[i]; + } + } + + return NULL; +} + +// Return a count of the number of responses. + +static int GetNumResponses(void) +{ + unsigned int i; + int result; + + result = 0; + + for (i = 0; i < num_targets; ++i) + { + if (targets[i].type == QUERY_TARGET_SERVER + && targets[i].state == QUERY_TARGET_RESPONDED) + { + ++result; + } + } + + return result; +} + +int NET_StartLANQuery(void) +{ + query_target_t *target; + + NET_Query_Init(); + + // Add a broadcast target to the list. + + target = GetTargetForAddr(NULL, true); + target->type = QUERY_TARGET_BROADCAST; + + return 1; +} + +int NET_StartMasterQuery(void) +{ + net_addr_t *master; + query_target_t *target; + + NET_Query_Init(); + + // Resolve master address and add to targets list. + + master = NET_Query_ResolveMaster(query_context); + + if (master == NULL) + { + return 0; + } + + target = GetTargetForAddr(master, true); + target->type = QUERY_TARGET_MASTER; + + return 1; +} + +// ----------------------------------------------------------------------- + +static void formatted_printf(int wide, char *s, ...) +{ + va_list args; + int i; + + va_start(args, s); + i = vprintf(s, args); + va_end(args); + + while (i < wide) + { + putchar(' '); + ++i; + } +} + +static char *GameDescription(GameMode_t mode, GameMission_t mission) +{ + switch (mission) + { + case doom: + if (mode == shareware) + return "swdoom"; + else if (mode == registered) + return "regdoom"; + else if (mode == retail) + return "ultdoom"; + else + return "doom"; + case doom2: + return "doom2"; + case pack_tnt: + return "tnt"; + case pack_plut: + return "plutonia"; + case pack_chex: + return "chex"; + case pack_hacx: + return "hacx"; + case heretic: + return "heretic"; + case hexen: + return "hexen"; + case strife: + return "strife"; + default: + return "?"; + } +} + +static void PrintHeader(void) +{ + int i; + + putchar('\n'); + formatted_printf(5, "Ping"); + formatted_printf(18, "Address"); + formatted_printf(8, "Players"); + puts("Description"); + + for (i=0; i<70; ++i) + putchar('='); + putchar('\n'); +} + +// Callback function that just prints information in a table. + +static void NET_QueryPrintCallback(net_addr_t *addr, + net_querydata_t *data, + unsigned int ping_time, + void *user_data) +{ + // If this is the first server, print the header. + + if (!printed_header) + { + PrintHeader(); + printed_header = true; + } + + formatted_printf(5, "%4i", ping_time); + formatted_printf(18, "%s: ", NET_AddrToString(addr)); + formatted_printf(8, "%i/%i", data->num_players, + data->max_players); + + if (data->gamemode != indetermined) + { + printf("(%s) ", GameDescription(data->gamemode, + data->gamemission)); + } + + if (data->server_state) + { + printf("(game running) "); + } + + NET_SafePuts(data->description); +} + +void NET_LANQuery(void) +{ + if (NET_StartLANQuery()) + { + printf("\nSearching for servers on local LAN ...\n"); + + NET_Query_QueryLoop(NET_QueryPrintCallback, NULL); + + printf("\n%i server(s) found.\n", GetNumResponses()); + } +} + +void NET_MasterQuery(void) +{ + if (NET_StartMasterQuery()) + { + printf("\nSearching for servers on Internet ...\n"); + + NET_Query_QueryLoop(NET_QueryPrintCallback, NULL); + + printf("\n%i server(s) found.\n", GetNumResponses()); + } +} + +void NET_QueryAddress(char *addr_str) +{ + net_addr_t *addr; + query_target_t *target; + + NET_Query_Init(); + + addr = NET_ResolveAddress(query_context, addr_str); + + if (addr == NULL) + { + I_Error("NET_QueryAddress: Host '%s' not found!", addr_str); + } + + // Add the address to the list of targets. + + target = GetTargetForAddr(addr, true); + + printf("\nQuerying '%s'...\n", addr_str); + + // Run query loop. + + NET_Query_QueryLoop(NET_Query_ExitCallback, NULL); + + // Check if the target responded. + + if (target->state == QUERY_TARGET_RESPONDED) + { + NET_QueryPrintCallback(addr, &target->data, target->ping_time, NULL); + } + else + { + I_Error("No response from '%s'", addr_str); + } +} + +net_addr_t *NET_FindLANServer(void) +{ + query_target_t *target; + query_target_t *responder; + + NET_Query_Init(); + + // Add a broadcast target to the list. + + target = GetTargetForAddr(NULL, true); + target->type = QUERY_TARGET_BROADCAST; + + // Run the query loop, and stop at the first target found. + + NET_Query_QueryLoop(NET_Query_ExitCallback, NULL); + + responder = FindFirstResponder(); + + if (responder != NULL) + { + return responder->addr; + } + else + { + return NULL; + } +} + +// Block until a packet of the given type is received from the given +// address. + +static net_packet_t *BlockForPacket(net_addr_t *addr, unsigned int packet_type, + unsigned int timeout_ms) +{ + net_packet_t *packet; + net_addr_t *packet_src; + unsigned int read_packet_type; + unsigned int start_time; + + start_time = I_GetTimeMS(); + + while (I_GetTimeMS() < start_time + timeout_ms) + { + if (!NET_RecvPacket(query_context, &packet_src, &packet)) + { + I_Sleep(20); + continue; + } + + if (packet_src == addr + && NET_ReadInt16(packet, &read_packet_type) + && packet_type == read_packet_type) + { + return packet; + } + + NET_FreePacket(packet); + } + + // Timeout - no response. + + return NULL; +} + +// Query master server for secure demo start seed value. + +boolean NET_StartSecureDemo(prng_seed_t seed) +{ + net_packet_t *request, *response; + net_addr_t *master_addr; + char *signature; + boolean result; + + NET_Query_Init(); + master_addr = NET_Query_ResolveMaster(query_context); + + // Send request packet to master server. + + request = NET_NewPacket(10); + NET_WriteInt16(request, NET_MASTER_PACKET_TYPE_SIGN_START); + NET_SendPacket(master_addr, request); + NET_FreePacket(request); + + // Block for response and read contents. + // The signed start message will be saved for later. + + response = BlockForPacket(master_addr, + NET_MASTER_PACKET_TYPE_SIGN_START_RESPONSE, + SIGNATURE_TIMEOUT_SECS * 1000); + + result = false; + + if (response != NULL) + { + if (NET_ReadPRNGSeed(response, seed)) + { + signature = NET_ReadString(response); + + if (signature != NULL) + { + securedemo_start_message = M_StringDuplicate(signature); + result = true; + } + } + + NET_FreePacket(response); + } + + return result; +} + +// Query master server for secure demo end signature. + +char *NET_EndSecureDemo(sha1_digest_t demo_hash) +{ + net_packet_t *request, *response; + net_addr_t *master_addr; + char *signature; + + master_addr = NET_Query_ResolveMaster(query_context); + + // Construct end request and send to master server. + + request = NET_NewPacket(10); + NET_WriteInt16(request, NET_MASTER_PACKET_TYPE_SIGN_END); + NET_WriteSHA1Sum(request, demo_hash); + NET_WriteString(request, securedemo_start_message); + NET_SendPacket(master_addr, request); + NET_FreePacket(request); + + // Block for response. The response packet simply contains a string + // with the ASCII signature. + + response = BlockForPacket(master_addr, + NET_MASTER_PACKET_TYPE_SIGN_END_RESPONSE, + SIGNATURE_TIMEOUT_SECS * 1000); + + if (response == NULL) + { + return NULL; + } + + signature = NET_ReadString(response); + + NET_FreePacket(response); + + return signature; +} + diff --git a/client/src/net_query.h b/client/src/net_query.h new file mode 100644 index 0000000..323545f --- /dev/null +++ b/client/src/net_query.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Querying servers to find their current status. +// + +#ifndef NET_QUERY_H +#define NET_QUERY_H + +#include "doomtype.h" +#include "net_defs.h" + +typedef void (*net_query_callback_t)(net_addr_t *addr, + net_querydata_t *querydata, + unsigned int ping_time, + void *user_data); + +extern int NET_StartLANQuery(void); +extern int NET_StartMasterQuery(void); + +extern void NET_LANQuery(void); +extern void NET_MasterQuery(void); +extern void NET_QueryAddress(char *addr); +extern net_addr_t *NET_FindLANServer(void); + +extern int NET_Query_Poll(net_query_callback_t callback, void *user_data); + +extern net_addr_t *NET_Query_ResolveMaster(net_context_t *context); +extern void NET_Query_AddToMaster(net_addr_t *master_addr); +extern boolean NET_Query_CheckAddedToMaster(boolean *result); +extern void NET_Query_MasterResponse(net_packet_t *packet); + +#endif /* #ifndef NET_QUERY_H */ + diff --git a/client/src/net_sdl.c b/client/src/net_sdl.c new file mode 100644 index 0000000..cd20a79 --- /dev/null +++ b/client/src/net_sdl.c @@ -0,0 +1,381 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Networking module which uses SDL_net +// + +#include +#include +#include + +#include "doomtype.h" +#include "i_system.h" +#include "m_argv.h" +#include "m_misc.h" +#include "net_defs.h" +#include "net_io.h" +#include "net_packet.h" +#include "net_sdl.h" +#include "z_zone.h" + +// +// NETWORKING +// + +#include + +#define DEFAULT_PORT 2342 + +static boolean initted = false; +static int port = DEFAULT_PORT; +static UDPsocket udpsocket; +static UDPpacket *recvpacket; + +typedef struct +{ + net_addr_t net_addr; + IPaddress sdl_addr; +} addrpair_t; + +static addrpair_t **addr_table; +static int addr_table_size = -1; + +// Initializes the address table + +static void NET_SDL_InitAddrTable(void) +{ + addr_table_size = 16; + + addr_table = Z_Malloc(sizeof(addrpair_t *) * addr_table_size, + PU_STATIC, 0); + memset(addr_table, 0, sizeof(addrpair_t *) * addr_table_size); +} + +static boolean AddressesEqual(IPaddress *a, IPaddress *b) +{ + return a->host == b->host + && a->port == b->port; +} + +// Finds an address by searching the table. If the address is not found, +// it is added to the table. + +static net_addr_t *NET_SDL_FindAddress(IPaddress *addr) +{ + addrpair_t *new_entry; + int empty_entry = -1; + int i; + + if (addr_table_size < 0) + { + NET_SDL_InitAddrTable(); + } + + for (i=0; isdl_addr)) + { + return &addr_table[i]->net_addr; + } + + if (empty_entry < 0 && addr_table[i] == NULL) + empty_entry = i; + } + + // Was not found in list. We need to add it. + + // Is there any space in the table? If not, increase the table size + + if (empty_entry < 0) + { + addrpair_t **new_addr_table; + int new_addr_table_size; + + // after reallocing, we will add this in as the first entry + // in the new block of memory + + empty_entry = addr_table_size; + + // allocate a new array twice the size, init to 0 and copy + // the existing table in. replace the old table. + + new_addr_table_size = addr_table_size * 2; + new_addr_table = Z_Malloc(sizeof(addrpair_t *) * new_addr_table_size, + PU_STATIC, 0); + memset(new_addr_table, 0, sizeof(addrpair_t *) * new_addr_table_size); + memcpy(new_addr_table, addr_table, + sizeof(addrpair_t *) * addr_table_size); + Z_Free(addr_table); + addr_table = new_addr_table; + addr_table_size = new_addr_table_size; + } + + // Add a new entry + + new_entry = Z_Malloc(sizeof(addrpair_t), PU_STATIC, 0); + + new_entry->sdl_addr = *addr; + new_entry->net_addr.handle = &new_entry->sdl_addr; + new_entry->net_addr.module = &net_sdl_module; + + addr_table[empty_entry] = new_entry; + + return &new_entry->net_addr; +} + +static void NET_SDL_FreeAddress(net_addr_t *addr) +{ + int i; + + for (i=0; inet_addr) + { + Z_Free(addr_table[i]); + addr_table[i] = NULL; + return; + } + } + + I_Error("NET_SDL_FreeAddress: Attempted to remove an unused address!"); +} + +static boolean NET_SDL_InitClient(void) +{ + int p; + + if (initted) + return true; + + //! + // @category net + // @arg + // + // Use the specified UDP port for communications, instead of + // the default (2342). + // + + p = M_CheckParmWithArgs("-port", 1); + if (p > 0) + port = atoi(myargv[p+1]); + + SDLNet_Init(); + + udpsocket = SDLNet_UDP_Open(0); + + if (udpsocket == NULL) + { + I_Error("NET_SDL_InitClient: Unable to open a socket!"); + } + + recvpacket = SDLNet_AllocPacket(1500); + +#ifdef DROP_PACKETS + srand(time(NULL)); +#endif + + initted = true; + + return true; +} + +static boolean NET_SDL_InitServer(void) +{ + int p; + + if (initted) + return true; + + p = M_CheckParmWithArgs("-port", 1); + if (p > 0) + port = atoi(myargv[p+1]); + + SDLNet_Init(); + + udpsocket = SDLNet_UDP_Open(port); + + if (udpsocket == NULL) + { + I_Error("NET_SDL_InitServer: Unable to bind to port %i", port); + } + + recvpacket = SDLNet_AllocPacket(1500); +#ifdef DROP_PACKETS + srand(time(NULL)); +#endif + + initted = true; + + return true; +} + +static void NET_SDL_SendPacket(net_addr_t *addr, net_packet_t *packet) +{ + UDPpacket sdl_packet; + IPaddress ip; + + if (addr == &net_broadcast_addr) + { + SDLNet_ResolveHost(&ip, NULL, port); + ip.host = INADDR_BROADCAST; + } + else + { + ip = *((IPaddress *) addr->handle); + } + +#if 0 + { + static int this_second_sent = 0; + static int lasttime; + + this_second_sent += packet->len + 64; + + if (I_GetTime() - lasttime > TICRATE) + { + printf("%i bytes sent in the last second\n", this_second_sent); + lasttime = I_GetTime(); + this_second_sent = 0; + } + } +#endif + +#ifdef DROP_PACKETS + if ((rand() % 4) == 0) + return; +#endif + + sdl_packet.channel = 0; + sdl_packet.data = packet->data; + sdl_packet.len = packet->len; + sdl_packet.address = ip; + + if (!SDLNet_UDP_Send(udpsocket, -1, &sdl_packet)) + { + I_Error("NET_SDL_SendPacket: Error transmitting packet: %s", + SDLNet_GetError()); + } +} + +static boolean NET_SDL_RecvPacket(net_addr_t **addr, net_packet_t **packet) +{ + int result; + + result = SDLNet_UDP_Recv(udpsocket, recvpacket); + + if (result < 0) + { + I_Error("NET_SDL_RecvPacket: Error receiving packet: %s", + SDLNet_GetError()); + } + + // no packets received + + if (result == 0) + return false; + + // Put the data into a new packet structure + + *packet = NET_NewPacket(recvpacket->len); + memcpy((*packet)->data, recvpacket->data, recvpacket->len); + (*packet)->len = recvpacket->len; + + // Address + + *addr = NET_SDL_FindAddress(&recvpacket->address); + + return true; +} + +void NET_SDL_AddrToString(net_addr_t *addr, char *buffer, int buffer_len) +{ + IPaddress *ip; + uint32_t host; + uint16_t port; + + ip = (IPaddress *) addr->handle; + host = SDLNet_Read32(&ip->host); + port = SDLNet_Read16(&ip->port); + + M_snprintf(buffer, buffer_len, "%i.%i.%i.%i", + (host >> 24) & 0xff, (host >> 16) & 0xff, + (host >> 8) & 0xff, host & 0xff); + + // If we are using the default port we just need to show the IP address, + // but otherwise we need to include the port. This is important because + // we use the string representation in the setup tool to provided an + // address to connect to. + if (port != DEFAULT_PORT) + { + char portbuf[10]; + M_snprintf(portbuf, sizeof(portbuf), ":%i", port); + M_StringConcat(buffer, portbuf, buffer_len); + } +} + +net_addr_t *NET_SDL_ResolveAddress(char *address) +{ + IPaddress ip; + char *addr_hostname; + int addr_port; + int result; + char *colon; + + colon = strchr(address, ':'); + + if (colon != NULL) + { + addr_hostname = M_StringDuplicate(address); + addr_hostname[colon - address] = '\0'; + addr_port = atoi(colon + 1); + } + else + { + addr_hostname = address; + addr_port = port; + } + + result = SDLNet_ResolveHost(&ip, addr_hostname, addr_port); + + if (addr_hostname != address) + { + free(addr_hostname); + } + + if (result) + { + // unable to resolve + + return NULL; + } + else + { + return NET_SDL_FindAddress(&ip); + } +} + +// Complete module + +net_module_t net_sdl_module = +{ + NET_SDL_InitClient, + NET_SDL_InitServer, + NET_SDL_SendPacket, + NET_SDL_RecvPacket, + NET_SDL_AddrToString, + NET_SDL_FreeAddress, + NET_SDL_ResolveAddress, +}; + diff --git a/client/src/net_sdl.h b/client/src/net_sdl.h new file mode 100644 index 0000000..c249de1 --- /dev/null +++ b/client/src/net_sdl.h @@ -0,0 +1,26 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Networking module which uses SDL_net +// + +#ifndef NET_SDL_H +#define NET_SDL_H + +#include "net_defs.h" + +extern net_module_t net_sdl_module; + +#endif /* #ifndef NET_SDL_H */ + diff --git a/client/src/net_server.c b/client/src/net_server.c new file mode 100644 index 0000000..0308b05 --- /dev/null +++ b/client/src/net_server.c @@ -0,0 +1,1925 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// Network server code +// + +#include +#include +#include +#include +#include + +#include "d_mode.h" +#include "doomtype.h" +#include "i_system.h" +#include "i_timer.h" +#include "m_argv.h" +#include "m_misc.h" +#include "net_client.h" +#include "net_common.h" +#include "net_defs.h" +#include "net_io.h" +#include "net_packet.h" +#include "net_query.h" +#include "net_server.h" +#include "net_structrw.h" +#include "sha1.h" + +// How often to refresh our registration with the master server. + +#define MASTER_REFRESH_PERIOD 20 * 60 /* 20 minutes */ + +// How often to re-resolve the address of the master server? + +#define MASTER_RESOLVE_PERIOD 8 * 60 * 60 /* 8 hours */ + +typedef enum +{ + // waiting for the game to be "launched" (key player to press the start + // button) + + SERVER_WAITING_LAUNCH, + + // game has been launched, we are waiting for all players to be ready + // so the game can start. + + SERVER_WAITING_START, + + // in a game + + SERVER_IN_GAME, +} net_server_state_t; + +typedef struct +{ + boolean active; + int player_number; + net_addr_t *addr; + net_connection_t connection; + int last_send_time; + char *name; + + // If true, the client has sent the NET_PACKET_TYPE_GAMESTART + // message indicating that it is ready for the game to start. + + boolean ready; + + // Time that this client connected to the server. + // This is used to determine the controller (oldest client). + + unsigned int connect_time; + + // Last time new gamedata was received from this client + + int last_gamedata_time; + + // recording a demo without -longtics + + boolean recording_lowres; + + // send queue: items to send to the client + // this is a circular buffer + + int sendseq; + net_full_ticcmd_t sendqueue[BACKUPTICS]; + + // Latest acknowledged by the client + + unsigned int acknowledged; + + // Value of max_players specified by the client on connect. + + int max_players; + + // Observer: receives data but does not participate in the game. + + boolean drone; + + // SHA1 hash sums of the client's WAD directory + + sha1_digest_t wad_sha1sum; + + // Is this client is playing with the Freedoom IWAD? + + unsigned int is_freedoom; + + // Player class (for Hexen) + + int player_class; + +} net_client_t; + +// structure used for the recv window + +typedef struct +{ + // Whether this tic has been received yet + + boolean active; + + // Latency value received from the client + + signed int latency; + + // Last time we sent a resend request for this tic + + unsigned int resend_time; + + // Tic data itself + + net_ticdiff_t diff; +} net_client_recv_t; + +static net_server_state_t server_state; +static boolean server_initialized = false; +static net_client_t clients[MAXNETNODES]; +static net_client_t *sv_players[NET_MAXPLAYERS]; +static net_context_t *server_context; +static unsigned int sv_gamemode; +static unsigned int sv_gamemission; +static net_gamesettings_t sv_settings; + +// For registration with master server: + +static net_addr_t *master_server = NULL; +static unsigned int master_refresh_time; +static unsigned int master_resolve_time; + +// receive window + +static unsigned int recvwindow_start; +static net_client_recv_t recvwindow[BACKUPTICS][NET_MAXPLAYERS]; + +#define NET_SV_ExpandTicNum(b) NET_ExpandTicNum(recvwindow_start, (b)) + +static void NET_SV_DisconnectClient(net_client_t *client) +{ + if (client->active) + { + NET_Conn_Disconnect(&client->connection); + } +} + +static boolean ClientConnected(net_client_t *client) +{ + // Check that the client is properly connected: ie. not in the + // process of connecting or disconnecting + + return client->active + && client->connection.state == NET_CONN_STATE_CONNECTED; +} + +// Send a message to be displayed on a client's console + +static void NET_SV_SendConsoleMessage(net_client_t *client, char *s, ...) +{ + char buf[1024]; + va_list args; + net_packet_t *packet; + + va_start(args, s); + M_vsnprintf(buf, sizeof(buf), s, args); + va_end(args); + + packet = NET_Conn_NewReliable(&client->connection, + NET_PACKET_TYPE_CONSOLE_MESSAGE); + + NET_WriteString(packet, buf); +} + +// Send a message to all clients + +static void NET_SV_BroadcastMessage(char *s, ...) +{ + char buf[1024]; + va_list args; + int i; + + va_start(args, s); + M_vsnprintf(buf, sizeof(buf), s, args); + va_end(args); + + for (i=0; iplayer_number = pl; + ++pl; + } + else + { + clients[i].player_number = -1; + } + } + } + + for (; plconnect_time) + { + best = &clients[i]; + } + } + + return best; +} + +static void NET_SV_SendWaitingData(net_client_t *client) +{ + net_waitdata_t wait_data; + net_packet_t *packet; + net_client_t *controller; + int i; + + NET_SV_AssignPlayers(); + + controller = NET_SV_Controller(); + + wait_data.num_players = NET_SV_NumPlayers(); + wait_data.num_drones = NET_SV_NumDrones(); + wait_data.ready_players = NET_SV_NumReadyPlayers(); + wait_data.max_players = NET_SV_MaxPlayers(); + wait_data.is_controller = (client == controller); + wait_data.consoleplayer = client->player_number; + + // Send the WAD checksums of the controlling client. + // If no controller found (?), send the details that the client + // is expecting anyway. + + if (controller == NULL) + { + controller = client; + } + + memcpy(&wait_data.wad_sha1sum, &controller->wad_sha1sum, + sizeof(sha1_digest_t)); + wait_data.is_freedoom = controller->is_freedoom; + + // set name and address of each player: + + for (i = 0; i < wait_data.num_players; ++i) + { + M_StringCopy(wait_data.player_names[i], + sv_players[i]->name, + MAXPLAYERNAME); + M_StringCopy(wait_data.player_addrs[i], + NET_AddrToString(sv_players[i]->addr), + MAXPLAYERNAME); + } + + // Construct packet: + + packet = NET_NewPacket(10); + NET_WriteInt16(packet, NET_PACKET_TYPE_WAITING_DATA); + NET_WriteWaitData(packet, &wait_data); + + // Send packet to client and free + + NET_Conn_SendPacket(&client->connection, packet); + NET_FreePacket(packet); +} + +// Find the latest tic which has been acknowledged as received by +// all clients. + +static unsigned int NET_SV_LatestAcknowledged(void) +{ + unsigned int lowtic = UINT_MAX; + int i; + + for (i=0; iactive = true; + client->connect_time = I_GetTimeMS(); + NET_Conn_InitServer(&client->connection, addr); + client->addr = addr; + client->last_send_time = -1; + client->name = M_StringDuplicate(player_name); + + // init the ticcmd send queue + + client->sendseq = 0; + client->acknowledged = 0; + client->drone = false; + client->ready = false; + + client->last_gamedata_time = 0; + + memset(client->sendqueue, 0xff, sizeof(client->sendqueue)); +} + +// parse a SYN from a client(initiating a connection) + +static void NET_SV_ParseSYN(net_packet_t *packet, + net_client_t *client, + net_addr_t *addr) +{ + unsigned int magic; + net_connect_data_t data; + char *player_name; + char *client_version; + int i; + + // read the magic number + + if (!NET_ReadInt32(packet, &magic)) + { + return; + } + + if (magic != NET_MAGIC_NUMBER) + { + // invalid magic number + + return; + } + + // Check the client version is the same as the server + + client_version = NET_ReadString(packet); + + if (client_version == NULL) + { + return; + } + + if (strcmp(client_version, "Chocolate Doom 3") != 0) + { + //! + // @category net + // + // When running a netgame server, ignore version mismatches between + // the server and the client. Using this option may cause game + // desyncs to occur, or differences in protocol may mean the netgame + // will simply not function at all. + // + + if (M_CheckParm("-ignoreversion") == 0) + { + NET_SV_SendReject(addr, + "Different " "Chocolate Doom" " versions cannot play a net game!\n" + "Version mismatch: server version is: " "Chocolate Doom 3"); + return; + } + } + + // read the game mode and mission + + if (!NET_ReadConnectData(packet, &data)) + { + return; + } + + if (!D_ValidGameMode(data.gamemission, data.gamemode)) + { + return; + } + + // Check max_players value. This must be in a sensible range. + + if (data.max_players > NET_MAXPLAYERS) + { + return; + } + + // read the player's name + + player_name = NET_ReadString(packet); + + if (player_name == NULL) + { + return; + } + + // received a valid SYN + + // not accepting new connections? + + if (server_state != SERVER_WAITING_LAUNCH) + { + NET_SV_SendReject(addr, "Server is not currently accepting connections"); + return; + } + + // allocate a client slot if there isn't one already + + if (client == NULL) + { + // find a slot, or return if none found + + for (i=0; iconnection.state == NET_CONN_STATE_DISCONNECTED) + { + client->active = false; + } + } + + // New client? + + if (!client->active) + { + int num_players; + + // Before accepting a new client, check that there is a slot + // free + + NET_SV_AssignPlayers(); + num_players = NET_SV_NumPlayers(); + + if ((!data.drone && num_players >= NET_SV_MaxPlayers()) + || NET_SV_NumClients() >= MAXNETNODES) + { + NET_SV_SendReject(addr, "Server is full!"); + return; + } + + // TODO: Add server option to allow rejecting clients which + // set lowres_turn. This is potentially desirable as the + // presence of such clients affects turning resolution. + + // Adopt the game mode and mission of the first connecting client + + if (num_players == 0 && !data.drone) + { + sv_gamemode = data.gamemode; + sv_gamemission = data.gamemission; + } + + // Save the SHA1 checksums + + memcpy(client->wad_sha1sum, data.wad_sha1sum, sizeof(sha1_digest_t)); + client->is_freedoom = data.is_freedoom; + client->max_players = data.max_players; + + // Check the connecting client is playing the same game as all + // the other clients + + if (data.gamemode != sv_gamemode || data.gamemission != sv_gamemission) + { + NET_SV_SendReject(addr, "You are playing the wrong game!"); + return; + } + + // Activate, initialize connection + + NET_SV_InitNewClient(client, addr, player_name); + + client->recording_lowres = data.lowres_turn; + client->drone = data.drone; + client->player_class = data.player_class; + } + + if (client->connection.state == NET_CONN_STATE_WAITING_ACK) + { + // force an acknowledgement + client->connection.last_send_time = -1; + } +} + +// Parse a launch packet. This is sent by the key player when the "start" +// button is pressed, and causes the startup process to continue. + +static void NET_SV_ParseLaunch(net_packet_t *packet, net_client_t *client) +{ + net_packet_t *launchpacket; + int num_players; + unsigned int i; + + // Only the controller can launch the game. + + if (client != NET_SV_Controller()) + { + return; + } + + // Can only launch when we are in the waiting state. + + if (server_state != SERVER_WAITING_LAUNCH) + { + return; + } + + // Forward launch on to all clients. + + NET_SV_AssignPlayers(); + num_players = NET_SV_NumPlayers(); + + for (i=0; irecording_lowres) + { + sv_settings.lowres_turn = true; + } + } + + sv_settings.num_players = NET_SV_NumPlayers(); + + // Copy player classes: + + for (i = 0; i < NET_MAXPLAYERS; ++i) + { + if (sv_players[i] != NULL) + { + sv_settings.player_classes[i] = sv_players[i]->player_class; + } + else + { + sv_settings.player_classes[i] = 0; + } + } + + nowtime = I_GetTimeMS(); + + // Send start packets to each connected node + + for (i = 0; i < MAXNETNODES; ++i) + { + if (!ClientConnected(&clients[i])) + continue; + + clients[i].last_gamedata_time = nowtime; + + startpacket = NET_Conn_NewReliable(&clients[i].connection, + NET_PACKET_TYPE_GAMESTART); + + sv_settings.consoleplayer = clients[i].player_number; + + NET_WriteSettings(startpacket, &sv_settings); + } + + // Change server state + + server_state = SERVER_IN_GAME; + + memset(recvwindow, 0, sizeof(recvwindow)); + recvwindow_start = 0; +} + +// Returns true when all nodes have indicated readiness to start the game. + +static boolean AllNodesReady(void) +{ + unsigned int i; + + for (i = 0; i < MAXNETNODES; ++i) + { + if (ClientConnected(&clients[i]) && !clients[i].ready) + { + return false; + } + } + + return true; +} + +// Check if the game should start, and if so, start it. + +static void CheckStartGame(void) +{ + if (AllNodesReady()) + { + StartGame(); + } +} + +// Send waiting data with current status to all nodes that are ready to +// start the game. + +static void SendAllWaitingData(void) +{ + unsigned int i; + + for (i = 0; i < MAXNETNODES; ++i) + { + if (ClientConnected(&clients[i]) && clients[i].ready) + { + NET_SV_SendWaitingData(&clients[i]); + } + } +} + +// Parse a game start packet + +static void NET_SV_ParseGameStart(net_packet_t *packet, net_client_t *client) +{ + net_gamesettings_t settings; + + // Can only start a game if we are in the waiting start state. + + if (server_state != SERVER_WAITING_START) + { + return; + } + + if (client == NET_SV_Controller()) + { + if (!NET_ReadSettings(packet, &settings)) + { + // Malformed packet + + return; + } + + // Check the game settings are valid + + if (!NET_ValidGameSettings(sv_gamemode, sv_gamemission, &settings)) + { + return; + } + + sv_settings = settings; + } + + client->ready = true; + + CheckStartGame(); + + // Update all ready clients with the current state (number of players + // ready, etc.). This is used by games that show startup progress + // (eg. Hexen's spinal loading) + + SendAllWaitingData(); +} + +// Send a resend request to a client + +static void NET_SV_SendResendRequest(net_client_t *client, int start, int end) +{ + net_packet_t *packet; + net_client_recv_t *recvobj; + int i; + unsigned int nowtime; + int index; + + //printf("SV: send resend for %i-%i\n", start, end); + + packet = NET_NewPacket(20); + + NET_WriteInt16(packet, NET_PACKET_TYPE_GAMEDATA_RESEND); + NET_WriteInt32(packet, start); + NET_WriteInt8(packet, end - start + 1); + + NET_Conn_SendPacket(&client->connection, packet); + NET_FreePacket(packet); + + // Store the time we send the resend request + + nowtime = I_GetTimeMS(); + + for (i=start; i<=end; ++i) + { + index = i - recvwindow_start; + + if (index >= BACKUPTICS) + { + // Outside the range + + continue; + } + + recvobj = &recvwindow[index][client->player_number]; + + recvobj->resend_time = nowtime; + } +} + +// Check for expired resend requests + +static void NET_SV_CheckResends(net_client_t *client) +{ + int i; + int player; + int resend_start, resend_end; + unsigned int nowtime; + + nowtime = I_GetTimeMS(); + + player = client->player_number; + resend_start = -1; + resend_end = -1; + + for (i=0; iactive + && recvobj->resend_time != 0 + && nowtime > recvobj->resend_time + 300; + + if (need_resend) + { + // Start a new run of resend tics? + + if (resend_start < 0) + { + resend_start = i; + } + + resend_end = i; + } + else + { + if (resend_start >= 0) + { + // End of a run of resend tics + + //printf("SV: resend request timed out: %i-%i\n", resend_start, resend_end); + NET_SV_SendResendRequest(client, + recvwindow_start + resend_start, + recvwindow_start + resend_end); + + resend_start = -1; + } + } + } + + if (resend_start >= 0) + { + NET_SV_SendResendRequest(client, + recvwindow_start + resend_start, + recvwindow_start + resend_end); + } +} + +// Process game data from a client + +static void NET_SV_ParseGameData(net_packet_t *packet, net_client_t *client) +{ + net_client_recv_t *recvobj; + unsigned int seq; + unsigned int ackseq; + unsigned int num_tics; + unsigned int nowtime; + size_t i; + int player; + int resend_start, resend_end; + int index; + + if (server_state != SERVER_IN_GAME) + { + return; + } + + if (client->drone) + { + // Drones do not contribute any game data. + return; + } + + player = client->player_number; + + // Read header + + if (!NET_ReadInt8(packet, &ackseq) + || !NET_ReadInt8(packet, &seq) + || !NET_ReadInt8(packet, &num_tics)) + { + return; + } + + // Get the current time + + nowtime = I_GetTimeMS(); + + // Expand 8-bit values to the full sequence number + + ackseq = NET_SV_ExpandTicNum(ackseq); + seq = NET_SV_ExpandTicNum(seq); + + // Sanity checks + + for (i=0; i= BACKUPTICS) + { + // Not in range of the recv window + + continue; + } + + recvobj = &recvwindow[index][player]; + recvobj->active = true; + recvobj->diff = diff; + recvobj->latency = latency; + + client->last_gamedata_time = nowtime; + } + + // Higher acknowledgement point? + + if (ackseq > client->acknowledged) + { + client->acknowledged = ackseq; + } + + // Has this been received out of sequence, ie. have we not received + // all tics before the first tic in this packet? If so, send a + // resend request. + + //printf("SV: %p: %i\n", client, seq); + + resend_end = seq - recvwindow_start; + + if (resend_end <= 0) + return; + + if (resend_end >= BACKUPTICS) + resend_end = BACKUPTICS - 1; + + index = resend_end - 1; + resend_start = resend_end; + + while (index >= 0) + { + recvobj = &recvwindow[index][player]; + + if (recvobj->active) + { + // ended our run of unreceived tics + + break; + } + + if (recvobj->resend_time != 0) + { + // Already sent a resend request for this tic + + break; + } + + resend_start = index; + --index; + } + + // Possibly send a resend request + + if (resend_start < resend_end) + { + /* + printf("missed %i-%i before %i, send resend\n", + recvwindow_start + resend_start, + recvwindow_start + resend_end - 1, + seq); + */ + NET_SV_SendResendRequest(client, + recvwindow_start + resend_start, + recvwindow_start + resend_end - 1); + } +} + +static void NET_SV_ParseGameDataACK(net_packet_t *packet, net_client_t *client) +{ + unsigned int ackseq; + + if (server_state != SERVER_IN_GAME) + { + return; + } + + // Read header + + if (!NET_ReadInt8(packet, &ackseq)) + { + return; + } + + // Expand 8-bit values to the full sequence number + + ackseq = NET_SV_ExpandTicNum(ackseq); + + // Higher acknowledgement point than we already have? + + if (ackseq > client->acknowledged) + { + client->acknowledged = ackseq; + } +} + +static void NET_SV_SendTics(net_client_t *client, + unsigned int start, unsigned int end) +{ + net_packet_t *packet; + unsigned int i; + + packet = NET_NewPacket(500); + + NET_WriteInt16(packet, NET_PACKET_TYPE_GAMEDATA); + + // Send the start tic and number of tics + + NET_WriteInt8(packet, start & 0xff); + NET_WriteInt8(packet, end-start + 1); + + // Write the tics + + for (i=start; i<=end; ++i) + { + net_full_ticcmd_t *cmd; + + cmd = &client->sendqueue[i % BACKUPTICS]; + + if (i != cmd->seq) + { + I_Error("Wanted to send %i, but %i is in its place", i, cmd->seq); + } + + // Add command + + NET_WriteFullTiccmd(packet, cmd, sv_settings.lowres_turn); + } + + // Send packet + + NET_Conn_SendPacket(&client->connection, packet); + + NET_FreePacket(packet); +} + +// Parse a retransmission request from a client + +static void NET_SV_ParseResendRequest(net_packet_t *packet, net_client_t *client) +{ + unsigned int start, last; + unsigned int num_tics; + unsigned int i; + + // Read the starting tic and number of tics + + if (!NET_ReadInt32(packet, &start) + || !NET_ReadInt8(packet, &num_tics)) + { + return; + } + + //printf("SV: %p: resend %i-%i\n", client, start, start+num_tics-1); + + // Check we have all the requested tics + + last = start + num_tics - 1; + + for (i=start; i<=last; ++i) + { + net_full_ticcmd_t *cmd; + + cmd = &client->sendqueue[i % BACKUPTICS]; + + if (i != cmd->seq) + { + // We do not have the requested tic (any more) + // This is pretty fatal. We could disconnect the client, + // but then again this could be a spoofed packet. Just + // ignore it. + + return; + } + } + + // Resend those tics + + NET_SV_SendTics(client, start, last); +} + +// Send a response back to the client + +void NET_SV_SendQueryResponse(net_addr_t *addr) +{ + net_packet_t *reply; + net_querydata_t querydata; + int p; + + // Version + + querydata.version = "Chocolate Doom 3"; + + // Server state + + querydata.server_state = server_state; + + // Number of players/maximum players + + querydata.num_players = NET_SV_NumPlayers(); + querydata.max_players = NET_SV_MaxPlayers(); + + // Game mode/mission + + querydata.gamemode = sv_gamemode; + querydata.gamemission = sv_gamemission; + + //! + // @category net + // @arg + // + // When starting a network server, specify a name for the server. + // + + p = M_CheckParmWithArgs("-servername", 1); + + if (p > 0) + { + querydata.description = myargv[p + 1]; + } + else + { + querydata.description = "Unnamed server"; + } + + // Send it and we're done. + + reply = NET_NewPacket(64); + NET_WriteInt16(reply, NET_PACKET_TYPE_QUERY_RESPONSE); + NET_WriteQueryData(reply, &querydata); + NET_SendPacket(addr, reply); + NET_FreePacket(reply); +} + +// Process a packet received by the server + +static void NET_SV_Packet(net_packet_t *packet, net_addr_t *addr) +{ + net_client_t *client; + unsigned int packet_type; + + // Response from master server? + + if (addr != NULL && addr == master_server) + { + NET_Query_MasterResponse(packet); + return; + } + + // Find which client this packet came from + + client = NET_SV_FindClient(addr); + + // Read the packet type + + if (!NET_ReadInt16(packet, &packet_type)) + { + // no packet type + + return; + } + + if (packet_type == NET_PACKET_TYPE_SYN) + { + NET_SV_ParseSYN(packet, client, addr); + } + else if (packet_type == NET_PACKET_TYPE_QUERY) + { + NET_SV_SendQueryResponse(addr); + } + else if (client == NULL) + { + // Must come from a valid client; ignore otherwise + } + else if (NET_Conn_Packet(&client->connection, packet, &packet_type)) + { + // Packet was eaten by the common connection code + } + else + { + //printf("SV: %s: %i\n", NET_AddrToString(addr), packet_type); + + switch (packet_type) + { + case NET_PACKET_TYPE_GAMESTART: + NET_SV_ParseGameStart(packet, client); + break; + case NET_PACKET_TYPE_LAUNCH: + NET_SV_ParseLaunch(packet, client); + break; + case NET_PACKET_TYPE_GAMEDATA: + NET_SV_ParseGameData(packet, client); + break; + case NET_PACKET_TYPE_GAMEDATA_ACK: + NET_SV_ParseGameDataACK(packet, client); + break; + case NET_PACKET_TYPE_GAMEDATA_RESEND: + NET_SV_ParseResendRequest(packet, client); + break; + default: + // unknown packet type + + break; + } + } + + // If this address is not in the list of clients, be sure to + // free it back. + + if (NET_SV_FindClient(addr) == NULL) + { + NET_FreeAddress(addr); + } +} + + +static void NET_SV_PumpSendQueue(net_client_t *client) +{ + net_full_ticcmd_t cmd; + int recv_index; + int num_players; + int i; + int starttic, endtic; + + // If a client has not sent any acknowledgments for a while, + // wait until they catch up. + + if (client->sendseq - NET_SV_LatestAcknowledged() > 40) + { + return; + } + + // Work out the index into the receive window + + recv_index = client->sendseq - recvwindow_start; + + if (recv_index < 0 || recv_index >= BACKUPTICS) + { + return; + } + + // Check if we can generate a new entry for the send queue + // using the data in recvwindow. + + num_players = 0; + + for (i=0; isendseq > recvwindow_start + 10) + { + return; + } + + //printf("SV: have complete ticcmd for %i\n", client->sendseq); + + // We have all data we need to generate a command for this tic. + + cmd.seq = client->sendseq; + + // Add ticcmds from all players + + cmd.latency = 0; + + for (i=0; idiff; + + if (recvobj->latency > cmd.latency) + cmd.latency = recvobj->latency; + } + + //printf("SV: %i: latency %i\n", client->player_number, cmd.latency); + + // Add into the queue + + client->sendqueue[client->sendseq % BACKUPTICS] = cmd; + + // Transmit the new tic to the client + + starttic = client->sendseq - sv_settings.extratics; + endtic = client->sendseq; + + if (starttic < 0) + starttic = 0; + + NET_SV_SendTics(client, starttic, endtic); + + ++client->sendseq; +} + +// Prevent against deadlock: resend requests are usually only +// triggered if we miss a packet and receive the next one. +// If we miss a whole load of packets, we can end up in a +// deadlock situation where the client will not send any more. +// If we don't receive any game data in a while, trigger a resend +// request for the next tic we're expecting. + +void NET_SV_CheckDeadlock(net_client_t *client) +{ + int nowtime; + int i; + + // Don't expect game data from clients. + + if (client->drone) + { + return; + } + + nowtime = I_GetTimeMS(); + + // If we haven't received anything for a long time, it may be a deadlock. + + if (nowtime - client->last_gamedata_time > 1000) + { + // Search the receive window for the first tic we are expecting + // from this player. + + for (i=0; iplayer_number][i].active) + { + //printf("Possible deadlock: Sending resend request\n"); + + // Found a tic we haven't received. Send a resend request. + + NET_SV_SendResendRequest(client, + recvwindow_start + i, + recvwindow_start + i + 5); + + client->last_gamedata_time = nowtime; + break; + } + } + } +} + +// Called when all players have disconnected. Return to listening for +// players to start a new game, and disconnect any drones still connected. + +static void NET_SV_GameEnded(void) +{ + int i; + + server_state = SERVER_WAITING_LAUNCH; + sv_gamemode = indetermined; + + for (i=0; iconnection); + + if (client->connection.state == NET_CONN_STATE_DISCONNECTED + && client->connection.disconnect_reason == NET_DISCONNECT_TIMEOUT) + { + NET_SV_BroadcastMessage("Client '%s' timed out and disconnected", + client->name); + } + + // Is this client disconnected? + + if (client->connection.state == NET_CONN_STATE_DISCONNECTED) + { + client->active = false; + + // If we were about to start a game, any player disconnecting + // should cause an abort. + + if (server_state == SERVER_WAITING_START && !client->drone) + { + NET_SV_BroadcastMessage("Game startup aborted because " + "player '%s' disconnected.", + client->name); + NET_SV_GameEnded(); + } + + free(client->name); + NET_FreeAddress(client->addr); + + // Are there any clients left connected? If not, return the + // server to the waiting-for-players state. + // + // Disconnect any drones still connected. + + if (NET_SV_NumPlayers() <= 0) + { + NET_SV_GameEnded(); + } + } + + if (!ClientConnected(client)) + { + // client has not yet finished connecting + + return; + } + + if (server_state == SERVER_WAITING_LAUNCH) + { + // Waiting for the game to start + + // Send information once every second + + if (client->last_send_time < 0 + || I_GetTimeMS() - client->last_send_time > 1000) + { + NET_SV_SendWaitingData(client); + client->last_send_time = I_GetTimeMS(); + } + } + + if (server_state == SERVER_IN_GAME) + { + NET_SV_PumpSendQueue(client); + NET_SV_CheckDeadlock(client); + } +} + +// Add a network module to the server context + +void NET_SV_AddModule(net_module_t *module) +{ + module->InitServer(); + NET_AddModule(server_context, module); +} + +// Initialize server and wait for connections + +void NET_SV_Init(void) +{ + int i; + + // initialize send/receive context + + server_context = NET_NewContext(); + + // no clients yet + + for (i=0; i MASTER_RESOLVE_PERIOD * 1000) + { + net_addr_t *new_addr; + + new_addr = NET_Query_ResolveMaster(server_context); + + // Has the master server changed address? + + if (new_addr != NULL && new_addr != master_server) + { + NET_FreeAddress(master_server); + master_server = new_addr; + } + + master_resolve_time = now; + } + + // Possibly refresh our registration with the master server. + + if (now - master_refresh_time > MASTER_REFRESH_PERIOD * 1000) + { + NET_Query_AddToMaster(master_server); + master_refresh_time = now; + } +} + +void NET_SV_RegisterWithMaster(void) +{ + //! + // @category net + // + // When running a server, don't register with the global master server. + // Implies -server. + // + + if (!M_CheckParm("-privateserver")) + { + master_server = NET_Query_ResolveMaster(server_context); + } + else + { + master_server = NULL; + } + + // Send request. + + if (master_server != NULL) + { + NET_Query_AddToMaster(master_server); + master_refresh_time = I_GetTimeMS(); + master_resolve_time = master_refresh_time; + } +} + +// Run server code to check for new packets/send packets as the server +// requires + +void NET_SV_Run(void) +{ + net_addr_t *addr; + net_packet_t *packet; + int i; + + if (!server_initialized) + { + return; + } + + while (NET_RecvPacket(server_context, &addr, &packet)) + { + NET_SV_Packet(packet, addr); + NET_FreePacket(packet); + } + + if (master_server != NULL) + { + UpdateMasterServer(); + } + + // "Run" any clients that may have things to do, independent of responses + // to received packets + + for (i=0; i 5000) + { + running = false; + fprintf(stderr, "SV: Timed out waiting for clients to disconnect.\n"); + } + + // Run the client code in case this is a loopback client. + + NET_CL_Run(); + NET_SV_Run(); + + // Don't hog the CPU + + I_Sleep(1); + } +} diff --git a/client/src/net_server.h b/client/src/net_server.h new file mode 100644 index 0000000..d144107 --- /dev/null +++ b/client/src/net_server.h @@ -0,0 +1,43 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// Network server code +// + +#ifndef NET_SERVER_H +#define NET_SERVER_H + +#include "net_defs.h" +// initialize server and wait for connections + +void NET_SV_Init(void); + +// run server: check for new packets received etc. + +void NET_SV_Run(void); + +// Shut down the server +// Blocks until all clients disconnect, or until a 5 second timeout + +void NET_SV_Shutdown(void); + +// Add a network module to the context used by the server + +void NET_SV_AddModule(net_module_t *module); + +// Register server with master server. + +void NET_SV_RegisterWithMaster(void); + +#endif /* #ifndef NET_SERVER_H */ + diff --git a/client/src/net_structrw.c b/client/src/net_structrw.c new file mode 100644 index 0000000..9153224 --- /dev/null +++ b/client/src/net_structrw.c @@ -0,0 +1,568 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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. +// +// Reading and writing various structures into packets +// + +#include +#include +#include +#include + +#include "doomtype.h" +#include "m_misc.h" +#include "net_packet.h" +#include "net_structrw.h" + +void NET_WriteConnectData(net_packet_t *packet, net_connect_data_t *data) +{ + NET_WriteInt8(packet, data->gamemode); + NET_WriteInt8(packet, data->gamemission); + NET_WriteInt8(packet, data->lowres_turn); + NET_WriteInt8(packet, data->drone); + NET_WriteInt8(packet, data->max_players); + NET_WriteInt8(packet, data->is_freedoom); + NET_WriteSHA1Sum(packet, data->wad_sha1sum); + NET_WriteInt8(packet, data->player_class); +} + +boolean NET_ReadConnectData(net_packet_t *packet, net_connect_data_t *data) +{ + return NET_ReadInt8(packet, (unsigned int *) &data->gamemode) + && NET_ReadInt8(packet, (unsigned int *) &data->gamemission) + && NET_ReadInt8(packet, (unsigned int *) &data->lowres_turn) + && NET_ReadInt8(packet, (unsigned int *) &data->drone) + && NET_ReadInt8(packet, (unsigned int *) &data->max_players) + && NET_ReadInt8(packet, (unsigned int *) &data->is_freedoom) + && NET_ReadSHA1Sum(packet, data->wad_sha1sum) + && NET_ReadInt8(packet, (unsigned int *) &data->player_class); +} + +void NET_WriteSettings(net_packet_t *packet, net_gamesettings_t *settings) +{ + int i; + + NET_WriteInt8(packet, settings->ticdup); + NET_WriteInt8(packet, settings->extratics); + NET_WriteInt8(packet, settings->deathmatch); + NET_WriteInt8(packet, settings->nomonsters); + NET_WriteInt8(packet, settings->fast_monsters); + NET_WriteInt8(packet, settings->respawn_monsters); + NET_WriteInt8(packet, settings->episode); + NET_WriteInt8(packet, settings->map); + NET_WriteInt8(packet, settings->skill); + NET_WriteInt8(packet, settings->gameversion); + NET_WriteInt8(packet, settings->lowres_turn); + NET_WriteInt8(packet, settings->new_sync); + NET_WriteInt32(packet, settings->timelimit); + NET_WriteInt8(packet, settings->loadgame); + NET_WriteInt8(packet, settings->random); + NET_WriteInt8(packet, settings->num_players); + NET_WriteInt8(packet, settings->consoleplayer); + + for (i = 0; i < settings->num_players; ++i) + { + NET_WriteInt8(packet, settings->player_classes[i]); + } +} + +boolean NET_ReadSettings(net_packet_t *packet, net_gamesettings_t *settings) +{ + boolean success; + int i; + + success = NET_ReadInt8(packet, (unsigned int *) &settings->ticdup) + && NET_ReadInt8(packet, (unsigned int *) &settings->extratics) + && NET_ReadInt8(packet, (unsigned int *) &settings->deathmatch) + && NET_ReadInt8(packet, (unsigned int *) &settings->nomonsters) + && NET_ReadInt8(packet, (unsigned int *) &settings->fast_monsters) + && NET_ReadInt8(packet, (unsigned int *) &settings->respawn_monsters) + && NET_ReadInt8(packet, (unsigned int *) &settings->episode) + && NET_ReadInt8(packet, (unsigned int *) &settings->map) + && NET_ReadSInt8(packet, &settings->skill) + && NET_ReadInt8(packet, (unsigned int *) &settings->gameversion) + && NET_ReadInt8(packet, (unsigned int *) &settings->lowres_turn) + && NET_ReadInt8(packet, (unsigned int *) &settings->new_sync) + && NET_ReadInt32(packet, (unsigned int *) &settings->timelimit) + && NET_ReadSInt8(packet, (signed int *) &settings->loadgame) + && NET_ReadInt8(packet, (unsigned int *) &settings->random) + && NET_ReadInt8(packet, (unsigned int *) &settings->num_players) + && NET_ReadSInt8(packet, (signed int *) &settings->consoleplayer); + + if (!success) + { + return false; + } + + for (i = 0; i < settings->num_players; ++i) + { + if (!NET_ReadInt8(packet, + (unsigned int *) &settings->player_classes[i])) + { + return false; + } + } + + return true; +} + +boolean NET_ReadQueryData(net_packet_t *packet, net_querydata_t *query) +{ + boolean result; + + query->version = NET_ReadString(packet); + + result = query->version != NULL + && NET_ReadInt8(packet, (unsigned int *) &query->server_state) + && NET_ReadInt8(packet, (unsigned int *) &query->num_players) + && NET_ReadInt8(packet, (unsigned int *) &query->max_players) + && NET_ReadInt8(packet, (unsigned int *) &query->gamemode) + && NET_ReadInt8(packet, (unsigned int *) &query->gamemission); + + if (result) + { + query->description = NET_ReadString(packet); + + return query->description != NULL; + } + else + { + return false; + } +} + +void NET_WriteQueryData(net_packet_t *packet, net_querydata_t *query) +{ + NET_WriteString(packet, query->version); + NET_WriteInt8(packet, query->server_state); + NET_WriteInt8(packet, query->num_players); + NET_WriteInt8(packet, query->max_players); + NET_WriteInt8(packet, query->gamemode); + NET_WriteInt8(packet, query->gamemission); + NET_WriteString(packet, query->description); +} + +void NET_WriteTiccmdDiff(net_packet_t *packet, net_ticdiff_t *diff, + boolean lowres_turn) +{ + // Header + + NET_WriteInt8(packet, diff->diff); + + // Write the fields which are enabled: + + if (diff->diff & NET_TICDIFF_FORWARD) + NET_WriteInt8(packet, diff->cmd.forwardmove); + if (diff->diff & NET_TICDIFF_SIDE) + NET_WriteInt8(packet, diff->cmd.sidemove); + if (diff->diff & NET_TICDIFF_TURN) + { + if (lowres_turn) + { + NET_WriteInt8(packet, diff->cmd.angleturn / 256); + } + else + { + NET_WriteInt16(packet, diff->cmd.angleturn); + } + } + if (diff->diff & NET_TICDIFF_BUTTONS) + NET_WriteInt8(packet, diff->cmd.buttons); + if (diff->diff & NET_TICDIFF_CONSISTANCY) + NET_WriteInt8(packet, diff->cmd.consistancy); + if (diff->diff & NET_TICDIFF_CHATCHAR) + NET_WriteInt8(packet, diff->cmd.chatchar); + if (diff->diff & NET_TICDIFF_RAVEN) + { + NET_WriteInt8(packet, diff->cmd.lookfly); + NET_WriteInt8(packet, diff->cmd.arti); + } + if (diff->diff & NET_TICDIFF_STRIFE) + { + NET_WriteInt8(packet, diff->cmd.buttons2); + NET_WriteInt16(packet, diff->cmd.inventory); + } +} + +boolean NET_ReadTiccmdDiff(net_packet_t *packet, net_ticdiff_t *diff, + boolean lowres_turn) +{ + unsigned int val; + signed int sval; + + // Read header + + if (!NET_ReadInt8(packet, &diff->diff)) + return false; + + // Read fields + + if (diff->diff & NET_TICDIFF_FORWARD) + { + if (!NET_ReadSInt8(packet, &sval)) + return false; + diff->cmd.forwardmove = sval; + } + + if (diff->diff & NET_TICDIFF_SIDE) + { + if (!NET_ReadSInt8(packet, &sval)) + return false; + diff->cmd.sidemove = sval; + } + + if (diff->diff & NET_TICDIFF_TURN) + { + if (lowres_turn) + { + if (!NET_ReadSInt8(packet, &sval)) + return false; + diff->cmd.angleturn = sval * 256; + } + else + { + if (!NET_ReadSInt16(packet, &sval)) + return false; + diff->cmd.angleturn = sval; + } + } + + if (diff->diff & NET_TICDIFF_BUTTONS) + { + if (!NET_ReadInt8(packet, &val)) + return false; + diff->cmd.buttons = val; + } + + if (diff->diff & NET_TICDIFF_CONSISTANCY) + { + if (!NET_ReadInt8(packet, &val)) + return false; + diff->cmd.consistancy = val; + } + + if (diff->diff & NET_TICDIFF_CHATCHAR) + { + if (!NET_ReadInt8(packet, &val)) + return false; + diff->cmd.chatchar = val; + } + + if (diff->diff & NET_TICDIFF_RAVEN) + { + if (!NET_ReadInt8(packet, &val)) + return false; + diff->cmd.lookfly = val; + + if (!NET_ReadInt8(packet, &val)) + return false; + diff->cmd.arti = val; + } + + if (diff->diff & NET_TICDIFF_STRIFE) + { + if (!NET_ReadInt8(packet, &val)) + return false; + diff->cmd.buttons2 = val; + + if (!NET_ReadInt16(packet, &val)) + return false; + diff->cmd.inventory = val; + } + + return true; +} + +void NET_TiccmdDiff(ticcmd_t *tic1, ticcmd_t *tic2, net_ticdiff_t *diff) +{ + diff->diff = 0; + diff->cmd = *tic2; + + if (tic1->forwardmove != tic2->forwardmove) + diff->diff |= NET_TICDIFF_FORWARD; + if (tic1->sidemove != tic2->sidemove) + diff->diff |= NET_TICDIFF_SIDE; + if (tic1->angleturn != tic2->angleturn) + diff->diff |= NET_TICDIFF_TURN; + if (tic1->buttons != tic2->buttons) + diff->diff |= NET_TICDIFF_BUTTONS; + if (tic1->consistancy != tic2->consistancy) + diff->diff |= NET_TICDIFF_CONSISTANCY; + if (tic2->chatchar != 0) + diff->diff |= NET_TICDIFF_CHATCHAR; + + // Heretic/Hexen-specific + + if (tic1->lookfly != tic2->lookfly || tic2->arti != 0) + diff->diff |= NET_TICDIFF_RAVEN; + + // Strife-specific + + if (tic1->buttons2 != tic2->buttons2 || tic2->inventory != 0) + diff->diff |= NET_TICDIFF_STRIFE; +} + +void NET_TiccmdPatch(ticcmd_t *src, net_ticdiff_t *diff, ticcmd_t *dest) +{ + memmove(dest, src, sizeof(ticcmd_t)); + + // Apply the diff + + if (diff->diff & NET_TICDIFF_FORWARD) + dest->forwardmove = diff->cmd.forwardmove; + if (diff->diff & NET_TICDIFF_SIDE) + dest->sidemove = diff->cmd.sidemove; + if (diff->diff & NET_TICDIFF_TURN) + dest->angleturn = diff->cmd.angleturn; + if (diff->diff & NET_TICDIFF_BUTTONS) + dest->buttons = diff->cmd.buttons; + if (diff->diff & NET_TICDIFF_CONSISTANCY) + dest->consistancy = diff->cmd.consistancy; + + if (diff->diff & NET_TICDIFF_CHATCHAR) + dest->chatchar = diff->cmd.chatchar; + else + dest->chatchar = 0; + + // Heretic/Hexen specific: + + if (diff->diff & NET_TICDIFF_RAVEN) + { + dest->lookfly = diff->cmd.lookfly; + dest->arti = diff->cmd.arti; + } + else + { + dest->arti = 0; + } + + // Strife-specific: + + if (diff->diff & NET_TICDIFF_STRIFE) + { + dest->buttons2 = diff->cmd.buttons2; + dest->inventory = diff->cmd.inventory; + } + else + { + dest->inventory = 0; + } +} + +// +// net_full_ticcmd_t +// + +boolean NET_ReadFullTiccmd(net_packet_t *packet, net_full_ticcmd_t *cmd, boolean lowres_turn) +{ + unsigned int bitfield; + int i; + + // Latency + + if (!NET_ReadSInt16(packet, &cmd->latency)) + { + return false; + } + + // Regenerate playeringame from the "header" bitfield + + if (!NET_ReadInt8(packet, &bitfield)) + { + return false; + } + + for (i=0; iplayeringame[i] = (bitfield & (1 << i)) != 0; + } + + // Read cmds + + for (i=0; iplayeringame[i]) + { + if (!NET_ReadTiccmdDiff(packet, &cmd->cmds[i], lowres_turn)) + { + return false; + } + } + } + + return true; +} + +void NET_WriteFullTiccmd(net_packet_t *packet, net_full_ticcmd_t *cmd, boolean lowres_turn) +{ + unsigned int bitfield; + int i; + + // Write the latency + + NET_WriteInt16(packet, cmd->latency); + + // Write "header" byte indicating which players are active + // in this ticcmd + + bitfield = 0; + + for (i=0; iplayeringame[i]) + { + bitfield |= 1 << i; + } + } + + NET_WriteInt8(packet, bitfield); + + // Write player ticcmds + + for (i=0; iplayeringame[i]) + { + NET_WriteTiccmdDiff(packet, &cmd->cmds[i], lowres_turn); + } + } +} + +void NET_WriteWaitData(net_packet_t *packet, net_waitdata_t *data) +{ + int i; + + NET_WriteInt8(packet, data->num_players); + NET_WriteInt8(packet, data->num_drones); + NET_WriteInt8(packet, data->ready_players); + NET_WriteInt8(packet, data->max_players); + NET_WriteInt8(packet, data->is_controller); + NET_WriteInt8(packet, data->consoleplayer); + + for (i = 0; i < data->num_players && i < NET_MAXPLAYERS; ++i) + { + NET_WriteString(packet, data->player_names[i]); + NET_WriteString(packet, data->player_addrs[i]); + } + + NET_WriteSHA1Sum(packet, data->wad_sha1sum); + NET_WriteInt8(packet, data->is_freedoom); +} + +boolean NET_ReadWaitData(net_packet_t *packet, net_waitdata_t *data) +{ + int i; + char *s; + + if (!NET_ReadInt8(packet, (unsigned int *) &data->num_players) + || !NET_ReadInt8(packet, (unsigned int *) &data->num_drones) + || !NET_ReadInt8(packet, (unsigned int *) &data->ready_players) + || !NET_ReadInt8(packet, (unsigned int *) &data->max_players) + || !NET_ReadInt8(packet, (unsigned int *) &data->is_controller) + || !NET_ReadSInt8(packet, &data->consoleplayer)) + { + return false; + } + + for (i = 0; i < data->num_players && i < NET_MAXPLAYERS; ++i) + { + s = NET_ReadString(packet); + + if (s == NULL || strlen(s) >= MAXPLAYERNAME) + { + return false; + } + + M_StringCopy(data->player_names[i], s, MAXPLAYERNAME); + + s = NET_ReadString(packet); + + if (s == NULL || strlen(s) >= MAXPLAYERNAME) + { + return false; + } + + M_StringCopy(data->player_addrs[i], s, MAXPLAYERNAME); + } + + return NET_ReadSHA1Sum(packet, data->wad_sha1sum) + && NET_ReadInt8(packet, (unsigned int *) &data->is_freedoom); +} + +static boolean NET_ReadBlob(net_packet_t *packet, uint8_t *buf, size_t len) +{ + unsigned int b; + int i; + + for (i=0; i + +#include "d_think.h" +// State. +#include "doomstat.h" +#include "doomtype.h" +#include "m_fixed.h" +#include "p_local.h" +#include "p_spec.h" +#include "r_defs.h" +#include "r_state.h" +#include "s_sound.h" +// Data. +#include "sounds.h" +#include "z_zone.h" + +// +// CEILINGS +// + +ceiling_t *activeceilings[MAXCEILINGS]; + +// +// T_MoveCeiling +// + +void T_MoveCeiling(ceiling_t *ceiling) { + result_e res; + + switch (ceiling->direction) { + case 0: + // IN STASIS + break; + case 1: + // UP + res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->topheight, + false, 1, ceiling->direction); + + if (!(leveltime & 7)) { + switch (ceiling->type) { + case silentCrushAndRaise: + break; + default: + S_StartSound(&ceiling->sector->soundorg, sfx_stnmov); + // ? + break; + } + } + + if (res == pastdest) { + switch (ceiling->type) { + case raiseToHighest: + P_RemoveActiveCeiling(ceiling); + break; + + case silentCrushAndRaise: + S_StartSound(&ceiling->sector->soundorg, sfx_pstop); + case fastCrushAndRaise: + case crushAndRaise: + ceiling->direction = -1; + break; + + default: + break; + } + } + break; + + case -1: + // DOWN + res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->bottomheight, + ceiling->crush, 1, ceiling->direction); + + if (!(leveltime & 7)) { + switch (ceiling->type) { + case silentCrushAndRaise: + break; + default: + S_StartSound(&ceiling->sector->soundorg, sfx_stnmov); + } + } + + if (res == pastdest) { + switch (ceiling->type) { + case silentCrushAndRaise: + S_StartSound(&ceiling->sector->soundorg, sfx_pstop); + case crushAndRaise: + ceiling->speed = CEILSPEED; + case fastCrushAndRaise: + ceiling->direction = 1; + break; + + case lowerAndCrush: + case lowerToFloor: + P_RemoveActiveCeiling(ceiling); + break; + + default: + break; + } + } else // ( res != pastdest ) + { + if (res == crushed) { + switch (ceiling->type) { + case silentCrushAndRaise: + case crushAndRaise: + case lowerAndCrush: + ceiling->speed = CEILSPEED / 8; + break; + + default: + break; + } + } + } + break; + } +} + +// +// EV_DoCeiling +// Move a ceiling up/down and all around! +// +int EV_DoCeiling(line_t *line, ceiling_e type) { + int secnum; + int rtn; + sector_t *sec; + ceiling_t *ceiling; + + secnum = -1; + rtn = 0; + + // Reactivate in-stasis ceilings...for certain types. + switch (type) { + case fastCrushAndRaise: + case silentCrushAndRaise: + case crushAndRaise: + P_ActivateInStasisCeiling(line); + default: + break; + } + + while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0) { + sec = §ors[secnum]; + if (sec->specialdata) + continue; + + // new door thinker + rtn = 1; + ceiling = Z_Malloc(sizeof(*ceiling), PU_LEVSPEC, 0); + P_AddThinker(&ceiling->thinker); + sec->specialdata = ceiling; + ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling; + ceiling->sector = sec; + ceiling->crush = false; + + switch (type) { + case fastCrushAndRaise: + ceiling->crush = true; + ceiling->topheight = sec->ceilingheight; + ceiling->bottomheight = sec->floorheight + (8 * FRACUNIT); + ceiling->direction = -1; + ceiling->speed = CEILSPEED * 2; + break; + + case silentCrushAndRaise: + case crushAndRaise: + ceiling->crush = true; + ceiling->topheight = sec->ceilingheight; + case lowerAndCrush: + case lowerToFloor: + ceiling->bottomheight = sec->floorheight; + if (type != lowerToFloor) + ceiling->bottomheight += 8 * FRACUNIT; + ceiling->direction = -1; + ceiling->speed = CEILSPEED; + break; + + case raiseToHighest: + ceiling->topheight = P_FindHighestCeilingSurrounding(sec); + ceiling->direction = 1; + ceiling->speed = CEILSPEED; + break; + } + + ceiling->tag = sec->tag; + ceiling->type = type; + P_AddActiveCeiling(ceiling); + } + return rtn; +} + +// +// Add an active ceiling +// +void P_AddActiveCeiling(ceiling_t *c) { + int i; + + for (i = 0; i < MAXCEILINGS; i++) { + if (activeceilings[i] == NULL) { + activeceilings[i] = c; + return; + } + } +} + +// +// Remove a ceiling's thinker +// +void P_RemoveActiveCeiling(ceiling_t *c) { + int i; + + for (i = 0; i < MAXCEILINGS; i++) { + if (activeceilings[i] == c) { + activeceilings[i]->sector->specialdata = NULL; + P_RemoveThinker(&activeceilings[i]->thinker); + activeceilings[i] = NULL; + break; + } + } +} + +// +// Restart a ceiling that's in-stasis +// +void P_ActivateInStasisCeiling(line_t *line) { + int i; + + for (i = 0; i < MAXCEILINGS; i++) { + if (activeceilings[i] && (activeceilings[i]->tag == line->tag) && + (activeceilings[i]->direction == 0)) { + activeceilings[i]->direction = activeceilings[i]->olddirection; + activeceilings[i]->thinker.function.acp1 = (actionf_p1)T_MoveCeiling; + } + } +} + +// +// EV_CeilingCrushStop +// Stop a ceiling from crushing! +// +int EV_CeilingCrushStop(line_t *line) { + int i; + int rtn; + + rtn = 0; + for (i = 0; i < MAXCEILINGS; i++) { + if (activeceilings[i] && (activeceilings[i]->tag == line->tag) && + (activeceilings[i]->direction != 0)) { + activeceilings[i]->olddirection = activeceilings[i]->direction; + activeceilings[i]->thinker.function.acv = (actionf_v)NULL; + activeceilings[i]->direction = 0; // in-stasis + rtn = 1; + } + } + + return rtn; +} diff --git a/client/src/p_doors.c b/client/src/p_doors.c new file mode 100644 index 0000000..83eaf14 --- /dev/null +++ b/client/src/p_doors.c @@ -0,0 +1,717 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: Door animation code (opening/closing) +// + +#include + +#include "d_englsh.h" +#include "d_player.h" +#include "d_think.h" +#include "doomdef.h" +#include "doomtype.h" +#include "i_system.h" +#include "i_timer.h" +#include "m_fixed.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_spec.h" +#include "r_defs.h" +#include "r_state.h" +#include "s_sound.h" +#include "sounds.h" +#include "z_zone.h" + +#if 0 +// +// Sliding door frame information +// +slidename_t slideFrameNames[MAXSLIDEDOORS] = +{ + {"GDOORF1","GDOORF2","GDOORF3","GDOORF4", // front + "GDOORB1","GDOORB2","GDOORB3","GDOORB4"}, // back + + {"\0","\0","\0","\0"} +}; +#endif + +// +// VERTICAL DOORS +// + +// +// T_VerticalDoor +// +void T_VerticalDoor(vldoor_t *door) { + result_e res; + + switch (door->direction) { + case 0: + // WAITING + if (!--door->topcountdown) { + switch (door->type) { + case vld_blazeRaise: + door->direction = -1; // time to go back down + S_StartSound(&door->sector->soundorg, sfx_bdcls); + break; + + case vld_normal: + door->direction = -1; // time to go back down + S_StartSound(&door->sector->soundorg, sfx_dorcls); + break; + + case vld_close30ThenOpen: + door->direction = 1; + S_StartSound(&door->sector->soundorg, sfx_doropn); + break; + + default: + break; + } + } + break; + + case 2: + // INITIAL WAIT + if (!--door->topcountdown) { + switch (door->type) { + case vld_raiseIn5Mins: + door->direction = 1; + door->type = vld_normal; + S_StartSound(&door->sector->soundorg, sfx_doropn); + break; + + default: + break; + } + } + break; + + case -1: + // DOWN + res = T_MovePlane(door->sector, door->speed, door->sector->floorheight, + false, 1, door->direction); + if (res == pastdest) { + switch (door->type) { + case vld_blazeRaise: + case vld_blazeClose: + door->sector->specialdata = NULL; + P_RemoveThinker(&door->thinker); // unlink and free + S_StartSound(&door->sector->soundorg, sfx_bdcls); + break; + + case vld_normal: + case vld_close: + door->sector->specialdata = NULL; + P_RemoveThinker(&door->thinker); // unlink and free + break; + + case vld_close30ThenOpen: + door->direction = 0; + door->topcountdown = TICRATE * 30; + break; + + default: + break; + } + } else if (res == crushed) { + switch (door->type) { + case vld_blazeClose: + case vld_close: // DO NOT GO BACK UP! + break; + + default: + door->direction = 1; + S_StartSound(&door->sector->soundorg, sfx_doropn); + break; + } + } + break; + + case 1: + // UP + res = T_MovePlane(door->sector, door->speed, door->topheight, false, 1, + door->direction); + + if (res == pastdest) { + switch (door->type) { + case vld_blazeRaise: + case vld_normal: + door->direction = 0; // wait at top + door->topcountdown = door->topwait; + break; + + case vld_close30ThenOpen: + case vld_blazeOpen: + case vld_open: + door->sector->specialdata = NULL; + P_RemoveThinker(&door->thinker); // unlink and free + break; + + default: + break; + } + } + break; + } +} + +// +// EV_DoLockedDoor +// Move a locked door up/down +// + +int EV_DoLockedDoor(line_t *line, vldoor_e type, mobj_t *thing) { + player_t *p; + + p = thing->player; + + if (!p) + return 0; + + switch (line->special) { + case 99: // Blue Lock + case 133: + if (!p) + return 0; + if (!p->cards[it_bluecard] && !p->cards[it_blueskull]) { + p->message = (PD_BLUEO); + S_StartSound(NULL, sfx_oof); + return 0; + } + break; + + case 134: // Red Lock + case 135: + if (!p) + return 0; + if (!p->cards[it_redcard] && !p->cards[it_redskull]) { + p->message = (PD_REDO); + S_StartSound(NULL, sfx_oof); + return 0; + } + break; + + case 136: // Yellow Lock + case 137: + if (!p) + return 0; + if (!p->cards[it_yellowcard] && !p->cards[it_yellowskull]) { + p->message = (PD_YELLOWO); + S_StartSound(NULL, sfx_oof); + return 0; + } + break; + } + + return EV_DoDoor(line, type); +} + +int EV_DoDoor(line_t *line, vldoor_e type) { + int secnum, rtn; + sector_t *sec; + vldoor_t *door; + + secnum = -1; + rtn = 0; + + while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0) { + sec = §ors[secnum]; + if (sec->specialdata) + continue; + + // new door thinker + rtn = 1; + door = Z_Malloc(sizeof(*door), PU_LEVSPEC, 0); + P_AddThinker(&door->thinker); + sec->specialdata = door; + + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + door->sector = sec; + door->type = type; + door->topwait = VDOORWAIT; + door->speed = VDOORSPEED; + + switch (type) { + case vld_blazeClose: + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4 * FRACUNIT; + door->direction = -1; + door->speed = VDOORSPEED * 4; + S_StartSound(&door->sector->soundorg, sfx_bdcls); + break; + + case vld_close: + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4 * FRACUNIT; + door->direction = -1; + S_StartSound(&door->sector->soundorg, sfx_dorcls); + break; + + case vld_close30ThenOpen: + door->topheight = sec->ceilingheight; + door->direction = -1; + S_StartSound(&door->sector->soundorg, sfx_dorcls); + break; + + case vld_blazeRaise: + case vld_blazeOpen: + door->direction = 1; + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4 * FRACUNIT; + door->speed = VDOORSPEED * 4; + if (door->topheight != sec->ceilingheight) + S_StartSound(&door->sector->soundorg, sfx_bdopn); + break; + + case vld_normal: + case vld_open: + door->direction = 1; + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4 * FRACUNIT; + if (door->topheight != sec->ceilingheight) + S_StartSound(&door->sector->soundorg, sfx_doropn); + break; + + default: + break; + } + } + return rtn; +} + +// +// EV_VerticalDoor : open a door manually, no tag value +// +void EV_VerticalDoor(line_t *line, mobj_t *thing) { + player_t *player; + sector_t *sec; + vldoor_t *door; + int side; + + side = 0; // only front sides can be used + + // Check for locks + player = thing->player; + + switch (line->special) { + case 26: // Blue Lock + case 32: + if (!player) + return; + + if (!player->cards[it_bluecard] && !player->cards[it_blueskull]) { + player->message = (PD_BLUEK); + S_StartSound(NULL, sfx_oof); + return; + } + break; + + case 27: // Yellow Lock + case 34: + if (!player) + return; + + if (!player->cards[it_yellowcard] && !player->cards[it_yellowskull]) { + player->message = (PD_YELLOWK); + S_StartSound(NULL, sfx_oof); + return; + } + break; + + case 28: // Red Lock + case 33: + if (!player) + return; + + if (!player->cards[it_redcard] && !player->cards[it_redskull]) { + player->message = (PD_REDK); + S_StartSound(NULL, sfx_oof); + return; + } + break; + } + + // if the sector has an active thinker, use it + + if (line->sidenum[side ^ 1] == -1) { + I_Error("EV_VerticalDoor: DR special type on 1-sided linedef"); + } + + sec = sides[line->sidenum[side ^ 1]].sector; + + if (sec->specialdata) { + door = sec->specialdata; + switch (line->special) { + case 1: // ONLY FOR "RAISE" DOORS, NOT "OPEN"s + case 26: + case 27: + case 28: + case 117: + if (door->direction == -1) + door->direction = 1; // go back up + else { + if (!thing->player) + return; // JDC: bad guys never close doors + + // When is a door not a door? + // In Vanilla, door->direction is set, even though + // "specialdata" might not actually point at a door. + + if (door->thinker.function.acp1 == (actionf_p1)T_VerticalDoor) { + door->direction = -1; // start going down immediately + } else if (door->thinker.function.acp1 == (actionf_p1)T_PlatRaise) { + // Erm, this is a plat, not a door. + // This notably causes a problem in ep1-0500.lmp where + // a plat and a door are cross-referenced; the door + // doesn't open on 64-bit. + // The direction field in vldoor_t corresponds to the wait + // field in plat_t. Let's set that to -1 instead. + + plat_t *plat; + + plat = (plat_t *)door; + plat->wait = -1; + } else { + // This isn't a door OR a plat. Now we're in trouble. + + fprintf(stderr, "EV_VerticalDoor: Tried to close " + "something that wasn't a door.\n"); + + // Try closing it anyway. At least it will work on 32-bit + // machines. + + door->direction = -1; + } + } + return; + } + } + + // for proper sound + switch (line->special) { + case 117: // BLAZING DOOR RAISE + case 118: // BLAZING DOOR OPEN + S_StartSound(&sec->soundorg, sfx_bdopn); + break; + + case 1: // NORMAL DOOR SOUND + case 31: + S_StartSound(&sec->soundorg, sfx_doropn); + break; + + default: // LOCKED DOOR SOUND + S_StartSound(&sec->soundorg, sfx_doropn); + break; + } + + // new door thinker + door = Z_Malloc(sizeof(*door), PU_LEVSPEC, 0); + P_AddThinker(&door->thinker); + sec->specialdata = door; + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + door->sector = sec; + door->direction = 1; + door->speed = VDOORSPEED; + door->topwait = VDOORWAIT; + + switch (line->special) { + case 1: + case 26: + case 27: + case 28: + door->type = vld_normal; + break; + + case 31: + case 32: + case 33: + case 34: + door->type = vld_open; + line->special = 0; + break; + + case 117: // blazing door raise + door->type = vld_blazeRaise; + door->speed = VDOORSPEED * 4; + break; + case 118: // blazing door open + door->type = vld_blazeOpen; + line->special = 0; + door->speed = VDOORSPEED * 4; + break; + } + + // find the top and bottom of the movement range + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4 * FRACUNIT; +} + +// +// Spawn a door that closes after 30 seconds +// +void P_SpawnDoorCloseIn30(sector_t *sec) { + vldoor_t *door; + + door = Z_Malloc(sizeof(*door), PU_LEVSPEC, 0); + + P_AddThinker(&door->thinker); + + sec->specialdata = door; + sec->special = 0; + + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + door->sector = sec; + door->direction = 0; + door->type = vld_normal; + door->speed = VDOORSPEED; + door->topcountdown = 30 * TICRATE; +} + +// +// Spawn a door that opens after 5 minutes +// +void P_SpawnDoorRaiseIn5Mins(sector_t *sec, int secnum) { + vldoor_t *door; + + door = Z_Malloc(sizeof(*door), PU_LEVSPEC, 0); + + P_AddThinker(&door->thinker); + + sec->specialdata = door; + sec->special = 0; + + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + door->sector = sec; + door->direction = 2; + door->type = vld_raiseIn5Mins; + door->speed = VDOORSPEED; + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4 * FRACUNIT; + door->topwait = VDOORWAIT; + door->topcountdown = 5 * 60 * TICRATE; +} + +// UNUSED +// Separate into p_slidoor.c? + +#if 0 // ABANDONED TO THE MISTS OF TIME!!! +// +// EV_SlidingDoor : slide a door horizontally +// (animate midtexture, then set noblocking line) +// + + +slideframe_t slideFrames[MAXSLIDEDOORS]; + +void P_InitSlidingDoorFrames(void) +{ + int i; + int f1; + int f2; + int f3; + int f4; + + // DOOM II ONLY... + if ( gamemode != commercial) + return; + + for (i = 0;i < MAXSLIDEDOORS; i++) + { + if (!slideFrameNames[i].frontFrame1[0]) + break; + + f1 = R_TextureNumForName(slideFrameNames[i].frontFrame1); + f2 = R_TextureNumForName(slideFrameNames[i].frontFrame2); + f3 = R_TextureNumForName(slideFrameNames[i].frontFrame3); + f4 = R_TextureNumForName(slideFrameNames[i].frontFrame4); + + slideFrames[i].frontFrames[0] = f1; + slideFrames[i].frontFrames[1] = f2; + slideFrames[i].frontFrames[2] = f3; + slideFrames[i].frontFrames[3] = f4; + + f1 = R_TextureNumForName(slideFrameNames[i].backFrame1); + f2 = R_TextureNumForName(slideFrameNames[i].backFrame2); + f3 = R_TextureNumForName(slideFrameNames[i].backFrame3); + f4 = R_TextureNumForName(slideFrameNames[i].backFrame4); + + slideFrames[i].backFrames[0] = f1; + slideFrames[i].backFrames[1] = f2; + slideFrames[i].backFrames[2] = f3; + slideFrames[i].backFrames[3] = f4; + } +} + + +// +// Return index into "slideFrames" array +// for which door type to use +// +int P_FindSlidingDoorType(line_t* line) +{ + int i; + int val; + + for (i = 0;i < MAXSLIDEDOORS;i++) + { + val = sides[line->sidenum[0]].midtexture; + if (val == slideFrames[i].frontFrames[0]) + return i; + } + + return -1; +} + +void T_SlidingDoor (slidedoor_t* door) +{ + switch(door->status) + { + case sd_opening: + if (!door->timer--) + { + if (++door->frame == SNUMFRAMES) + { + // IF DOOR IS DONE OPENING... + sides[door->line->sidenum[0]].midtexture = 0; + sides[door->line->sidenum[1]].midtexture = 0; + door->line->flags &= ML_BLOCKING^0xff; + + if (door->type == sdt_openOnly) + { + door->frontsector->specialdata = NULL; + P_RemoveThinker (&door->thinker); + break; + } + + door->timer = SDOORWAIT; + door->status = sd_waiting; + } + else + { + // IF DOOR NEEDS TO ANIMATE TO NEXT FRAME... + door->timer = SWAITTICS; + + sides[door->line->sidenum[0]].midtexture = + slideFrames[door->whichDoorIndex]. + frontFrames[door->frame]; + sides[door->line->sidenum[1]].midtexture = + slideFrames[door->whichDoorIndex]. + backFrames[door->frame]; + } + } + break; + + case sd_waiting: + // IF DOOR IS DONE WAITING... + if (!door->timer--) + { + // CAN DOOR CLOSE? + if (door->frontsector->thinglist != NULL || + door->backsector->thinglist != NULL) + { + door->timer = SDOORWAIT; + break; + } + + //door->frame = SNUMFRAMES-1; + door->status = sd_closing; + door->timer = SWAITTICS; + } + break; + + case sd_closing: + if (!door->timer--) + { + if (--door->frame < 0) + { + // IF DOOR IS DONE CLOSING... + door->line->flags |= ML_BLOCKING; + door->frontsector->specialdata = NULL; + P_RemoveThinker (&door->thinker); + break; + } + else + { + // IF DOOR NEEDS TO ANIMATE TO NEXT FRAME... + door->timer = SWAITTICS; + + sides[door->line->sidenum[0]].midtexture = + slideFrames[door->whichDoorIndex]. + frontFrames[door->frame]; + sides[door->line->sidenum[1]].midtexture = + slideFrames[door->whichDoorIndex]. + backFrames[door->frame]; + } + } + break; + } +} + + + +void +EV_SlidingDoor +( line_t* line, + mobj_t* thing ) +{ + sector_t* sec; + slidedoor_t* door; + + // DOOM II ONLY... + if (gamemode != commercial) + return; + + // Make sure door isn't already being animated + sec = line->frontsector; + door = NULL; + if (sec->specialdata) + { + if (!thing->player) + return; + + door = sec->specialdata; + if (door->type == sdt_openAndClose) + { + if (door->status == sd_waiting) + door->status = sd_closing; + } + else + return; + } + + // Init sliding door vars + if (!door) + { + door = Z_Malloc (sizeof(*door), PU_LEVSPEC, 0); + P_AddThinker (&door->thinker); + sec->specialdata = door; + + door->type = sdt_openAndClose; + door->status = sd_opening; + door->whichDoorIndex = P_FindSlidingDoorType(line); + + if (door->whichDoorIndex < 0) + I_Error("EV_SlidingDoor: Can't use texture for sliding door!"); + + door->frontsector = sec; + door->backsector = line->backsector; + door->thinker.function = T_SlidingDoor; + door->timer = SWAITTICS; + door->frame = 0; + door->line = line; + } +} +#endif diff --git a/client/src/p_enemy.c b/client/src/p_enemy.c new file mode 100644 index 0000000..fa06edb --- /dev/null +++ b/client/src/p_enemy.c @@ -0,0 +1,1690 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Enemy thinking, AI. +// Action Pointer Functions +// that are associated with states/frames. +// + +#include +#include + +#include "d_loop.h" +#include "d_mode.h" +#include "d_player.h" +#include "d_think.h" +#include "doomdata.h" +#include "doomdef.h" +// State. +#include "doomstat.h" +#include "doomtype.h" +#include "g_game.h" +#include "i_system.h" +#include "info.h" +#include "m_fixed.h" +#include "m_random.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_pspr.h" +#include "p_spec.h" +#include "r_defs.h" +#include "r_main.h" +#include "r_state.h" +#include "s_sound.h" +// Data. +#include "sounds.h" +#include "tables.h" + +typedef enum { + DI_EAST, + DI_NORTHEAST, + DI_NORTH, + DI_NORTHWEST, + DI_WEST, + DI_SOUTHWEST, + DI_SOUTH, + DI_SOUTHEAST, + DI_NODIR, + NUMDIRS + +} dirtype_t; + +// +// P_NewChaseDir related LUT. +// +dirtype_t opposite[] = {DI_WEST, DI_SOUTHWEST, DI_SOUTH, + DI_SOUTHEAST, DI_EAST, DI_NORTHEAST, + DI_NORTH, DI_NORTHWEST, DI_NODIR}; + +dirtype_t diags[] = {DI_NORTHWEST, DI_NORTHEAST, DI_SOUTHWEST, DI_SOUTHEAST}; + +void A_Fall(mobj_t *actor); + +// +// ENEMY THINKING +// Enemies are allways spawned +// with targetplayer = -1, threshold = 0 +// Most monsters are spawned unaware of all players, +// but some can be made preaware +// + +// +// Called by P_NoiseAlert. +// Recursively traverse adjacent sectors, +// sound blocking lines cut off traversal. +// + +mobj_t *soundtarget; + +void P_RecursiveSound(sector_t *sec, int soundblocks) { + int i; + line_t *check; + sector_t *other; + + // wake up all monsters in this sector + if (sec->validcount == validcount && sec->soundtraversed <= soundblocks + 1) { + return; // already flooded + } + + sec->validcount = validcount; + sec->soundtraversed = soundblocks + 1; + sec->soundtarget = soundtarget; + + for (i = 0; i < sec->linecount; i++) { + check = sec->lines[i]; + if (!(check->flags & ML_TWOSIDED)) + continue; + + P_LineOpening(check); + + if (openrange <= 0) + continue; // closed door + + if (sides[check->sidenum[0]].sector == sec) + other = sides[check->sidenum[1]].sector; + else + other = sides[check->sidenum[0]].sector; + + if (check->flags & ML_SOUNDBLOCK) { + if (!soundblocks) + P_RecursiveSound(other, 1); + } else + P_RecursiveSound(other, soundblocks); + } +} + +// +// P_NoiseAlert +// If a monster yells at a player, +// it will alert other monsters to the player. +// +void P_NoiseAlert(mobj_t *target, mobj_t *emmiter) { + soundtarget = target; + validcount++; + P_RecursiveSound(emmiter->subsector->sector, 0); +} + +// +// P_CheckMeleeRange +// +boolean P_CheckMeleeRange(mobj_t *actor) { + mobj_t *pl; + fixed_t dist; + + if (!actor->target) + return false; + + pl = actor->target; + dist = P_AproxDistance(pl->x - actor->x, pl->y - actor->y); + + if (dist >= MELEERANGE - 20 * FRACUNIT + pl->info->radius) + return false; + + if (!P_CheckSight(actor, actor->target)) + return false; + + return true; +} + +// +// P_CheckMissileRange +// +boolean P_CheckMissileRange(mobj_t *actor) { + fixed_t dist; + + if (!P_CheckSight(actor, actor->target)) + return false; + + if (actor->flags & MF_JUSTHIT) { + // the target just hit the enemy, + // so fight back! + actor->flags &= ~MF_JUSTHIT; + return true; + } + + if (actor->reactiontime) + return false; // do not attack yet + + // OPTIMIZE: get this from a global checksight + dist = P_AproxDistance(actor->x - actor->target->x, + actor->y - actor->target->y) - + 64 * FRACUNIT; + + if (!actor->info->meleestate) + dist -= 128 * FRACUNIT; // no melee attack, so fire more + + dist >>= FRACBITS; + + if (actor->type == MT_VILE) { + if (dist > 14 * 64) + return false; // too far away + } + + if (actor->type == MT_UNDEAD) { + if (dist < 196) + return false; // close for fist attack + dist >>= 1; + } + + if (actor->type == MT_CYBORG || actor->type == MT_SPIDER || + actor->type == MT_SKULL) { + dist >>= 1; + } + + if (dist > 200) + dist = 200; + + if (actor->type == MT_CYBORG && dist > 160) + dist = 160; + + if (P_Random() < dist) + return false; + + return true; +} + +// +// P_Move +// Move in the current direction, +// returns false if the move is blocked. +// +fixed_t xspeed[8] = {FRACUNIT, 47000, 0, -47000, -FRACUNIT, -47000, 0, 47000}; +fixed_t yspeed[8] = {0, 47000, FRACUNIT, 47000, 0, -47000, -FRACUNIT, -47000}; + +boolean P_Move(mobj_t *actor) { + fixed_t tryx; + fixed_t tryy; + + line_t *ld; + + // warning: 'catch', 'throw', and 'try' + // are all C++ reserved words + boolean try_ok; + boolean good; + + if (actor->movedir == DI_NODIR) + return false; + + if ((unsigned)actor->movedir >= 8) + I_Error("Weird actor->movedir!"); + + tryx = actor->x + actor->info->speed * xspeed[actor->movedir]; + tryy = actor->y + actor->info->speed * yspeed[actor->movedir]; + + try_ok = P_TryMove(actor, tryx, tryy); + + if (!try_ok) { + // open any specials + if (actor->flags & MF_FLOAT && floatok) { + // must adjust height + if (actor->z < tmfloorz) + actor->z += FLOATSPEED; + else + actor->z -= FLOATSPEED; + + actor->flags |= MF_INFLOAT; + return true; + } + + if (!numspechit) + return false; + + actor->movedir = DI_NODIR; + good = false; + while (numspechit--) { + ld = spechit[numspechit]; + // if the special is not a door + // that can be opened, + // return false + if (P_UseSpecialLine(actor, ld, 0)) + good = true; + } + return good; + } else { + actor->flags &= ~MF_INFLOAT; + } + + if (!(actor->flags & MF_FLOAT)) + actor->z = actor->floorz; + return true; +} + +// +// TryWalk +// Attempts to move actor on +// in its current (ob->moveangle) direction. +// If blocked by either a wall or an actor +// returns FALSE +// If move is either clear or blocked only by a door, +// returns TRUE and sets... +// If a door is in the way, +// an OpenDoor call is made to start it opening. +// +boolean P_TryWalk(mobj_t *actor) { + if (!P_Move(actor)) { + return false; + } + + actor->movecount = P_Random() & 15; + return true; +} + +void P_NewChaseDir(mobj_t *actor) { + fixed_t deltax; + fixed_t deltay; + + dirtype_t d[3]; + + int tdir; + dirtype_t olddir; + + dirtype_t turnaround; + + if (!actor->target) + I_Error("P_NewChaseDir: called with no target"); + + olddir = actor->movedir; + turnaround = opposite[olddir]; + + deltax = actor->target->x - actor->x; + deltay = actor->target->y - actor->y; + + if (deltax > 10 * FRACUNIT) + d[1] = DI_EAST; + else if (deltax < -10 * FRACUNIT) + d[1] = DI_WEST; + else + d[1] = DI_NODIR; + + if (deltay < -10 * FRACUNIT) + d[2] = DI_SOUTH; + else if (deltay > 10 * FRACUNIT) + d[2] = DI_NORTH; + else + d[2] = DI_NODIR; + + // try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) { + actor->movedir = diags[((deltay < 0) << 1) + (deltax > 0)]; + if (actor->movedir != (int)turnaround && P_TryWalk(actor)) + return; + } + + // try other directions + if (P_Random() > 200 || abs(deltay) > abs(deltax)) { + tdir = d[1]; + d[1] = d[2]; + d[2] = tdir; + } + + if (d[1] == turnaround) + d[1] = DI_NODIR; + if (d[2] == turnaround) + d[2] = DI_NODIR; + + if (d[1] != DI_NODIR) { + actor->movedir = d[1]; + if (P_TryWalk(actor)) { + // either moved forward or attacked + return; + } + } + + if (d[2] != DI_NODIR) { + actor->movedir = d[2]; + + if (P_TryWalk(actor)) + return; + } + + // there is no direct path to the player, + // so pick another direction. + if (olddir != DI_NODIR) { + actor->movedir = olddir; + + if (P_TryWalk(actor)) + return; + } + + // randomly determine direction of search + if (P_Random() & 1) { + for (tdir = DI_EAST; tdir <= DI_SOUTHEAST; tdir++) { + if (tdir != (int)turnaround) { + actor->movedir = tdir; + + if (P_TryWalk(actor)) + return; + } + } + } else { + for (tdir = DI_SOUTHEAST; tdir != (DI_EAST - 1); tdir--) { + if (tdir != (int)turnaround) { + actor->movedir = tdir; + + if (P_TryWalk(actor)) + return; + } + } + } + + if (turnaround != DI_NODIR) { + actor->movedir = turnaround; + if (P_TryWalk(actor)) + return; + } + + actor->movedir = DI_NODIR; // can not move +} + +// +// P_LookForPlayers +// If allaround is false, only look 180 degrees in front. +// Returns true if a player is targeted. +// +boolean P_LookForPlayers(mobj_t *actor, boolean allaround) { + int c; + int stop; + player_t *player; + angle_t an; + fixed_t dist; + + c = 0; + stop = (actor->lastlook - 1) & 3; + + for (;; actor->lastlook = (actor->lastlook + 1) & 3) { + if (!playeringame[actor->lastlook]) + continue; + + if (c++ == 2 || actor->lastlook == stop) { + // done looking + return false; + } + + player = &players[actor->lastlook]; + + if (player->health <= 0) + continue; // dead + + if (!P_CheckSight(actor, player->mo)) + continue; // out of sight + + if (!allaround) { + an = R_PointToAngle2(actor->x, actor->y, player->mo->x, player->mo->y) - + actor->angle; + + if (an > ANG90 && an < ANG270) { + dist = + P_AproxDistance(player->mo->x - actor->x, player->mo->y - actor->y); + // if real close, react anyway + if (dist > MELEERANGE) + continue; // behind back + } + } + + actor->target = player->mo; + return true; + } + + return false; +} + +// +// A_KeenDie +// DOOM II special, map 32. +// Uses special tag 666. +// +void A_KeenDie(mobj_t *mo) { + thinker_t *th; + mobj_t *mo2; + line_t junk; + + A_Fall(mo); + + // scan the remaining thinkers + // to see if all Keens are dead + for (th = thinkercap.next; th != &thinkercap; th = th->next) { + if (th->function.acp1 != (actionf_p1)P_MobjThinker) + continue; + + mo2 = (mobj_t *)th; + if (mo2 != mo && mo2->type == mo->type && mo2->health > 0) { + // other Keen not dead + return; + } + } + + junk.tag = 666; + EV_DoDoor(&junk, vld_open); +} + +// +// ACTION ROUTINES +// + +// +// A_Look +// Stay in state until a player is sighted. +// +void A_Look(mobj_t *actor) { + mobj_t *targ; + + actor->threshold = 0; // any shot will wake up + targ = actor->subsector->sector->soundtarget; + + if (targ && (targ->flags & MF_SHOOTABLE)) { + actor->target = targ; + + if (actor->flags & MF_AMBUSH) { + if (P_CheckSight(actor, actor->target)) + goto seeyou; + } else + goto seeyou; + } + + if (!P_LookForPlayers(actor, false)) + return; + +// go into chase state +seeyou: + if (actor->info->seesound) { + int sound; + + switch (actor->info->seesound) { + case sfx_posit1: + case sfx_posit2: + case sfx_posit3: + sound = sfx_posit1 + P_Random() % 3; + break; + + case sfx_bgsit1: + case sfx_bgsit2: + sound = sfx_bgsit1 + P_Random() % 2; + break; + + default: + sound = actor->info->seesound; + break; + } + + if (actor->type == MT_SPIDER || actor->type == MT_CYBORG) { + // full volume + S_StartSound(NULL, sound); + } else + S_StartSound(actor, sound); + } + + P_SetMobjState(actor, actor->info->seestate); +} + +// +// A_Chase +// Actor has a melee attack, +// so it tries to close as fast as possible +// +void A_Chase(mobj_t *actor) { + int delta; + + if (actor->reactiontime) + actor->reactiontime--; + + // modify target threshold + if (actor->threshold) { + if (!actor->target || actor->target->health <= 0) { + actor->threshold = 0; + } else + actor->threshold--; + } + + // turn towards movement direction if not there yet + if (actor->movedir < 8) { + actor->angle &= (7 << 29); + delta = actor->angle - (actor->movedir << 29); + + if (delta > 0) + actor->angle -= ANG90 / 2; + else if (delta < 0) + actor->angle += ANG90 / 2; + } + + if (!actor->target || !(actor->target->flags & MF_SHOOTABLE)) { + // look for a new target + if (P_LookForPlayers(actor, true)) + return; // got a new target + + P_SetMobjState(actor, actor->info->spawnstate); + return; + } + + // do not attack twice in a row + if (actor->flags & MF_JUSTATTACKED) { + actor->flags &= ~MF_JUSTATTACKED; + if (gameskill != sk_nightmare && !fastparm) + P_NewChaseDir(actor); + return; + } + + // check for melee attack + if (actor->info->meleestate && P_CheckMeleeRange(actor)) { + if (actor->info->attacksound) + S_StartSound(actor, actor->info->attacksound); + + P_SetMobjState(actor, actor->info->meleestate); + return; + } + + // check for missile attack + if (actor->info->missilestate) { + if (gameskill < sk_nightmare && !fastparm && actor->movecount) { + goto nomissile; + } + + if (!P_CheckMissileRange(actor)) + goto nomissile; + + P_SetMobjState(actor, actor->info->missilestate); + actor->flags |= MF_JUSTATTACKED; + return; + } + +// ? +nomissile: + // possibly choose another target + if (netgame && !actor->threshold && !P_CheckSight(actor, actor->target)) { + if (P_LookForPlayers(actor, true)) + return; // got a new target + } + + // chase towards player + if (--actor->movecount < 0 || !P_Move(actor)) { + P_NewChaseDir(actor); + } + + // make active sound + if (actor->info->activesound && P_Random() < 3) { + S_StartSound(actor, actor->info->activesound); + } +} + +// +// A_FaceTarget +// +void A_FaceTarget(mobj_t *actor) { + if (!actor->target) + return; + + actor->flags &= ~MF_AMBUSH; + + actor->angle = + R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y); + + if (actor->target->flags & MF_SHADOW) + actor->angle += P_SubRandom() << 21; +} + +// +// A_PosAttack +// +void A_PosAttack(mobj_t *actor) { + int angle; + int damage; + int slope; + + if (!actor->target) + return; + + A_FaceTarget(actor); + angle = actor->angle; + slope = P_AimLineAttack(actor, angle, MISSILERANGE); + + S_StartSound(actor, sfx_pistol); + angle += P_SubRandom() << 20; + damage = ((P_Random() % 5) + 1) * 3; + P_LineAttack(actor, angle, MISSILERANGE, slope, damage); +} + +void A_SPosAttack(mobj_t *actor) { + int i; + int angle; + int bangle; + int damage; + int slope; + + if (!actor->target) + return; + + S_StartSound(actor, sfx_shotgn); + A_FaceTarget(actor); + bangle = actor->angle; + slope = P_AimLineAttack(actor, bangle, MISSILERANGE); + + for (i = 0; i < 3; i++) { + angle = bangle + (P_SubRandom() << 20); + damage = ((P_Random() % 5) + 1) * 3; + P_LineAttack(actor, angle, MISSILERANGE, slope, damage); + } +} + +void A_CPosAttack(mobj_t *actor) { + int angle; + int bangle; + int damage; + int slope; + + if (!actor->target) + return; + + S_StartSound(actor, sfx_shotgn); + A_FaceTarget(actor); + bangle = actor->angle; + slope = P_AimLineAttack(actor, bangle, MISSILERANGE); + + angle = bangle + (P_SubRandom() << 20); + damage = ((P_Random() % 5) + 1) * 3; + P_LineAttack(actor, angle, MISSILERANGE, slope, damage); +} + +void A_CPosRefire(mobj_t *actor) { + // keep firing unless target got out of sight + A_FaceTarget(actor); + + if (P_Random() < 40) + return; + + if (!actor->target || actor->target->health <= 0 || + !P_CheckSight(actor, actor->target)) { + P_SetMobjState(actor, actor->info->seestate); + } +} + +void A_SpidRefire(mobj_t *actor) { + // keep firing unless target got out of sight + A_FaceTarget(actor); + + if (P_Random() < 10) + return; + + if (!actor->target || actor->target->health <= 0 || + !P_CheckSight(actor, actor->target)) { + P_SetMobjState(actor, actor->info->seestate); + } +} + +void A_BspiAttack(mobj_t *actor) { + if (!actor->target) + return; + + A_FaceTarget(actor); + + // launch a missile + P_SpawnMissile(actor, actor->target, MT_ARACHPLAZ); +} + +// +// A_TroopAttack +// +void A_TroopAttack(mobj_t *actor) { + int damage; + + if (!actor->target) + return; + + A_FaceTarget(actor); + if (P_CheckMeleeRange(actor)) { + S_StartSound(actor, sfx_claw); + damage = (P_Random() % 8 + 1) * 3; + P_DamageMobj(actor->target, actor, actor, damage); + return; + } + + // launch a missile + P_SpawnMissile(actor, actor->target, MT_TROOPSHOT); +} + +void A_SargAttack(mobj_t *actor) { + int damage; + + if (!actor->target) + return; + + A_FaceTarget(actor); + if (P_CheckMeleeRange(actor)) { + damage = ((P_Random() % 10) + 1) * 4; + P_DamageMobj(actor->target, actor, actor, damage); + } +} + +void A_HeadAttack(mobj_t *actor) { + int damage; + + if (!actor->target) + return; + + A_FaceTarget(actor); + if (P_CheckMeleeRange(actor)) { + damage = (P_Random() % 6 + 1) * 10; + P_DamageMobj(actor->target, actor, actor, damage); + return; + } + + // launch a missile + P_SpawnMissile(actor, actor->target, MT_HEADSHOT); +} + +void A_CyberAttack(mobj_t *actor) { + if (!actor->target) + return; + + A_FaceTarget(actor); + P_SpawnMissile(actor, actor->target, MT_ROCKET); +} + +void A_BruisAttack(mobj_t *actor) { + int damage; + + if (!actor->target) + return; + + if (P_CheckMeleeRange(actor)) { + S_StartSound(actor, sfx_claw); + damage = (P_Random() % 8 + 1) * 10; + P_DamageMobj(actor->target, actor, actor, damage); + return; + } + + // launch a missile + P_SpawnMissile(actor, actor->target, MT_BRUISERSHOT); +} + +// +// A_SkelMissile +// +void A_SkelMissile(mobj_t *actor) { + mobj_t *mo; + + if (!actor->target) + return; + + A_FaceTarget(actor); + actor->z += 16 * FRACUNIT; // so missile spawns higher + mo = P_SpawnMissile(actor, actor->target, MT_TRACER); + actor->z -= 16 * FRACUNIT; // back to normal + + mo->x += mo->momx; + mo->y += mo->momy; + mo->tracer = actor->target; +} + +int TRACEANGLE = 0xc000000; + +void A_Tracer(mobj_t *actor) { + angle_t exact; + fixed_t dist; + fixed_t slope; + mobj_t *dest; + mobj_t *th; + + if (gametic & 3) + return; + + // spawn a puff of smoke behind the rocket + P_SpawnPuff(actor->x, actor->y, actor->z); + + th = P_SpawnMobj(actor->x - actor->momx, actor->y - actor->momy, actor->z, + MT_SMOKE); + + th->momz = FRACUNIT; + th->tics -= P_Random() & 3; + if (th->tics < 1) + th->tics = 1; + + // adjust direction + dest = actor->tracer; + + if (!dest || dest->health <= 0) + return; + + // change angle + exact = R_PointToAngle2(actor->x, actor->y, dest->x, dest->y); + + if (exact != actor->angle) { + if (exact - actor->angle > 0x80000000) { + actor->angle -= TRACEANGLE; + if (exact - actor->angle < 0x80000000) + actor->angle = exact; + } else { + actor->angle += TRACEANGLE; + if (exact - actor->angle > 0x80000000) + actor->angle = exact; + } + } + + exact = actor->angle >> ANGLETOFINESHIFT; + actor->momx = FixedMul(actor->info->speed, finecosine[exact]); + actor->momy = FixedMul(actor->info->speed, finesine[exact]); + + // change slope + dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y); + + dist = dist / actor->info->speed; + + if (dist < 1) + dist = 1; + slope = (dest->z + 40 * FRACUNIT - actor->z) / dist; + + if (slope < actor->momz) + actor->momz -= FRACUNIT / 8; + else + actor->momz += FRACUNIT / 8; +} + +void A_SkelWhoosh(mobj_t *actor) { + if (!actor->target) + return; + A_FaceTarget(actor); + S_StartSound(actor, sfx_skeswg); +} + +void A_SkelFist(mobj_t *actor) { + int damage; + + if (!actor->target) + return; + + A_FaceTarget(actor); + + if (P_CheckMeleeRange(actor)) { + damage = ((P_Random() % 10) + 1) * 6; + S_StartSound(actor, sfx_skepch); + P_DamageMobj(actor->target, actor, actor, damage); + } +} + +// +// PIT_VileCheck +// Detect a corpse that could be raised. +// +mobj_t *corpsehit; +mobj_t *vileobj; +fixed_t viletryx; +fixed_t viletryy; + +boolean PIT_VileCheck(mobj_t *thing) { + int maxdist; + boolean check; + + if (!(thing->flags & MF_CORPSE)) + return true; // not a monster + + if (thing->tics != -1) + return true; // not lying still yet + + if (thing->info->raisestate == S_NULL) + return true; // monster doesn't have a raise state + + maxdist = thing->info->radius + mobjinfo[MT_VILE].radius; + + if (abs(thing->x - viletryx) > maxdist || abs(thing->y - viletryy) > maxdist) + return true; // not actually touching + + corpsehit = thing; + corpsehit->momx = corpsehit->momy = 0; + corpsehit->height <<= 2; + check = P_CheckPosition(corpsehit, corpsehit->x, corpsehit->y); + corpsehit->height >>= 2; + + if (!check) + return true; // doesn't fit here + + return false; // got one, so stop checking +} + +// +// A_VileChase +// Check for ressurecting a body +// +void A_VileChase(mobj_t *actor) { + int xl; + int xh; + int yl; + int yh; + + int bx; + int by; + + mobjinfo_t *info; + mobj_t *temp; + + if (actor->movedir != DI_NODIR) { + // check for corpses to raise + viletryx = actor->x + actor->info->speed * xspeed[actor->movedir]; + viletryy = actor->y + actor->info->speed * yspeed[actor->movedir]; + + xl = (viletryx - bmaporgx - MAXRADIUS * 2) >> MAPBLOCKSHIFT; + xh = (viletryx - bmaporgx + MAXRADIUS * 2) >> MAPBLOCKSHIFT; + yl = (viletryy - bmaporgy - MAXRADIUS * 2) >> MAPBLOCKSHIFT; + yh = (viletryy - bmaporgy + MAXRADIUS * 2) >> MAPBLOCKSHIFT; + + vileobj = actor; + for (bx = xl; bx <= xh; bx++) { + for (by = yl; by <= yh; by++) { + // Call PIT_VileCheck to check + // whether object is a corpse + // that canbe raised. + if (!P_BlockThingsIterator(bx, by, PIT_VileCheck)) { + // got one! + temp = actor->target; + actor->target = corpsehit; + A_FaceTarget(actor); + actor->target = temp; + + P_SetMobjState(actor, S_VILE_HEAL1); + S_StartSound(corpsehit, sfx_slop); + info = corpsehit->info; + + P_SetMobjState(corpsehit, info->raisestate); + corpsehit->height <<= 2; + corpsehit->flags = info->flags; + corpsehit->health = info->spawnhealth; + corpsehit->target = NULL; + + return; + } + } + } + } + + // Return to normal attack. + A_Chase(actor); +} + +// +// A_VileStart +// +void A_VileStart(mobj_t *actor) { S_StartSound(actor, sfx_vilatk); } + +// +// A_Fire +// Keep fire in front of player unless out of sight +// +void A_Fire(mobj_t *actor); + +void A_StartFire(mobj_t *actor) { + S_StartSound(actor, sfx_flamst); + A_Fire(actor); +} + +void A_FireCrackle(mobj_t *actor) { + S_StartSound(actor, sfx_flame); + A_Fire(actor); +} + +void A_Fire(mobj_t *actor) { + mobj_t *dest; + mobj_t *target; + unsigned an; + + dest = actor->tracer; + if (!dest) + return; + + target = P_SubstNullMobj(actor->target); + + // don't move it if the vile lost sight + if (!P_CheckSight(target, dest)) + return; + + an = dest->angle >> ANGLETOFINESHIFT; + + P_UnsetThingPosition(actor); + actor->x = dest->x + FixedMul(24 * FRACUNIT, finecosine[an]); + actor->y = dest->y + FixedMul(24 * FRACUNIT, finesine[an]); + actor->z = dest->z; + P_SetThingPosition(actor); +} + +// +// A_VileTarget +// Spawn the hellfire +// +void A_VileTarget(mobj_t *actor) { + mobj_t *fog; + + if (!actor->target) + return; + + A_FaceTarget(actor); + + fog = P_SpawnMobj(actor->target->x, actor->target->x, actor->target->z, + MT_FIRE); + + actor->tracer = fog; + fog->target = actor; + fog->tracer = actor->target; + A_Fire(fog); +} + +// +// A_VileAttack +// +void A_VileAttack(mobj_t *actor) { + mobj_t *fire; + int an; + + if (!actor->target) + return; + + A_FaceTarget(actor); + + if (!P_CheckSight(actor, actor->target)) + return; + + S_StartSound(actor, sfx_barexp); + P_DamageMobj(actor->target, actor, actor, 20); + actor->target->momz = 1000 * FRACUNIT / actor->target->info->mass; + + an = actor->angle >> ANGLETOFINESHIFT; + + fire = actor->tracer; + + if (!fire) + return; + + // move the fire between the vile and the player + fire->x = actor->target->x - FixedMul(24 * FRACUNIT, finecosine[an]); + fire->y = actor->target->y - FixedMul(24 * FRACUNIT, finesine[an]); + P_RadiusAttack(fire, actor, 70); +} + +// +// Mancubus attack, +// firing three missiles (bruisers) +// in three different directions? +// Doesn't look like it. +// +#define FATSPREAD (ANG90 / 8) + +void A_FatRaise(mobj_t *actor) { + A_FaceTarget(actor); + S_StartSound(actor, sfx_manatk); +} + +void A_FatAttack1(mobj_t *actor) { + mobj_t *mo; + mobj_t *target; + int an; + + A_FaceTarget(actor); + + // Change direction to ... + actor->angle += FATSPREAD; + target = P_SubstNullMobj(actor->target); + P_SpawnMissile(actor, target, MT_FATSHOT); + + mo = P_SpawnMissile(actor, target, MT_FATSHOT); + mo->angle += FATSPREAD; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul(mo->info->speed, finecosine[an]); + mo->momy = FixedMul(mo->info->speed, finesine[an]); +} + +void A_FatAttack2(mobj_t *actor) { + mobj_t *mo; + mobj_t *target; + int an; + + A_FaceTarget(actor); + // Now here choose opposite deviation. + actor->angle -= FATSPREAD; + target = P_SubstNullMobj(actor->target); + P_SpawnMissile(actor, target, MT_FATSHOT); + + mo = P_SpawnMissile(actor, target, MT_FATSHOT); + mo->angle -= FATSPREAD * 2; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul(mo->info->speed, finecosine[an]); + mo->momy = FixedMul(mo->info->speed, finesine[an]); +} + +void A_FatAttack3(mobj_t *actor) { + mobj_t *mo; + mobj_t *target; + int an; + + A_FaceTarget(actor); + + target = P_SubstNullMobj(actor->target); + + mo = P_SpawnMissile(actor, target, MT_FATSHOT); + mo->angle -= FATSPREAD / 2; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul(mo->info->speed, finecosine[an]); + mo->momy = FixedMul(mo->info->speed, finesine[an]); + + mo = P_SpawnMissile(actor, target, MT_FATSHOT); + mo->angle += FATSPREAD / 2; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul(mo->info->speed, finecosine[an]); + mo->momy = FixedMul(mo->info->speed, finesine[an]); +} + +// +// SkullAttack +// Fly at the player like a missile. +// +#define SKULLSPEED (20 * FRACUNIT) + +void A_SkullAttack(mobj_t *actor) { + mobj_t *dest; + angle_t an; + int dist; + + if (!actor->target) + return; + + dest = actor->target; + actor->flags |= MF_SKULLFLY; + + S_StartSound(actor, actor->info->attacksound); + A_FaceTarget(actor); + an = actor->angle >> ANGLETOFINESHIFT; + actor->momx = FixedMul(SKULLSPEED, finecosine[an]); + actor->momy = FixedMul(SKULLSPEED, finesine[an]); + dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y); + dist = dist / SKULLSPEED; + + if (dist < 1) + dist = 1; + actor->momz = (dest->z + (dest->height >> 1) - actor->z) / dist; +} + +// +// A_PainShootSkull +// Spawn a lost soul and launch it at the target +// +void A_PainShootSkull(mobj_t *actor, angle_t angle) { + fixed_t x; + fixed_t y; + fixed_t z; + + mobj_t *newmobj; + angle_t an; + int prestep; + int count; + thinker_t *currentthinker; + + // count total number of skull currently on the level + count = 0; + + currentthinker = thinkercap.next; + while (currentthinker != &thinkercap) { + if ((currentthinker->function.acp1 == (actionf_p1)P_MobjThinker) && + ((mobj_t *)currentthinker)->type == MT_SKULL) + count++; + currentthinker = currentthinker->next; + } + + // if there are allready 20 skulls on the level, + // don't spit another one + if (count > 20) + return; + + // okay, there's playe for another one + an = angle >> ANGLETOFINESHIFT; + + prestep = + 4 * FRACUNIT + 3 * (actor->info->radius + mobjinfo[MT_SKULL].radius) / 2; + + x = actor->x + FixedMul(prestep, finecosine[an]); + y = actor->y + FixedMul(prestep, finesine[an]); + z = actor->z + 8 * FRACUNIT; + + newmobj = P_SpawnMobj(x, y, z, MT_SKULL); + + // Check for movements. + if (!P_TryMove(newmobj, newmobj->x, newmobj->y)) { + // kill it immediately + P_DamageMobj(newmobj, actor, actor, 10000); + return; + } + + newmobj->target = actor->target; + A_SkullAttack(newmobj); +} + +// +// A_PainAttack +// Spawn a lost soul and launch it at the target +// +void A_PainAttack(mobj_t *actor) { + if (!actor->target) + return; + + A_FaceTarget(actor); + A_PainShootSkull(actor, actor->angle); +} + +void A_PainDie(mobj_t *actor) { + A_Fall(actor); + A_PainShootSkull(actor, actor->angle + ANG90); + A_PainShootSkull(actor, actor->angle + ANG180); + A_PainShootSkull(actor, actor->angle + ANG270); +} + +void A_Scream(mobj_t *actor) { + int sound; + + switch (actor->info->deathsound) { + case 0: + return; + + case sfx_podth1: + case sfx_podth2: + case sfx_podth3: + sound = sfx_podth1 + P_Random() % 3; + break; + + case sfx_bgdth1: + case sfx_bgdth2: + sound = sfx_bgdth1 + P_Random() % 2; + break; + + default: + sound = actor->info->deathsound; + break; + } + + // Check for bosses. + if (actor->type == MT_SPIDER || actor->type == MT_CYBORG) { + // full volume + S_StartSound(NULL, sound); + } else + S_StartSound(actor, sound); +} + +void A_XScream(mobj_t *actor) { S_StartSound(actor, sfx_slop); } + +void A_Pain(mobj_t *actor) { + if (actor->info->painsound) + S_StartSound(actor, actor->info->painsound); +} + +void A_Fall(mobj_t *actor) { + // actor is on ground, it can be walked over + actor->flags &= ~MF_SOLID; + + // So change this if corpse objects + // are meant to be obstacles. +} + +// +// A_Explode +// +void A_Explode(mobj_t *thingy) { P_RadiusAttack(thingy, thingy->target, 128); } + +// Check whether the death of the specified monster type is allowed +// to trigger the end of episode special action. +// +// This behavior changed in v1.9, the most notable effect of which +// was to break uac_dead.wad + +static boolean CheckBossEnd(mobjtype_t motype) { + if (gameversion < exe_ultimate) { + if (gamemap != 8) { + return false; + } + + // Baron death on later episodes is nothing special. + + if (motype == MT_BRUISER && gameepisode != 1) { + return false; + } + + return true; + } else { + // New logic that appeared in Ultimate Doom. + // Looks like the logic was overhauled while adding in the + // episode 4 support. Now bosses only trigger on their + // specific episode. + + switch (gameepisode) { + case 1: + return gamemap == 8 && motype == MT_BRUISER; + + case 2: + return gamemap == 8 && motype == MT_CYBORG; + + case 3: + return gamemap == 8 && motype == MT_SPIDER; + + case 4: + return (gamemap == 6 && motype == MT_CYBORG) || + (gamemap == 8 && motype == MT_SPIDER); + + default: + return gamemap == 8; + } + } +} + +// +// A_BossDeath +// Possibly trigger special effects +// if on first boss level +// +void A_BossDeath(mobj_t *mo) { + thinker_t *th; + mobj_t *mo2; + line_t junk; + int i; + + if (gamemode == commercial) { + if (gamemap != 7) + return; + + if ((mo->type != MT_FATSO) && (mo->type != MT_BABY)) + return; + } else { + if (!CheckBossEnd(mo->type)) { + return; + } + } + + // make sure there is a player alive for victory + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i] && players[i].health > 0) + break; + + if (i == MAXPLAYERS) + return; // no one left alive, so do not end game + + // scan the remaining thinkers to see + // if all bosses are dead + for (th = thinkercap.next; th != &thinkercap; th = th->next) { + if (th->function.acp1 != (actionf_p1)P_MobjThinker) + continue; + + mo2 = (mobj_t *)th; + if (mo2 != mo && mo2->type == mo->type && mo2->health > 0) { + // other boss not dead + return; + } + } + + // victory! + if (gamemode == commercial) { + if (gamemap == 7) { + if (mo->type == MT_FATSO) { + junk.tag = 666; + EV_DoFloor(&junk, lowerFloorToLowest); + return; + } + + if (mo->type == MT_BABY) { + junk.tag = 667; + EV_DoFloor(&junk, raiseToTexture); + return; + } + } + } else { + switch (gameepisode) { + case 1: + junk.tag = 666; + EV_DoFloor(&junk, lowerFloorToLowest); + return; + break; + + case 4: + switch (gamemap) { + case 6: + junk.tag = 666; + EV_DoDoor(&junk, vld_blazeOpen); + return; + break; + + case 8: + junk.tag = 666; + EV_DoFloor(&junk, lowerFloorToLowest); + return; + break; + } + } + } + + G_ExitLevel(); +} + +void A_Hoof(mobj_t *mo) { + S_StartSound(mo, sfx_hoof); + A_Chase(mo); +} + +void A_Metal(mobj_t *mo) { + S_StartSound(mo, sfx_metal); + A_Chase(mo); +} + +void A_BabyMetal(mobj_t *mo) { + S_StartSound(mo, sfx_bspwlk); + A_Chase(mo); +} + +void A_OpenShotgun2(player_t *player, pspdef_t *psp) { + S_StartSound(player->mo, sfx_dbopn); +} + +void A_LoadShotgun2(player_t *player, pspdef_t *psp) { + S_StartSound(player->mo, sfx_dbload); +} + +void A_ReFire(player_t *player, pspdef_t *psp); + +void A_CloseShotgun2(player_t *player, pspdef_t *psp) { + S_StartSound(player->mo, sfx_dbcls); + A_ReFire(player, psp); +} + +mobj_t *braintargets[32]; +int numbraintargets; +int braintargeton = 0; + +void A_BrainAwake(mobj_t *mo) { + thinker_t *thinker; + mobj_t *m; + + // find all the target spots + numbraintargets = 0; + braintargeton = 0; + + thinker = thinkercap.next; + for (thinker = thinkercap.next; thinker != &thinkercap; + thinker = thinker->next) { + if (thinker->function.acp1 != (actionf_p1)P_MobjThinker) + continue; // not a mobj + + m = (mobj_t *)thinker; + + if (m->type == MT_BOSSTARGET) { + braintargets[numbraintargets] = m; + numbraintargets++; + } + } + + S_StartSound(NULL, sfx_bossit); +} + +void A_BrainPain(mobj_t *mo) { S_StartSound(NULL, sfx_bospn); } + +void A_BrainScream(mobj_t *mo) { + int x; + int y; + int z; + mobj_t *th; + + for (x = mo->x - 196 * FRACUNIT; x < mo->x + 320 * FRACUNIT; + x += FRACUNIT * 8) { + y = mo->y - 320 * FRACUNIT; + z = 128 + P_Random() * 2 * FRACUNIT; + th = P_SpawnMobj(x, y, z, MT_ROCKET); + th->momz = P_Random() * 512; + + P_SetMobjState(th, S_BRAINEXPLODE1); + + th->tics -= P_Random() & 7; + if (th->tics < 1) + th->tics = 1; + } + + S_StartSound(NULL, sfx_bosdth); +} + +void A_BrainExplode(mobj_t *mo) { + int x; + int y; + int z; + mobj_t *th; + + x = mo->x + P_SubRandom() * 2048; + y = mo->y; + z = 128 + P_Random() * 2 * FRACUNIT; + th = P_SpawnMobj(x, y, z, MT_ROCKET); + th->momz = P_Random() * 512; + + P_SetMobjState(th, S_BRAINEXPLODE1); + + th->tics -= P_Random() & 7; + if (th->tics < 1) + th->tics = 1; +} + +void A_BrainDie(mobj_t *mo) { G_ExitLevel(); } + +void A_BrainSpit(mobj_t *mo) { + mobj_t *targ; + mobj_t *newmobj; + + static int easy = 0; + + easy ^= 1; + if (gameskill <= sk_easy && (!easy)) + return; + + // shoot a cube at current target + targ = braintargets[braintargeton]; + braintargeton = (braintargeton + 1) % numbraintargets; + + // spawn brain missile + newmobj = P_SpawnMissile(mo, targ, MT_SPAWNSHOT); + newmobj->target = targ; + newmobj->reactiontime = + ((targ->y - mo->y) / newmobj->momy) / newmobj->state->tics; + + S_StartSound(NULL, sfx_bospit); +} + +void A_SpawnFly(mobj_t *mo); + +// travelling cube sound +void A_SpawnSound(mobj_t *mo) { + S_StartSound(mo, sfx_boscub); + A_SpawnFly(mo); +} + +void A_SpawnFly(mobj_t *mo) { + mobj_t *newmobj; + mobj_t *fog; + mobj_t *targ; + int r; + mobjtype_t type; + + if (--mo->reactiontime) + return; // still flying + + targ = P_SubstNullMobj(mo->target); + + // First spawn teleport fog. + fog = P_SpawnMobj(targ->x, targ->y, targ->z, MT_SPAWNFIRE); + S_StartSound(fog, sfx_telept); + + // Randomly select monster to spawn. + r = P_Random(); + + // Probability distribution (kind of :), + // decreasing likelihood. + if (r < 50) + type = MT_TROOP; + else if (r < 90) + type = MT_SERGEANT; + else if (r < 120) + type = MT_SHADOWS; + else if (r < 130) + type = MT_PAIN; + else if (r < 160) + type = MT_HEAD; + else if (r < 162) + type = MT_VILE; + else if (r < 172) + type = MT_UNDEAD; + else if (r < 192) + type = MT_BABY; + else if (r < 222) + type = MT_FATSO; + else if (r < 246) + type = MT_KNIGHT; + else + type = MT_BRUISER; + + newmobj = P_SpawnMobj(targ->x, targ->y, targ->z, type); + if (P_LookForPlayers(newmobj, true)) + P_SetMobjState(newmobj, newmobj->info->seestate); + + // telefrag anything in this spot + P_TeleportMove(newmobj, newmobj->x, newmobj->y); + + // remove self (i.e., cube). + P_RemoveMobj(mo); +} + +void A_PlayerScream(mobj_t *mo) { + // Default death sound. + int sound = sfx_pldeth; + + if ((gamemode == commercial) && (mo->health < -50)) { + // IF THE PLAYER DIES + // LESS THAN -50% WITHOUT GIBBING + sound = sfx_pdiehi; + } + + S_StartSound(mo, sound); +} diff --git a/client/src/p_floor.c b/client/src/p_floor.c new file mode 100644 index 0000000..c06c245 --- /dev/null +++ b/client/src/p_floor.c @@ -0,0 +1,469 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Floor animation: raising stairs. +// + +#include +#include + +#include "d_think.h" +#include "doomdata.h" +// State. +#include "doomstat.h" +#include "doomtype.h" +#include "m_fixed.h" +#include "p_local.h" +#include "p_spec.h" +#include "r_defs.h" +#include "r_state.h" +#include "s_sound.h" +// Data. +#include "sounds.h" +#include "z_zone.h" + +// +// FLOORS +// + +// +// Move a plane (floor or ceiling) and check for crushing +// +result_e T_MovePlane(sector_t *sector, fixed_t speed, fixed_t dest, + boolean crush, int floorOrCeiling, int direction) { + boolean flag; + fixed_t lastpos; + + switch (floorOrCeiling) { + case 0: + // FLOOR + switch (direction) { + case -1: + // DOWN + if (sector->floorheight - speed < dest) { + lastpos = sector->floorheight; + sector->floorheight = dest; + flag = P_ChangeSector(sector, crush); + if (flag == true) { + sector->floorheight = lastpos; + P_ChangeSector(sector, crush); + // return crushed; + } + return pastdest; + } else { + lastpos = sector->floorheight; + sector->floorheight -= speed; + flag = P_ChangeSector(sector, crush); + if (flag == true) { + sector->floorheight = lastpos; + P_ChangeSector(sector, crush); + return crushed; + } + } + break; + + case 1: + // UP + if (sector->floorheight + speed > dest) { + lastpos = sector->floorheight; + sector->floorheight = dest; + flag = P_ChangeSector(sector, crush); + if (flag == true) { + sector->floorheight = lastpos; + P_ChangeSector(sector, crush); + // return crushed; + } + return pastdest; + } else { + // COULD GET CRUSHED + lastpos = sector->floorheight; + sector->floorheight += speed; + flag = P_ChangeSector(sector, crush); + if (flag == true) { + if (crush == true) + return crushed; + sector->floorheight = lastpos; + P_ChangeSector(sector, crush); + return crushed; + } + } + break; + } + break; + + case 1: + // CEILING + switch (direction) { + case -1: + // DOWN + if (sector->ceilingheight - speed < dest) { + lastpos = sector->ceilingheight; + sector->ceilingheight = dest; + flag = P_ChangeSector(sector, crush); + + if (flag == true) { + sector->ceilingheight = lastpos; + P_ChangeSector(sector, crush); + // return crushed; + } + return pastdest; + } else { + // COULD GET CRUSHED + lastpos = sector->ceilingheight; + sector->ceilingheight -= speed; + flag = P_ChangeSector(sector, crush); + + if (flag == true) { + if (crush == true) + return crushed; + sector->ceilingheight = lastpos; + P_ChangeSector(sector, crush); + return crushed; + } + } + break; + + case 1: + // UP + if (sector->ceilingheight + speed > dest) { + lastpos = sector->ceilingheight; + sector->ceilingheight = dest; + flag = P_ChangeSector(sector, crush); + if (flag == true) { + sector->ceilingheight = lastpos; + P_ChangeSector(sector, crush); + // return crushed; + } + return pastdest; + } else { + lastpos = sector->ceilingheight; + sector->ceilingheight += speed; + flag = P_ChangeSector(sector, crush); +// UNUSED +#if 0 + if (flag == true) + { + sector->ceilingheight = lastpos; + P_ChangeSector(sector,crush); + return crushed; + } +#endif + } + break; + } + break; + } + return ok; +} + +// +// MOVE A FLOOR TO IT'S DESTINATION (UP OR DOWN) +// +void T_MoveFloor(floormove_t *floor) { + result_e res; + + res = T_MovePlane(floor->sector, floor->speed, floor->floordestheight, + floor->crush, 0, floor->direction); + + if (!(leveltime & 7)) + S_StartSound(&floor->sector->soundorg, sfx_stnmov); + + if (res == pastdest) { + floor->sector->specialdata = NULL; + + if (floor->direction == 1) { + switch (floor->type) { + case donutRaise: + floor->sector->special = floor->newspecial; + floor->sector->floorpic = floor->texture; + default: + break; + } + } else if (floor->direction == -1) { + switch (floor->type) { + case lowerAndChange: + floor->sector->special = floor->newspecial; + floor->sector->floorpic = floor->texture; + default: + break; + } + } + P_RemoveThinker(&floor->thinker); + + S_StartSound(&floor->sector->soundorg, sfx_pstop); + } +} + +// +// HANDLE FLOOR TYPES +// +int EV_DoFloor(line_t *line, floor_e floortype) { + int secnum; + int rtn; + int i; + sector_t *sec; + floormove_t *floor; + + secnum = -1; + rtn = 0; + while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0) { + sec = §ors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (sec->specialdata) + continue; + + // new floor thinker + rtn = 1; + floor = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0); + P_AddThinker(&floor->thinker); + sec->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor; + floor->type = floortype; + floor->crush = false; + + switch (floortype) { + case lowerFloor: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = P_FindHighestFloorSurrounding(sec); + break; + + case lowerFloorToLowest: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = P_FindLowestFloorSurrounding(sec); + break; + + case turboLower: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED * 4; + floor->floordestheight = P_FindHighestFloorSurrounding(sec); + if (floor->floordestheight != sec->floorheight) + floor->floordestheight += 8 * FRACUNIT; + break; + + case raiseFloorCrush: + floor->crush = true; + case raiseFloor: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = P_FindLowestCeilingSurrounding(sec); + if (floor->floordestheight > sec->ceilingheight) + floor->floordestheight = sec->ceilingheight; + floor->floordestheight -= (8 * FRACUNIT) * (floortype == raiseFloorCrush); + break; + + case raiseFloorTurbo: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED * 4; + floor->floordestheight = P_FindNextHighestFloor(sec, sec->floorheight); + break; + + case raiseFloorToNearest: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = P_FindNextHighestFloor(sec, sec->floorheight); + break; + + case raiseFloor24: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = floor->sector->floorheight + 24 * FRACUNIT; + break; + case raiseFloor512: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = floor->sector->floorheight + 512 * FRACUNIT; + break; + + case raiseFloor24AndChange: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = floor->sector->floorheight + 24 * FRACUNIT; + sec->floorpic = line->frontsector->floorpic; + sec->special = line->frontsector->special; + break; + + case raiseToTexture: { + int minsize = INT_MAX; + side_t *side; + + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + for (i = 0; i < sec->linecount; i++) { + if (twoSided(secnum, i)) { + side = getSide(secnum, i, 0); + if (side->bottomtexture >= 0) + if (textureheight[side->bottomtexture] < minsize) + minsize = textureheight[side->bottomtexture]; + side = getSide(secnum, i, 1); + if (side->bottomtexture >= 0) + if (textureheight[side->bottomtexture] < minsize) + minsize = textureheight[side->bottomtexture]; + } + } + floor->floordestheight = floor->sector->floorheight + minsize; + } break; + + case lowerAndChange: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = P_FindLowestFloorSurrounding(sec); + floor->texture = sec->floorpic; + + for (i = 0; i < sec->linecount; i++) { + if (twoSided(secnum, i)) { + if (getSide(secnum, i, 0)->sector - sectors == secnum) { + sec = getSector(secnum, i, 1); + + if (sec->floorheight == floor->floordestheight) { + floor->texture = sec->floorpic; + floor->newspecial = sec->special; + break; + } + } else { + sec = getSector(secnum, i, 0); + + if (sec->floorheight == floor->floordestheight) { + floor->texture = sec->floorpic; + floor->newspecial = sec->special; + break; + } + } + } + } + default: + break; + } + } + return rtn; +} + +// +// BUILD A STAIRCASE! +// +int EV_BuildStairs(line_t *line, stair_e type) { + int secnum; + int height; + int i; + int newsecnum; + int texture; + int ok; + int rtn; + + sector_t *sec; + sector_t *tsec; + + floormove_t *floor; + + fixed_t stairsize = 0; + fixed_t speed = 0; + + secnum = -1; + rtn = 0; + while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0) { + sec = §ors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (sec->specialdata) + continue; + + // new floor thinker + rtn = 1; + floor = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0); + P_AddThinker(&floor->thinker); + sec->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor; + floor->direction = 1; + floor->sector = sec; + switch (type) { + case build8: + speed = FLOORSPEED / 4; + stairsize = 8 * FRACUNIT; + break; + case turbo16: + speed = FLOORSPEED * 4; + stairsize = 16 * FRACUNIT; + break; + } + floor->speed = speed; + height = sec->floorheight + stairsize; + floor->floordestheight = height; + // Initialize + floor->type = lowerFloor; + floor->crush = true; + + texture = sec->floorpic; + + // Find next sector to raise + // 1. Find 2-sided line with same sector side[0] + // 2. Other side is the next sector to raise + do { + ok = 0; + for (i = 0; i < sec->linecount; i++) { + if (!((sec->lines[i])->flags & ML_TWOSIDED)) + continue; + + tsec = (sec->lines[i])->frontsector; + newsecnum = tsec - sectors; + + if (secnum != newsecnum) + continue; + + tsec = (sec->lines[i])->backsector; + newsecnum = tsec - sectors; + + if (tsec->floorpic != texture) + continue; + + height += stairsize; + + if (tsec->specialdata) + continue; + + sec = tsec; + secnum = newsecnum; + floor = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0); + + P_AddThinker(&floor->thinker); + + sec->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor; + floor->direction = 1; + floor->sector = sec; + floor->speed = speed; + floor->floordestheight = height; + // Initialize + floor->type = lowerFloor; + floor->crush = true; + ok = 1; + break; + } + } while (ok); + } + return rtn; +} diff --git a/client/src/p_inter.c b/client/src/p_inter.c new file mode 100644 index 0000000..291e4f1 --- /dev/null +++ b/client/src/p_inter.c @@ -0,0 +1,795 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Handling interactions (i.e., collisions). +// + +#include + +#include "am_map.h" +#include "d_englsh.h" +#include "d_items.h" +#include "d_mode.h" +// Data. +#include "doomdef.h" +#include "doomstat.h" +#include "i_system.h" +#include "info.h" +#include "m_fixed.h" +#include "m_random.h" +#include "p_inter.h" +#include "p_local.h" +#include "p_mobj.h" +#include "r_defs.h" +#include "r_main.h" +#include "s_sound.h" +#include "sounds.h" +#include "tables.h" + +#define BONUSADD 6 + +// a weapon is found with two clip loads, +// a big item has five clip loads +int maxammo[NUMAMMO] = {200, 50, 300, 50}; +int clipammo[NUMAMMO] = {10, 4, 20, 1}; + +// +// GET STUFF +// + +// +// P_GiveAmmo +// Num is the number of clip loads, +// not the individual count (0= 1/2 clip). +// Returns false if the ammo can't be picked up at all +// + +boolean P_GiveAmmo(player_t *player, ammotype_t ammo, int num) { + int oldammo; + + if (ammo == am_noammo) + return false; + + if (ammo > NUMAMMO) + I_Error("P_GiveAmmo: bad type %i", ammo); + + if (player->ammo[ammo] == player->maxammo[ammo]) + return false; + + if (num) + num *= clipammo[ammo]; + else + num = clipammo[ammo] / 2; + + if (gameskill == sk_baby || gameskill == sk_nightmare) { + // give double ammo in trainer mode, + // you'll need in nightmare + num <<= 1; + } + + oldammo = player->ammo[ammo]; + player->ammo[ammo] += num; + + if (player->ammo[ammo] > player->maxammo[ammo]) + player->ammo[ammo] = player->maxammo[ammo]; + + // If non zero ammo, + // don't change up weapons, + // player was lower on purpose. + if (oldammo) + return true; + + // We were down to zero, + // so select a new weapon. + // Preferences are not user selectable. + switch (ammo) { + case am_clip: + if (player->readyweapon == wp_fist) { + if (player->weaponowned[wp_chaingun]) + player->pendingweapon = wp_chaingun; + else + player->pendingweapon = wp_pistol; + } + break; + + case am_shell: + if (player->readyweapon == wp_fist || player->readyweapon == wp_pistol) { + if (player->weaponowned[wp_shotgun]) + player->pendingweapon = wp_shotgun; + } + break; + + case am_cell: + if (player->readyweapon == wp_fist || player->readyweapon == wp_pistol) { + if (player->weaponowned[wp_plasma]) + player->pendingweapon = wp_plasma; + } + break; + + case am_misl: + if (player->readyweapon == wp_fist) { + if (player->weaponowned[wp_missile]) + player->pendingweapon = wp_missile; + } + default: + break; + } + + return true; +} + +// +// P_GiveWeapon +// The weapon name may have a MF_DROPPED flag ored in. +// +boolean P_GiveWeapon(player_t *player, weapontype_t weapon, boolean dropped) { + boolean gaveammo; + boolean gaveweapon; + + if (netgame && (deathmatch != 2) && !dropped) { + // leave placed weapons forever on net games + if (player->weaponowned[weapon]) + return false; + + player->bonuscount += BONUSADD; + player->weaponowned[weapon] = true; + + if (deathmatch) + P_GiveAmmo(player, weaponinfo[weapon].ammo, 5); + else + P_GiveAmmo(player, weaponinfo[weapon].ammo, 2); + player->pendingweapon = weapon; + + if (player == &players[consoleplayer]) + S_StartSound(NULL, sfx_wpnup); + return false; + } + + if (weaponinfo[weapon].ammo != am_noammo) { + // give one clip with a dropped weapon, + // two clips with a found weapon + if (dropped) + gaveammo = P_GiveAmmo(player, weaponinfo[weapon].ammo, 1); + else + gaveammo = P_GiveAmmo(player, weaponinfo[weapon].ammo, 2); + } else + gaveammo = false; + + if (player->weaponowned[weapon]) + gaveweapon = false; + else { + gaveweapon = true; + player->weaponowned[weapon] = true; + player->pendingweapon = weapon; + } + + return (gaveweapon || gaveammo); +} + +// +// P_GiveBody +// Returns false if the body isn't needed at all +// +boolean P_GiveBody(player_t *player, int num) { + if (player->health >= MAXHEALTH) + return false; + + player->health += num; + if (player->health > MAXHEALTH) + player->health = MAXHEALTH; + player->mo->health = player->health; + + return true; +} + +// +// P_GiveArmor +// Returns false if the armor is worse +// than the current armor. +// +boolean P_GiveArmor(player_t *player, int armortype) { + int hits; + + hits = armortype * 100; + if (player->armorpoints >= hits) + return false; // don't pick up + + player->armortype = armortype; + player->armorpoints = hits; + + return true; +} + +// +// P_GiveCard +// +void P_GiveCard(player_t *player, card_t card) { + if (player->cards[card]) + return; + + player->bonuscount = BONUSADD; + player->cards[card] = 1; +} + +// +// P_GivePower +// +boolean P_GivePower(player_t *player, int /*powertype_t*/ power) { + if (power == pw_invulnerability) { + player->powers[power] = INVULNTICS; + return true; + } + + if (power == pw_invisibility) { + player->powers[power] = INVISTICS; + player->mo->flags |= MF_SHADOW; + return true; + } + + if (power == pw_infrared) { + player->powers[power] = INFRATICS; + return true; + } + + if (power == pw_ironfeet) { + player->powers[power] = IRONTICS; + return true; + } + + if (power == pw_strength) { + P_GiveBody(player, 100); + player->powers[power] = 1; + return true; + } + + if (player->powers[power]) + return false; // already got it + + player->powers[power] = 1; + return true; +} + +// +// P_TouchSpecialThing +// +void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher) { + player_t *player; + int i; + fixed_t delta; + int sound; + + delta = special->z - toucher->z; + + if (delta > toucher->height || delta < -8 * FRACUNIT) { + // out of reach + return; + } + + sound = sfx_itemup; + player = toucher->player; + + // Dead thing touching. + // Can happen with a sliding player corpse. + if (toucher->health <= 0) + return; + + // Identify by sprite. + switch (special->sprite) { + // armor + case SPR_ARM1: + // TODO: replace 1 below with GREEN armor constant + if (!P_GiveArmor(player, 1)) + return; + player->message = (GOTARMOR); + break; + + case SPR_ARM2: + // TODO: replace 2 below with BLUE armor constant + if (!P_GiveArmor(player, 2)) + return; + player->message = (GOTMEGA); + break; + + // bonus items + case SPR_BON1: + player->health++; // can go over 100% + // TODO: replace 200 below with a constant + if (player->health > 200) + player->health = 200; + player->mo->health = player->health; + player->message = (GOTHTHBONUS); + break; + + case SPR_BON2: + player->armorpoints++; // can go over 100% + if (player->armorpoints > 200) + player->armorpoints = 200; + if (!player->armortype) + player->armortype = 1; + player->message = (GOTARMBONUS); + break; + + case SPR_SOUL: + player->health += 100; + if (player->health > 100) + player->health = 100; + player->mo->health = player->health; + player->message = (GOTSUPER); + sound = sfx_getpow; + break; + + case SPR_MEGA: + if (gamemode != commercial) + return; + player->health = 200; + player->mo->health = player->health; + P_GiveArmor(player, 2); + player->message = (GOTMSPHERE); + sound = sfx_getpow; + break; + + // cards + // leave cards for everyone + case SPR_BKEY: + if (!player->cards[it_bluecard]) + player->message = (GOTBLUECARD); + P_GiveCard(player, it_bluecard); + if (!netgame) + break; + return; + + case SPR_YKEY: + if (!player->cards[it_yellowcard]) + player->message = (GOTYELWCARD); + P_GiveCard(player, it_yellowcard); + if (!netgame) + break; + return; + + case SPR_RKEY: + if (!player->cards[it_redcard]) + player->message = (GOTREDCARD); + P_GiveCard(player, it_redcard); + if (!netgame) + break; + return; + + case SPR_BSKU: + if (!player->cards[it_blueskull]) + player->message = (GOTBLUESKUL); + P_GiveCard(player, it_blueskull); + if (!netgame) + break; + return; + + case SPR_YSKU: + if (!player->cards[it_yellowskull]) + player->message = (GOTYELWSKUL); + P_GiveCard(player, it_yellowskull); + if (!netgame) + break; + return; + + case SPR_RSKU: + if (!player->cards[it_redskull]) + player->message = (GOTREDSKULL); + P_GiveCard(player, it_redskull); + if (!netgame) + break; + return; + + // medikits, heals + case SPR_STIM: + if (!P_GiveBody(player, 10)) + return; + player->message = (GOTSTIM); + break; + + case SPR_MEDI: + if (!P_GiveBody(player, 25)) + return; + + if (player->health < 25) + player->message = (GOTMEDINEED); + else + player->message = (GOTMEDIKIT); + break; + + // power ups + case SPR_PINV: + if (!P_GivePower(player, pw_invulnerability)) + return; + player->message = (GOTINVUL); + sound = sfx_getpow; + break; + + case SPR_PSTR: + if (!P_GivePower(player, pw_strength)) + return; + player->message = (GOTBERSERK); + if (player->readyweapon != wp_fist) + player->pendingweapon = wp_fist; + sound = sfx_getpow; + break; + + case SPR_PINS: + if (!P_GivePower(player, pw_invisibility)) + return; + player->message = (GOTINVIS); + sound = sfx_getpow; + break; + + case SPR_SUIT: + if (!P_GivePower(player, pw_ironfeet)) + return; + player->message = (GOTSUIT); + sound = sfx_getpow; + break; + + case SPR_PMAP: + if (!P_GivePower(player, pw_allmap)) + return; + player->message = (GOTMAP); + sound = sfx_getpow; + break; + + case SPR_PVIS: + if (!P_GivePower(player, pw_infrared)) + return; + player->message = (GOTVISOR); + sound = sfx_getpow; + break; + + // ammo + case SPR_CLIP: + if (special->flags & MF_DROPPED) { + if (!P_GiveAmmo(player, am_clip, 0)) + return; + } else { + if (!P_GiveAmmo(player, am_clip, 1)) + return; + } + player->message = (GOTCLIP); + break; + + case SPR_AMMO: + if (!P_GiveAmmo(player, am_clip, 5)) + return; + player->message = (GOTCLIPBOX); + break; + + case SPR_ROCK: + if (!P_GiveAmmo(player, am_misl, 1)) + return; + player->message = (GOTROCKET); + break; + + case SPR_BROK: + if (!P_GiveAmmo(player, am_misl, 5)) + return; + player->message = (GOTROCKBOX); + break; + + case SPR_CELL: + if (!P_GiveAmmo(player, am_cell, 1)) + return; + player->message = (GOTCELL); + break; + + case SPR_CELP: + if (!P_GiveAmmo(player, am_cell, 5)) + return; + player->message = (GOTCELLBOX); + break; + + case SPR_SHEL: + if (!P_GiveAmmo(player, am_shell, 1)) + return; + player->message = (GOTSHELLS); + break; + + case SPR_SBOX: + if (!P_GiveAmmo(player, am_shell, 5)) + return; + player->message = (GOTSHELLBOX); + break; + + case SPR_BPAK: + if (!player->backpack) { + for (i = 0; i < NUMAMMO; i++) + player->maxammo[i] *= 2; + player->backpack = true; + } + for (i = 0; i < NUMAMMO; i++) + P_GiveAmmo(player, i, 1); + player->message = (GOTBACKPACK); + break; + + // weapons + case SPR_BFUG: + if (!P_GiveWeapon(player, wp_bfg, false)) + return; + player->message = (GOTBFG9000); + sound = sfx_wpnup; + break; + + case SPR_MGUN: + if (!P_GiveWeapon(player, wp_chaingun, (special->flags & MF_DROPPED) != 0)) + return; + player->message = (GOTCHAINGUN); + sound = sfx_wpnup; + break; + + case SPR_CSAW: + if (!P_GiveWeapon(player, wp_chainsaw, false)) + return; + player->message = (GOTCHAINSAW); + sound = sfx_wpnup; + break; + + case SPR_LAUN: + if (!P_GiveWeapon(player, wp_missile, false)) + return; + player->message = (GOTLAUNCHER); + sound = sfx_wpnup; + break; + + case SPR_PLAS: + if (!P_GiveWeapon(player, wp_plasma, false)) + return; + player->message = (GOTPLASMA); + sound = sfx_wpnup; + break; + + case SPR_SHOT: + if (!P_GiveWeapon(player, wp_shotgun, (special->flags & MF_DROPPED) != 0)) + return; + player->message = (GOTSHOTGUN); + sound = sfx_wpnup; + break; + + case SPR_SGN2: + if (!P_GiveWeapon(player, wp_supershotgun, + (special->flags & MF_DROPPED) != 0)) + return; + player->message = (GOTSHOTGUN2); + sound = sfx_wpnup; + break; + + default: + I_Error("P_SpecialThing: Unknown gettable thing"); + } + + if (special->flags & MF_COUNTITEM) + player->itemcount++; + P_RemoveMobj(special); + player->bonuscount += BONUSADD; + if (player == &players[consoleplayer]) + S_StartSound(NULL, sound); +} + +// +// KillMobj +// +void P_KillMobj(mobj_t *source, mobj_t *target) { + mobjtype_t item; + mobj_t *mo; + + target->flags &= ~(MF_SHOOTABLE | MF_FLOAT | MF_SKULLFLY); + + if (target->type != MT_SKULL) + target->flags &= ~MF_NOGRAVITY; + + target->flags |= MF_CORPSE | MF_DROPOFF; + target->height >>= 2; + + if (source && source->player) { + // count for intermission + if (target->flags & MF_COUNTKILL) + source->player->killcount++; + + if (target->player) + source->player->frags[target->player - players]++; + } else if (!netgame && (target->flags & MF_COUNTKILL)) { + // count all monster deaths, + // even those caused by other monsters + players[0].killcount++; + } + + if (target->player) { + // count environment kills against you + if (!source) + target->player->frags[target->player - players]++; + + target->flags &= ~MF_SOLID; + target->player->playerstate = PST_DEAD; + P_DropWeapon(target->player); + + if (target->player == &players[consoleplayer] && automapactive) { + // don't die in auto map, + // switch view prior to dying + AM_Stop(); + } + } + + if (target->health < -target->info->spawnhealth && + target->info->xdeathstate) { + P_SetMobjState(target, target->info->xdeathstate); + } else + P_SetMobjState(target, target->info->deathstate); + target->tics -= P_Random() & 3; + + if (target->tics < 1) + target->tics = 1; + + // I_StartSound (&actor->r, actor->info->deathsound); + + // In Chex Quest, monsters don't drop items. + + if (gameversion == exe_chex) { + return; + } + + // Drop stuff. + // This determines the kind of object spawned + // during the death frame of a thing. + switch (target->type) { + case MT_WOLFSS: + case MT_POSSESSED: + item = MT_CLIP; + break; + + case MT_SHOTGUY: + item = MT_SHOTGUN; + break; + + case MT_CHAINGUY: + item = MT_CHAINGUN; + break; + + default: + return; + } + + mo = P_SpawnMobj(target->x, target->y, ONFLOORZ, item); + mo->flags |= MF_DROPPED; // special versions of items +} + +// +// P_DamageMobj +// Damages both enemies and players +// "inflictor" is the thing that caused the damage +// creature or missile, can be NULL (slime, etc) +// "source" is the thing to target after taking damage +// creature or NULL +// Source and inflictor are the same for melee attacks. +// Source can be NULL for slime, barrel explosions +// and other environmental stuff. +// +void P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, + int damage) { + unsigned ang; + int saved; + player_t *player; + fixed_t thrust; + int temp; + + if (!(target->flags & MF_SHOOTABLE)) + return; // shouldn't happen... + + if (target->health <= 0) + return; + + if (target->flags & MF_SKULLFLY) { + target->momx = target->momy = target->momz = 0; + } + + player = target->player; + if (player && gameskill == sk_baby) + damage >>= 1; // take half damage in trainer mode + + // Some close combat weapons should not + // inflict thrust and push the victim out of reach, + // thus kick away unless using the chainsaw. + if (inflictor && !(target->flags & MF_NOCLIP) && + (!source || !source->player || + source->player->readyweapon != wp_chainsaw)) { + ang = R_PointToAngle2(inflictor->x, inflictor->y, target->x, target->y); + + thrust = damage * (FRACUNIT >> 3) * 100 / target->info->mass; + + // make fall forwards sometimes + if (damage < 40 && damage > target->health && + target->z - inflictor->z > 64 * FRACUNIT && (P_Random() & 1)) { + ang += ANG180; + thrust *= 4; + } + + ang >>= ANGLETOFINESHIFT; + target->momx += FixedMul(thrust, finecosine[ang]); + target->momy += FixedMul(thrust, finesine[ang]); + } + + // player specific + if (player) { + // end of game hell hack + if (target->subsector->sector->special == 11 && damage >= target->health) { + damage = target->health - 1; + } + + // Below certain threshold, + // ignore damage in GOD mode, or with INVUL power. + if (damage < 1000 && + ((player->cheats & CF_GODMODE) || player->powers[pw_invulnerability])) { + return; + } + + if (player->armortype) { + if (player->armortype == 1) + saved = damage / 3; + else + saved = damage / 2; + + if (player->armorpoints <= saved) { + // armor is used up + saved = player->armorpoints; + player->armortype = 0; + } + player->armorpoints -= saved; + damage -= saved; + } + player->health -= damage; // mirror mobj health here for Dave + if (player->health < 0) + player->health = 0; + + player->attacker = source; + player->damagecount += damage; // add damage after armor / invuln + + if (player->damagecount > 100) + player->damagecount = 100; // teleport stomp does 10k points... + + temp = damage < 100 ? damage : 100; + + if (player == &players[consoleplayer]) + I_Tactile(40, 10, 40 + temp * 2); + } + + // do the damage + target->health -= damage; + if (target->health <= 0) { + P_KillMobj(source, target); + return; + } + + if ((P_Random() < target->info->painchance) && + !(target->flags & MF_SKULLFLY)) { + target->flags |= MF_JUSTHIT; // fight back! + + P_SetMobjState(target, target->info->painstate); + } + + target->reactiontime = 0; // we're awake now... + + if ((!target->threshold || target->type == MT_VILE) && source && + source != target && source->type != MT_VILE) { + // if not intent on another player, + // chase after this one + target->target = source; + target->threshold = BASETHRESHOLD; + if (target->state == &states[target->info->spawnstate] && + target->info->seestate != S_NULL) + P_SetMobjState(target, target->info->seestate); + } +} diff --git a/client/src/p_inter.h b/client/src/p_inter.h new file mode 100644 index 0000000..c5f7966 --- /dev/null +++ b/client/src/p_inter.h @@ -0,0 +1,27 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// + +#ifndef __P_INTER__ +#define __P_INTER__ + +#include "d_player.h" +#include "doomtype.h" + +boolean P_GivePower(player_t *, int); + +#endif diff --git a/client/src/p_lights.c b/client/src/p_lights.c new file mode 100644 index 0000000..fdb071d --- /dev/null +++ b/client/src/p_lights.c @@ -0,0 +1,290 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Handle Sector base lighting effects. +// Muzzle flash? +// + +#include "d_think.h" +#include "m_random.h" +#include "p_local.h" +#include "p_spec.h" +#include "r_defs.h" +// State. +#include "r_state.h" +#include "z_zone.h" + +// +// FIRELIGHT FLICKER +// + +// +// T_FireFlicker +// +void T_FireFlicker(fireflicker_t *flick) { + int amount; + + if (--flick->count) + return; + + amount = (P_Random() & 3) * 16; + + if (flick->sector->lightlevel - amount < flick->minlight) + flick->sector->lightlevel = flick->minlight; + else + flick->sector->lightlevel = flick->maxlight - amount; + + flick->count = 4; +} + +// +// P_SpawnFireFlicker +// +void P_SpawnFireFlicker(sector_t *sector) { + fireflicker_t *flick; + + // Note that we are resetting sector attributes. + // Nothing special about it during gameplay. + sector->special = 0; + + flick = Z_Malloc(sizeof(*flick), PU_LEVSPEC, 0); + + P_AddThinker(&flick->thinker); + + flick->thinker.function.acp1 = (actionf_p1)T_FireFlicker; + flick->sector = sector; + flick->maxlight = sector->lightlevel; + flick->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel) + 16; + flick->count = 4; +} + +// +// BROKEN LIGHT FLASHING +// + +// +// T_LightFlash +// Do flashing lights. +// +void T_LightFlash(lightflash_t *flash) { + if (--flash->count) + return; + + if (flash->sector->lightlevel == flash->maxlight) { + flash->sector->lightlevel = flash->minlight; + flash->count = (P_Random() & flash->mintime) + 1; + } else { + flash->sector->lightlevel = flash->maxlight; + flash->count = (P_Random() & flash->maxtime) + 1; + } +} + +// +// P_SpawnLightFlash +// After the map has been loaded, scan each sector +// for specials that spawn thinkers +// +void P_SpawnLightFlash(sector_t *sector) { + lightflash_t *flash; + + // nothing special about it during gameplay + sector->special = 0; + + flash = Z_Malloc(sizeof(*flash), PU_LEVSPEC, 0); + + P_AddThinker(&flash->thinker); + + flash->thinker.function.acp1 = (actionf_p1)T_LightFlash; + flash->sector = sector; + flash->maxlight = sector->lightlevel; + + flash->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel); + flash->maxtime = 64; + flash->mintime = 7; + flash->count = (P_Random() & flash->maxtime) + 1; +} + +// +// STROBE LIGHT FLASHING +// + +// +// T_StrobeFlash +// +void T_StrobeFlash(strobe_t *flash) { + if (--flash->count) + return; + + if (flash->sector->lightlevel == flash->minlight) { + flash->sector->lightlevel = flash->maxlight; + flash->count = flash->brighttime; + } else { + flash->sector->lightlevel = flash->minlight; + flash->count = flash->darktime; + } +} + +// +// P_SpawnStrobeFlash +// After the map has been loaded, scan each sector +// for specials that spawn thinkers +// +void P_SpawnStrobeFlash(sector_t *sector, int fastOrSlow, int inSync) { + strobe_t *flash; + + flash = Z_Malloc(sizeof(*flash), PU_LEVSPEC, 0); + + P_AddThinker(&flash->thinker); + + flash->sector = sector; + flash->darktime = fastOrSlow; + flash->brighttime = STROBEBRIGHT; + flash->thinker.function.acp1 = (actionf_p1)T_StrobeFlash; + flash->maxlight = sector->lightlevel; + flash->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel); + + if (flash->minlight == flash->maxlight) + flash->minlight = 0; + + // nothing special about it during gameplay + sector->special = 0; + + if (!inSync) + flash->count = (P_Random() & 7) + 1; + else + flash->count = 1; +} + +// +// Start strobing lights (usually from a trigger) +// +void EV_StartLightStrobing(line_t *line) { + int secnum; + sector_t *sec; + + secnum = -1; + while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0) { + sec = §ors[secnum]; + if (sec->specialdata) + continue; + + P_SpawnStrobeFlash(sec, SLOWDARK, 0); + } +} + +// +// TURN LINE'S TAG LIGHTS OFF +// +void EV_TurnTagLightsOff(line_t *line) { + int i; + int j; + int min; + sector_t *sector; + sector_t *tsec; + line_t *templine; + + sector = sectors; + + for (j = 0; j < numsectors; j++, sector++) { + if (sector->tag == line->tag) { + min = sector->lightlevel; + for (i = 0; i < sector->linecount; i++) { + templine = sector->lines[i]; + tsec = getNextSector(templine, sector); + if (!tsec) + continue; + if (tsec->lightlevel < min) + min = tsec->lightlevel; + } + sector->lightlevel = min; + } + } +} + +// +// TURN LINE'S TAG LIGHTS ON +// +void EV_LightTurnOn(line_t *line, int bright) { + int i; + int j; + sector_t *sector; + sector_t *temp; + line_t *templine; + + sector = sectors; + + for (i = 0; i < numsectors; i++, sector++) { + if (sector->tag == line->tag) { + // bright = 0 means to search + // for highest light level + // surrounding sector + if (!bright) { + for (j = 0; j < sector->linecount; j++) { + templine = sector->lines[j]; + temp = getNextSector(templine, sector); + + if (!temp) + continue; + + if (temp->lightlevel > bright) + bright = temp->lightlevel; + } + } + sector->lightlevel = bright; + } + } +} + +// +// Spawn glowing light +// + +void T_Glow(glow_t *g) { + switch (g->direction) { + case -1: + // DOWN + g->sector->lightlevel -= GLOWSPEED; + if (g->sector->lightlevel <= g->minlight) { + g->sector->lightlevel += GLOWSPEED; + g->direction = 1; + } + break; + + case 1: + // UP + g->sector->lightlevel += GLOWSPEED; + if (g->sector->lightlevel >= g->maxlight) { + g->sector->lightlevel -= GLOWSPEED; + g->direction = -1; + } + break; + } +} + +void P_SpawnGlowingLight(sector_t *sector) { + glow_t *g; + + g = Z_Malloc(sizeof(*g), PU_LEVSPEC, 0); + + P_AddThinker(&g->thinker); + + g->sector = sector; + g->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel); + g->maxlight = sector->lightlevel; + g->thinker.function.acp1 = (actionf_p1)T_Glow; + g->direction = -1; + + sector->special = 0; +} diff --git a/client/src/p_local.h b/client/src/p_local.h new file mode 100644 index 0000000..a808875 --- /dev/null +++ b/client/src/p_local.h @@ -0,0 +1,244 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Play functions, animation, global header. +// + +#ifndef __P_LOCAL__ +#define __P_LOCAL__ + +#ifndef __R_LOCAL__ +#include "r_local.h" +#endif + +#define FLOATSPEED (FRACUNIT * 4) + +#define MAXHEALTH 100 +#define VIEWHEIGHT (41 * FRACUNIT) + +// mapblocks are used to check movement +// against lines and things +#define MAPBLOCKUNITS 128 +#define MAPBLOCKSIZE (MAPBLOCKUNITS * FRACUNIT) +#define MAPBLOCKSHIFT (FRACBITS + 7) +#define MAPBMASK (MAPBLOCKSIZE - 1) +#define MAPBTOFRAC (MAPBLOCKSHIFT - FRACBITS) + +// player radius for movement checking +#define PLAYERRADIUS 16 * FRACUNIT + +// MAXRADIUS is for precalculated sector block boxes +// the spider demon is larger, +// but we do not have any moving sectors nearby +#define MAXRADIUS 32 * FRACUNIT + +#define GRAVITY FRACUNIT +#define MAXMOVE (30 * FRACUNIT) + +#define USERANGE (64 * FRACUNIT) +#define MELEERANGE (64 * FRACUNIT) +#define MISSILERANGE (32 * 64 * FRACUNIT) + +// follow a player exlusively for 3 seconds +#define BASETHRESHOLD 100 + +// +// P_TICK +// + +// both the head and tail of the thinker list +extern thinker_t thinkercap; // LATER + +void P_InitThinkers(void); +void P_AddThinker(thinker_t *thinker); +void P_RemoveThinker(thinker_t *thinker); + +// +// P_PSPR +// +void P_SetupPsprites(player_t *curplayer); +void P_MovePsprites(player_t *curplayer); +void P_DropWeapon(player_t *player); + +// +// P_USER +// +void P_PlayerThink(player_t *player); + +// +// P_MOBJ +// +#define ONFLOORZ INT_MIN +#define ONCEILINGZ INT_MAX + +// Time interval for item respawning. +#define ITEMQUESIZE 128 + +extern mapthing_t itemrespawnque[ITEMQUESIZE]; +extern int itemrespawntime[ITEMQUESIZE]; +extern int iquehead; +extern int iquetail; + +void P_RespawnSpecials(void); + +mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type); + +void P_RemoveMobj(mobj_t *th); +mobj_t *P_SubstNullMobj(mobj_t *th); +boolean P_SetMobjState(mobj_t *mobj, statenum_t state); +void P_MobjThinker(mobj_t *mobj); + +void P_SpawnPuff(fixed_t x, fixed_t y, fixed_t z); +void P_SpawnBlood(fixed_t x, fixed_t y, fixed_t z, int damage); +mobj_t *P_SpawnMissile(mobj_t *source, mobj_t *dest, mobjtype_t type); +void P_SpawnPlayerMissile(mobj_t *source, mobjtype_t type); + +// +// P_ENEMY +// +void P_NoiseAlert(mobj_t *target, mobj_t *emmiter); + +// +// P_MAPUTL +// +typedef struct { + fixed_t x; + fixed_t y; + fixed_t dx; + fixed_t dy; + +} divline_t; + +typedef struct { + fixed_t frac; // along trace line + boolean isaline; + union { + mobj_t *thing; + line_t *line; + } d; +} intercept_t; + +// Extended MAXINTERCEPTS, to allow for intercepts overrun emulation. + +#define MAXINTERCEPTS_ORIGINAL 128 +#define MAXINTERCEPTS (MAXINTERCEPTS_ORIGINAL + 61) + +extern intercept_t intercepts[MAXINTERCEPTS]; +extern intercept_t *intercept_p; + +typedef boolean (*traverser_t)(intercept_t *in); + +fixed_t P_AproxDistance(fixed_t dx, fixed_t dy); +int P_PointOnLineSide(fixed_t x, fixed_t y, line_t *line); +int P_PointOnDivlineSide(fixed_t x, fixed_t y, divline_t *line); +void P_MakeDivline(line_t *li, divline_t *dl); +fixed_t P_InterceptVector(divline_t *v2, divline_t *v1); +int P_BoxOnLineSide(fixed_t *tmbox, line_t *ld); + +extern fixed_t opentop; +extern fixed_t openbottom; +extern fixed_t openrange; +extern fixed_t lowfloor; + +void P_LineOpening(line_t *linedef); + +boolean P_BlockLinesIterator(int x, int y, boolean (*func)(line_t *)); +boolean P_BlockThingsIterator(int x, int y, boolean (*func)(mobj_t *)); + +#define PT_ADDLINES 1 +#define PT_ADDTHINGS 2 +#define PT_EARLYOUT 4 + +extern divline_t trace; + +boolean P_PathTraverse(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, + int flags, boolean (*trav)(intercept_t *)); + +void P_UnsetThingPosition(mobj_t *thing); +void P_SetThingPosition(mobj_t *thing); + +// +// P_MAP +// + +// If "floatok" true, move would be ok +// if within "tmfloorz - tmceilingz". +extern boolean floatok; +extern fixed_t tmfloorz; +extern fixed_t tmceilingz; + +extern line_t *ceilingline; + +// fraggle: I have increased the size of this buffer. In the original Doom, +// overrunning past this limit caused other bits of memory to be overwritten, +// affecting demo playback. However, in doing so, the limit was still +// exceeded. So we have to support more than 8 specials. +// +// We keep the original limit, to detect what variables in memory were +// overwritten (see SpechitOverrun()) + +#define MAXSPECIALCROSS 20 +#define MAXSPECIALCROSS_ORIGINAL 8 + +extern line_t *spechit[MAXSPECIALCROSS]; +extern int numspechit; + +boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y); +boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y); +boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y); +void P_SlideMove(mobj_t *mo); +boolean P_CheckSight(mobj_t *t1, mobj_t *t2); +void P_UseLines(player_t *player); + +boolean P_ChangeSector(sector_t *sector, boolean crunch); + +extern mobj_t *linetarget; // who got hit (or NULL) + +fixed_t P_AimLineAttack(mobj_t *t1, angle_t angle, fixed_t distance); + +void P_LineAttack(mobj_t *t1, angle_t angle, fixed_t distance, fixed_t slope, + int damage); + +void P_RadiusAttack(mobj_t *spot, mobj_t *source, int damage); + +// +// P_SETUP +// +extern byte *rejectmatrix; // for fast sight rejection +extern short *blockmaplump; // offsets in blockmap are from here +extern short *blockmap; +extern int bmapwidth; +extern int bmapheight; // in mapblocks +extern fixed_t bmaporgx; +extern fixed_t bmaporgy; // origin of block map +extern mobj_t **blocklinks; // for thing chains + +// +// P_INTER +// +extern int maxammo[NUMAMMO]; +extern int clipammo[NUMAMMO]; + +void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher); + +void P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, + int damage); + +// +// P_SPEC +// +#include "p_spec.h" + +#endif // __P_LOCAL__ diff --git a/client/src/p_map.c b/client/src/p_map.c new file mode 100644 index 0000000..3e21752 --- /dev/null +++ b/client/src/p_map.c @@ -0,0 +1,1291 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard, 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. +// +// DESCRIPTION: +// Movement, collision handling. +// Shooting and aiming. +// + +#include +#include + +#include "d_player.h" +#include "doomdata.h" +// State. +#include "doomstat.h" +#include "doomtype.h" +#include "i_system.h" +#include "i_video.h" +#include "info.h" +#include "m_argv.h" +#include "m_bbox.h" +#include "m_fixed.h" +#include "m_misc.h" +#include "m_random.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_spec.h" +#include "r_defs.h" +#include "r_main.h" +#include "r_state.h" +#include "s_sound.h" +// Data. +#include "sounds.h" +#include "tables.h" + +// Spechit overrun magic value. +// +// This is the value used by PrBoom-plus. I think the value below is +// actually better and works with more demos. However, I think +// it's better for the spechits emulation to be compatible with +// PrBoom-plus, at least so that the big spechits emulation list +// on Doomworld can also be used with Chocolate Doom. + +#define DEFAULT_SPECHIT_MAGIC 0x01C09C98 + +// This is from a post by myk on the Doomworld forums, +// outputted from entryway's spechit_magic generator for +// s205n546.lmp. The _exact_ value of this isn't too +// important; as long as it is in the right general +// range, it will usually work. Otherwise, we can use +// the generator (hacked doom2.exe) and provide it +// with -spechit. + +//#define DEFAULT_SPECHIT_MAGIC 0x84f968e8 + +fixed_t tmbbox[4]; +mobj_t *tmthing; +int tmflags; +fixed_t tmx; +fixed_t tmy; + +// If "floatok" true, move would be ok +// if within "tmfloorz - tmceilingz". +boolean floatok; + +fixed_t tmfloorz; +fixed_t tmceilingz; +fixed_t tmdropoffz; + +// keep track of the line that lowers the ceiling, +// so missiles don't explode against sky hack walls +line_t *ceilingline; + +// keep track of special lines as they are hit, +// but don't process them until the move is proven valid + +line_t *spechit[MAXSPECIALCROSS]; +int numspechit; + +// +// TELEPORT MOVE +// + +// +// PIT_StompThing +// +boolean PIT_StompThing(mobj_t *thing) { + fixed_t blockdist; + + if (!(thing->flags & MF_SHOOTABLE)) + return true; + + blockdist = thing->radius + tmthing->radius; + + if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist) { + // didn't hit it + return true; + } + + // don't clip against self + if (thing == tmthing) + return true; + + // monsters don't stomp things except on boss level + if (!tmthing->player && gamemap != 30) + return false; + + P_DamageMobj(thing, tmthing, tmthing, 10000); + + return true; +} + +// +// P_TeleportMove +// +boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y) { + int xl; + int xh; + int yl; + int yh; + int bx; + int by; + + subsector_t *newsubsec; + + // kill anything occupying the position + tmthing = thing; + tmflags = thing->flags; + + tmx = x; + tmy = y; + + tmbbox[BOXTOP] = y + tmthing->radius; + tmbbox[BOXBOTTOM] = y - tmthing->radius; + tmbbox[BOXRIGHT] = x + tmthing->radius; + tmbbox[BOXLEFT] = x - tmthing->radius; + + newsubsec = R_PointInSubsector(x, y); + ceilingline = NULL; + + // The base floor/ceiling is from the subsector + // that contains the point. + // Any contacted lines the step closer together + // will adjust them. + tmfloorz = tmdropoffz = newsubsec->sector->floorheight; + tmceilingz = newsubsec->sector->ceilingheight; + + validcount++; + numspechit = 0; + + // stomp on any things contacted + xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS) >> MAPBLOCKSHIFT; + xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS) >> MAPBLOCKSHIFT; + yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS) >> MAPBLOCKSHIFT; + yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS) >> MAPBLOCKSHIFT; + + for (bx = xl; bx <= xh; bx++) + for (by = yl; by <= yh; by++) + if (!P_BlockThingsIterator(bx, by, PIT_StompThing)) + return false; + + // the move is ok, + // so link the thing into its new position + P_UnsetThingPosition(thing); + + thing->floorz = tmfloorz; + thing->ceilingz = tmceilingz; + thing->x = x; + thing->y = y; + + P_SetThingPosition(thing); + + return true; +} + +// +// MOVEMENT ITERATOR FUNCTIONS +// + +static void SpechitOverrun(line_t *ld); + +// +// PIT_CheckLine +// Adjusts tmfloorz and tmceilingz as lines are contacted +// +boolean PIT_CheckLine(line_t *ld) { + if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT] || + tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT] || + tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM] || + tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP]) + return true; + + if (P_BoxOnLineSide(tmbbox, ld) != -1) + return true; + + // A line has been hit + + // The moving thing's destination position will cross + // the given line. + // If this should not be allowed, return false. + // If the line is special, keep track of it + // to process later if the move is proven ok. + // NOTE: specials are NOT sorted by order, + // so two special lines that are only 8 pixels apart + // could be crossed in either order. + + if (!ld->backsector) + return false; // one sided line + + if (!(tmthing->flags & MF_MISSILE)) { + if (ld->flags & ML_BLOCKING) + return false; // explicitly blocking everything + + if (!tmthing->player && ld->flags & ML_BLOCKMONSTERS) + return false; // block monsters only + } + + // set openrange, opentop, openbottom + P_LineOpening(ld); + + // adjust floor / ceiling heights + if (opentop < tmceilingz) { + tmceilingz = opentop; + ceilingline = ld; + } + + if (openbottom > tmfloorz) + tmfloorz = openbottom; + + if (lowfloor < tmdropoffz) + tmdropoffz = lowfloor; + + // if contacted a special line, add it to the list + if (ld->special) { + spechit[numspechit] = ld; + numspechit++; + + // fraggle: spechits overrun emulation code from prboom-plus + if (numspechit > MAXSPECIALCROSS_ORIGINAL) { + SpechitOverrun(ld); + } + } + + return true; +} + +// +// PIT_CheckThing +// +boolean PIT_CheckThing(mobj_t *thing) { + fixed_t blockdist; + boolean solid; + int damage; + + if (!(thing->flags & (MF_SOLID | MF_SPECIAL | MF_SHOOTABLE))) + return true; + + blockdist = thing->radius + tmthing->radius; + + if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist) { + // didn't hit it + return true; + } + + // don't clip against self + if (thing == tmthing) + return true; + + // check for skulls slamming into things + if (tmthing->flags & MF_SKULLFLY) { + damage = ((P_Random() % 8) + 1) * tmthing->info->damage; + + P_DamageMobj(thing, tmthing, tmthing, damage); + + tmthing->flags &= ~MF_SKULLFLY; + tmthing->momx = tmthing->momy = tmthing->momz = 0; + + P_SetMobjState(tmthing, tmthing->info->spawnstate); + + return false; // stop moving + } + + // missiles can hit other things + if (tmthing->flags & MF_MISSILE) { + // see if it went over / under + if (tmthing->z > thing->z + thing->height) + return true; // overhead + if (tmthing->z + tmthing->height < thing->z) + return true; // underneath + + if (tmthing->target && + (tmthing->target->type == thing->type || + (tmthing->target->type == MT_KNIGHT && thing->type == MT_BRUISER) || + (tmthing->target->type == MT_BRUISER && thing->type == MT_KNIGHT))) { + // Don't hit same species as originator. + if (thing == tmthing->target) + return true; + + if (thing->type != MT_PLAYER) { + // Explode, but do no damage. + // Let players missile other players. + return false; + } + } + + if (!(thing->flags & MF_SHOOTABLE)) { + // didn't do any damage + return !(thing->flags & MF_SOLID); + } + + // damage / explode + damage = ((P_Random() % 8) + 1) * tmthing->info->damage; + P_DamageMobj(thing, tmthing, tmthing->target, damage); + + // don't traverse any more + return false; + } + + // check for special pickup + if (thing->flags & MF_SPECIAL) { + solid = (thing->flags & MF_SOLID) != 0; + if (tmflags & MF_PICKUP) { + // can remove thing + P_TouchSpecialThing(thing, tmthing); + } + return !solid; + } + + return !(thing->flags & MF_SOLID); +} + +// +// MOVEMENT CLIPPING +// + +// +// P_CheckPosition +// This is purely informative, nothing is modified +// (except things picked up). +// +// in: +// a mobj_t (can be valid or invalid) +// a position to be checked +// (doesn't need to be related to the mobj_t->x,y) +// +// during: +// special things are touched if MF_PICKUP +// early out on solid lines? +// +// out: +// newsubsec +// floorz +// ceilingz +// tmdropoffz +// the lowest point contacted +// (monsters won't move to a dropoff) +// speciallines[] +// numspeciallines +// +boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y) { + int xl; + int xh; + int yl; + int yh; + int bx; + int by; + subsector_t *newsubsec; + + tmthing = thing; + tmflags = thing->flags; + + tmx = x; + tmy = y; + + tmbbox[BOXTOP] = y + tmthing->radius; + tmbbox[BOXBOTTOM] = y - tmthing->radius; + tmbbox[BOXRIGHT] = x + tmthing->radius; + tmbbox[BOXLEFT] = x - tmthing->radius; + + newsubsec = R_PointInSubsector(x, y); + ceilingline = NULL; + + // The base floor / ceiling is from the subsector + // that contains the point. + // Any contacted lines the step closer together + // will adjust them. + tmfloorz = tmdropoffz = newsubsec->sector->floorheight; + tmceilingz = newsubsec->sector->ceilingheight; + + validcount++; + numspechit = 0; + + if (tmflags & MF_NOCLIP) + return true; + + // Check things first, possibly picking things up. + // The bounding box is extended by MAXRADIUS + // because mobj_ts are grouped into mapblocks + // based on their origin point, and can overlap + // into adjacent blocks by up to MAXRADIUS units. + xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS) >> MAPBLOCKSHIFT; + xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS) >> MAPBLOCKSHIFT; + yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS) >> MAPBLOCKSHIFT; + yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS) >> MAPBLOCKSHIFT; + + for (bx = xl; bx <= xh; bx++) + for (by = yl; by <= yh; by++) + if (!P_BlockThingsIterator(bx, by, PIT_CheckThing)) + return false; + + // check lines + xl = (tmbbox[BOXLEFT] - bmaporgx) >> MAPBLOCKSHIFT; + xh = (tmbbox[BOXRIGHT] - bmaporgx) >> MAPBLOCKSHIFT; + yl = (tmbbox[BOXBOTTOM] - bmaporgy) >> MAPBLOCKSHIFT; + yh = (tmbbox[BOXTOP] - bmaporgy) >> MAPBLOCKSHIFT; + + for (bx = xl; bx <= xh; bx++) + for (by = yl; by <= yh; by++) + if (!P_BlockLinesIterator(bx, by, PIT_CheckLine)) + return false; + + return true; +} + +// +// P_TryMove +// Attempt to move to a new position, +// crossing special lines unless MF_TELEPORT is set. +// +boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y) { + fixed_t oldx; + fixed_t oldy; + int side; + int oldside; + line_t *ld; + + floatok = false; + if (!P_CheckPosition(thing, x, y)) + return false; // solid wall or thing + + if (!(thing->flags & MF_NOCLIP)) { + if (tmceilingz - tmfloorz < thing->height) + return false; // doesn't fit + + floatok = true; + + if (!(thing->flags & MF_TELEPORT) && tmceilingz - thing->z < thing->height) + return false; // mobj must lower itself to fit + + if (!(thing->flags & MF_TELEPORT) && tmfloorz - thing->z > 24 * FRACUNIT) + return false; // too big a step up + + if (!(thing->flags & (MF_DROPOFF | MF_FLOAT)) && + tmfloorz - tmdropoffz > 24 * FRACUNIT) + return false; // don't stand over a dropoff + } + + // the move is ok, + // so link the thing into its new position + P_UnsetThingPosition(thing); + + oldx = thing->x; + oldy = thing->y; + thing->floorz = tmfloorz; + thing->ceilingz = tmceilingz; + thing->x = x; + thing->y = y; + + P_SetThingPosition(thing); + + // if any special lines were hit, do the effect + if (!(thing->flags & (MF_TELEPORT | MF_NOCLIP))) { + while (numspechit--) { + // see if the line was crossed + ld = spechit[numspechit]; + side = P_PointOnLineSide(thing->x, thing->y, ld); + oldside = P_PointOnLineSide(oldx, oldy, ld); + if (side != oldside) { + if (ld->special) + P_CrossSpecialLine(ld - lines, oldside, thing); + } + } + } + + return true; +} + +// +// P_ThingHeightClip +// Takes a valid thing and adjusts the thing->floorz, +// thing->ceilingz, and possibly thing->z. +// This is called for all nearby monsters +// whenever a sector changes height. +// If the thing doesn't fit, +// the z will be set to the lowest value +// and false will be returned. +// +boolean P_ThingHeightClip(mobj_t *thing) { + boolean onfloor; + + onfloor = (thing->z == thing->floorz); + + P_CheckPosition(thing, thing->x, thing->y); + // what about stranding a monster partially off an edge? + + thing->floorz = tmfloorz; + thing->ceilingz = tmceilingz; + + if (onfloor) { + // walking monsters rise and fall with the floor + thing->z = thing->floorz; + } else { + // don't adjust a floating monster unless forced to + if (thing->z + thing->height > thing->ceilingz) + thing->z = thing->ceilingz - thing->height; + } + + if (thing->ceilingz - thing->floorz < thing->height) + return false; + + return true; +} + +// +// SLIDE MOVE +// Allows the player to slide along any angled walls. +// +fixed_t bestslidefrac; +fixed_t secondslidefrac; + +line_t *bestslideline; +line_t *secondslideline; + +mobj_t *slidemo; + +fixed_t tmxmove; +fixed_t tmymove; + +// +// P_HitSlideLine +// Adjusts the xmove / ymove +// so that the next move will slide along the wall. +// +void P_HitSlideLine(line_t *ld) { + int side; + + angle_t lineangle; + angle_t moveangle; + angle_t deltaangle; + + fixed_t movelen; + fixed_t newlen; + + if (ld->slopetype == ST_HORIZONTAL) { + tmymove = 0; + return; + } + + if (ld->slopetype == ST_VERTICAL) { + tmxmove = 0; + return; + } + + side = P_PointOnLineSide(slidemo->x, slidemo->y, ld); + + lineangle = R_PointToAngle2(0, 0, ld->dx, ld->dy); + + if (side == 1) + lineangle += ANG180; + + moveangle = R_PointToAngle2(0, 0, tmxmove, tmymove); + deltaangle = moveangle - lineangle; + + if (deltaangle > ANG180) + deltaangle += ANG180; + // I_Error ("SlideLine: ang>ANG180"); + + lineangle >>= ANGLETOFINESHIFT; + deltaangle >>= ANGLETOFINESHIFT; + + movelen = P_AproxDistance(tmxmove, tmymove); + newlen = FixedMul(movelen, finecosine[deltaangle]); + + tmxmove = FixedMul(newlen, finecosine[lineangle]); + tmymove = FixedMul(newlen, finesine[lineangle]); +} + +// +// PTR_SlideTraverse +// +boolean PTR_SlideTraverse(intercept_t *in) { + line_t *li; + + if (!in->isaline) + I_Error("PTR_SlideTraverse: not a line?"); + + li = in->d.line; + + if (!(li->flags & ML_TWOSIDED)) { + if (P_PointOnLineSide(slidemo->x, slidemo->y, li)) { + // don't hit the back side + return true; + } + goto isblocking; + } + + // set openrange, opentop, openbottom + P_LineOpening(li); + + if (openrange < slidemo->height) + goto isblocking; // doesn't fit + + if (opentop - slidemo->z < slidemo->height) + goto isblocking; // mobj is too high + + if (openbottom - slidemo->z > 24 * FRACUNIT) + goto isblocking; // too big a step up + + // this line doesn't block movement + return true; + +// the line does block movement, +// see if it is closer than best so far +isblocking: + if (in->frac < bestslidefrac) { + secondslidefrac = bestslidefrac; + secondslideline = bestslideline; + bestslidefrac = in->frac; + bestslideline = li; + } + + return false; // stop +} + +// +// P_SlideMove +// The momx / momy move is bad, so try to slide +// along a wall. +// Find the first line hit, move flush to it, +// and slide along it +// +// This is a kludgy mess. +// +void P_SlideMove(mobj_t *mo) { + fixed_t leadx; + fixed_t leady; + fixed_t trailx; + fixed_t traily; + fixed_t newx; + fixed_t newy; + int hitcount; + + slidemo = mo; + hitcount = 0; + +retry: + if (++hitcount == 3) + goto stairstep; // don't loop forever + + // trace along the three leading corners + if (mo->momx > 0) { + leadx = mo->x + mo->radius; + trailx = mo->x - mo->radius; + } else { + leadx = mo->x - mo->radius; + trailx = mo->x + mo->radius; + } + + if (mo->momy > 0) { + leady = mo->y + mo->radius; + traily = mo->y - mo->radius; + } else { + leady = mo->y - mo->radius; + traily = mo->y + mo->radius; + } + + bestslidefrac = FRACUNIT + 1; + + P_PathTraverse(leadx, leady, leadx + mo->momx, leady + mo->momy, PT_ADDLINES, + PTR_SlideTraverse); + P_PathTraverse(trailx, leady, trailx + mo->momx, leady + mo->momy, + PT_ADDLINES, PTR_SlideTraverse); + P_PathTraverse(leadx, traily, leadx + mo->momx, traily + mo->momy, + PT_ADDLINES, PTR_SlideTraverse); + + // move up to the wall + if (bestslidefrac == FRACUNIT + 1) { + // the move most have hit the middle, so stairstep + stairstep: + if (!P_TryMove(mo, mo->x, mo->y + mo->momy)) + P_TryMove(mo, mo->x + mo->momx, mo->y); + return; + } + + // fudge a bit to make sure it doesn't hit + bestslidefrac -= 0x800; + if (bestslidefrac > 0) { + newx = FixedMul(mo->momx, bestslidefrac); + newy = FixedMul(mo->momy, bestslidefrac); + + if (!P_TryMove(mo, mo->x + newx, mo->y + newy)) + goto stairstep; + } + + // Now continue along the wall. + // First calculate remainder. + bestslidefrac = FRACUNIT - (bestslidefrac + 0x800); + + if (bestslidefrac > FRACUNIT) + bestslidefrac = FRACUNIT; + + if (bestslidefrac <= 0) + return; + + tmxmove = FixedMul(mo->momx, bestslidefrac); + tmymove = FixedMul(mo->momy, bestslidefrac); + + P_HitSlideLine(bestslideline); // clip the moves + + mo->momx = tmxmove; + mo->momy = tmymove; + + if (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove)) { + goto retry; + } +} + +// +// P_LineAttack +// +mobj_t *linetarget; // who got hit (or NULL) +mobj_t *shootthing; + +// Height if not aiming up or down +// ???: use slope for monsters? +fixed_t shootz; + +int la_damage; +fixed_t attackrange; + +fixed_t aimslope; + +// slopes to top and bottom of target +extern fixed_t topslope; +extern fixed_t bottomslope; + +// +// PTR_AimTraverse +// Sets linetaget and aimslope when a target is aimed at. +// +boolean PTR_AimTraverse(intercept_t *in) { + line_t *li; + mobj_t *th; + fixed_t slope; + fixed_t thingtopslope; + fixed_t thingbottomslope; + fixed_t dist; + + if (in->isaline) { + li = in->d.line; + + if (!(li->flags & ML_TWOSIDED)) + return false; // stop + + // Crosses a two sided line. + // A two sided line will restrict + // the possible target ranges. + P_LineOpening(li); + + if (openbottom >= opentop) + return false; // stop + + dist = FixedMul(attackrange, in->frac); + + if (li->backsector == NULL || + li->frontsector->floorheight != li->backsector->floorheight) { + slope = FixedDiv(openbottom - shootz, dist); + if (slope > bottomslope) + bottomslope = slope; + } + + if (li->backsector == NULL || + li->frontsector->ceilingheight != li->backsector->ceilingheight) { + slope = FixedDiv(opentop - shootz, dist); + if (slope < topslope) + topslope = slope; + } + + if (topslope <= bottomslope) + return false; // stop + + return true; // shot continues + } + + // shoot a thing + th = in->d.thing; + if (th == shootthing) + return true; // can't shoot self + + if (!(th->flags & MF_SHOOTABLE)) + return true; // corpse or something + + // check angles to see if the thing can be aimed at + dist = FixedMul(attackrange, in->frac); + thingtopslope = FixedDiv(th->z + th->height - shootz, dist); + + if (thingtopslope < bottomslope) + return true; // shot over the thing + + thingbottomslope = FixedDiv(th->z - shootz, dist); + + if (thingbottomslope > topslope) + return true; // shot under the thing + + // this thing can be hit! + if (thingtopslope > topslope) + thingtopslope = topslope; + + if (thingbottomslope < bottomslope) + thingbottomslope = bottomslope; + + aimslope = (thingtopslope + thingbottomslope) / 2; + linetarget = th; + + return false; // don't go any farther +} + +// +// PTR_ShootTraverse +// +boolean PTR_ShootTraverse(intercept_t *in) { + fixed_t x; + fixed_t y; + fixed_t z; + fixed_t frac; + + line_t *li; + + mobj_t *th; + + fixed_t slope; + fixed_t dist; + fixed_t thingtopslope; + fixed_t thingbottomslope; + + if (in->isaline) { + li = in->d.line; + + if (li->special) + P_ShootSpecialLine(shootthing, li); + + if (!(li->flags & ML_TWOSIDED)) + goto hitline; + + // crosses a two sided line + P_LineOpening(li); + + dist = FixedMul(attackrange, in->frac); + + // e6y: emulation of missed back side on two-sided lines. + // backsector can be NULL when emulating missing back side. + + if (li->backsector == NULL) { + slope = FixedDiv(openbottom - shootz, dist); + if (slope > aimslope) + goto hitline; + + slope = FixedDiv(opentop - shootz, dist); + if (slope < aimslope) + goto hitline; + } else { + if (li->frontsector->floorheight != li->backsector->floorheight) { + slope = FixedDiv(openbottom - shootz, dist); + if (slope > aimslope) + goto hitline; + } + + if (li->frontsector->ceilingheight != li->backsector->ceilingheight) { + slope = FixedDiv(opentop - shootz, dist); + if (slope < aimslope) + goto hitline; + } + } + + // shot continues + return true; + + // hit line + hitline: + // position a bit closer + frac = in->frac - FixedDiv(4 * FRACUNIT, attackrange); + x = trace.x + FixedMul(trace.dx, frac); + y = trace.y + FixedMul(trace.dy, frac); + z = shootz + FixedMul(aimslope, FixedMul(frac, attackrange)); + + if (li->frontsector->ceilingpic == skyflatnum) { + // don't shoot the sky! + if (z > li->frontsector->ceilingheight) + return false; + + // it's a sky hack wall + if (li->backsector && li->backsector->ceilingpic == skyflatnum) + return false; + } + + // Spawn bullet puffs. + P_SpawnPuff(x, y, z); + + // don't go any farther + return false; + } + + // shoot a thing + th = in->d.thing; + if (th == shootthing) + return true; // can't shoot self + + if (!(th->flags & MF_SHOOTABLE)) + return true; // corpse or something + + // check angles to see if the thing can be aimed at + dist = FixedMul(attackrange, in->frac); + thingtopslope = FixedDiv(th->z + th->height - shootz, dist); + + if (thingtopslope < aimslope) + return true; // shot over the thing + + thingbottomslope = FixedDiv(th->z - shootz, dist); + + if (thingbottomslope > aimslope) + return true; // shot under the thing + + // hit thing + // position a bit closer + frac = in->frac - FixedDiv(10 * FRACUNIT, attackrange); + + x = trace.x + FixedMul(trace.dx, frac); + y = trace.y + FixedMul(trace.dy, frac); + z = shootz + FixedMul(aimslope, FixedMul(frac, attackrange)); + + // Spawn bullet puffs or blod spots, + // depending on target type. + if (in->d.thing->flags & MF_NOBLOOD) + P_SpawnPuff(x, y, z); + else + P_SpawnBlood(x, y, z, la_damage); + + if (la_damage) + P_DamageMobj(th, shootthing, shootthing, la_damage); + + // don't go any farther + return false; +} + +// +// P_AimLineAttack +// +fixed_t P_AimLineAttack(mobj_t *t1, angle_t angle, fixed_t distance) { + fixed_t x2; + fixed_t y2; + + t1 = P_SubstNullMobj(t1); + + angle >>= ANGLETOFINESHIFT; + shootthing = t1; + + x2 = t1->x + (distance >> FRACBITS) * finecosine[angle]; + y2 = t1->y + (distance >> FRACBITS) * finesine[angle]; + shootz = t1->z + (t1->height >> 1) + 8 * FRACUNIT; + + // can't shoot outside view angles + topslope = (SCREENHEIGHT / 2) * FRACUNIT / (SCREENWIDTH / 2); + bottomslope = -(SCREENHEIGHT / 2) * FRACUNIT / (SCREENWIDTH / 2); + + attackrange = distance; + linetarget = NULL; + + P_PathTraverse(t1->x, t1->y, x2, y2, PT_ADDLINES | PT_ADDTHINGS, + PTR_AimTraverse); + + if (linetarget) + return aimslope; + + return 0; +} + +// +// P_LineAttack +// If damage == 0, it is just a test trace +// that will leave linetarget set. +// +void P_LineAttack(mobj_t *t1, angle_t angle, fixed_t distance, fixed_t slope, + int damage) { + fixed_t x2; + fixed_t y2; + + angle >>= ANGLETOFINESHIFT; + shootthing = t1; + la_damage = damage; + x2 = t1->x + (distance >> FRACBITS) * finecosine[angle]; + y2 = t1->y + (distance >> FRACBITS) * finesine[angle]; + shootz = t1->z + (t1->height >> 1) + 8 * FRACUNIT; + attackrange = distance; + aimslope = slope; + + P_PathTraverse(t1->x, t1->y, x2, y2, PT_ADDLINES | PT_ADDTHINGS, + PTR_ShootTraverse); +} + +// +// USE LINES +// +mobj_t *usething; + +boolean PTR_UseTraverse(intercept_t *in) { + int side; + + if (!in->d.line->special) { + P_LineOpening(in->d.line); + if (openrange <= 0) { + S_StartSound(usething, sfx_noway); + + // can't use through a wall + return false; + } + // not a special line, but keep checking + return true; + } + + side = 0; + if (P_PointOnLineSide(usething->x, usething->y, in->d.line) == 1) + side = 1; + + // return false; // don't use back side + + P_UseSpecialLine(usething, in->d.line, side); + + // can't use for than one special line in a row + return false; +} + +// +// P_UseLines +// Looks for special lines in front of the player to activate. +// +void P_UseLines(player_t *player) { + int angle; + fixed_t x1; + fixed_t y1; + fixed_t x2; + fixed_t y2; + + usething = player->mo; + + angle = player->mo->angle >> ANGLETOFINESHIFT; + + x1 = player->mo->x; + y1 = player->mo->y; + x2 = x1 + (USERANGE >> FRACBITS) * finecosine[angle]; + y2 = y1 + (USERANGE >> FRACBITS) * finesine[angle]; + + P_PathTraverse(x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse); +} + +// +// RADIUS ATTACK +// +mobj_t *bombsource; +mobj_t *bombspot; +int bombdamage; + +// +// PIT_RadiusAttack +// "bombsource" is the creature +// that caused the explosion at "bombspot". +// +boolean PIT_RadiusAttack(mobj_t *thing) { + fixed_t dx; + fixed_t dy; + fixed_t dist; + + if (!(thing->flags & MF_SHOOTABLE)) + return true; + + // Boss spider and cyborg + // take no damage from concussion. + if (thing->type == MT_CYBORG || thing->type == MT_SPIDER) + return true; + + dx = abs(thing->x - bombspot->x); + dy = abs(thing->y - bombspot->y); + + dist = dx > dy ? dx : dy; + dist = (dist - thing->radius) >> FRACBITS; + + if (dist < 0) + dist = 0; + + if (dist >= bombdamage) + return true; // out of range + + if (P_CheckSight(thing, bombspot)) { + // must be in direct path + P_DamageMobj(thing, bombspot, bombsource, bombdamage - dist); + } + + return true; +} + +// +// P_RadiusAttack +// Source is the creature that caused the explosion at spot. +// +void P_RadiusAttack(mobj_t *spot, mobj_t *source, int damage) { + int x; + int y; + + int xl; + int xh; + int yl; + int yh; + + fixed_t dist; + + dist = (damage + MAXRADIUS) << FRACBITS; + yh = (spot->y + dist - bmaporgy) >> MAPBLOCKSHIFT; + yl = (spot->y - dist - bmaporgy) >> MAPBLOCKSHIFT; + xh = (spot->x + dist - bmaporgx) >> MAPBLOCKSHIFT; + xl = (spot->x - dist - bmaporgx) >> MAPBLOCKSHIFT; + bombspot = spot; + bombsource = source; + bombdamage = damage; + + for (y = yl; y <= yh; y++) + for (x = xl; x <= xh; x++) + P_BlockThingsIterator(x, y, PIT_RadiusAttack); +} + +// +// SECTOR HEIGHT CHANGING +// After modifying a sectors floor or ceiling height, +// call this routine to adjust the positions +// of all things that touch the sector. +// +// If anything doesn't fit anymore, true will be returned. +// If crunch is true, they will take damage +// as they are being crushed. +// If Crunch is false, you should set the sector height back +// the way it was and call P_ChangeSector again +// to undo the changes. +// +boolean crushchange; +boolean nofit; + +// +// PIT_ChangeSector +// +boolean PIT_ChangeSector(mobj_t *thing) { + mobj_t *mo; + + if (P_ThingHeightClip(thing)) { + // keep checking + return true; + } + + // crunch bodies to giblets + if (thing->health <= 0) { + P_SetMobjState(thing, S_GIBS); + + thing->flags &= ~MF_SOLID; + thing->height = 0; + thing->radius = 0; + + // keep checking + return true; + } + + // crunch dropped items + if (thing->flags & MF_DROPPED) { + P_RemoveMobj(thing); + + // keep checking + return true; + } + + if (!(thing->flags & MF_SHOOTABLE)) { + // assume it is bloody gibs or something + return true; + } + + nofit = true; + + if (crushchange && !(leveltime & 3)) { + P_DamageMobj(thing, NULL, NULL, 10); + + // spray blood in a random direction + mo = + P_SpawnMobj(thing->x, thing->y, thing->z + thing->height / 2, MT_BLOOD); + + mo->momx = P_SubRandom() << 12; + mo->momy = P_SubRandom() << 12; + } + + // keep checking (crush other things) + return true; +} + +// +// P_ChangeSector +// +boolean P_ChangeSector(sector_t *sector, boolean crunch) { + int x; + int y; + + nofit = false; + crushchange = crunch; + + // re-check heights for all things near the moving sector + for (x = sector->blockbox[BOXLEFT]; x <= sector->blockbox[BOXRIGHT]; x++) + for (y = sector->blockbox[BOXBOTTOM]; y <= sector->blockbox[BOXTOP]; y++) + P_BlockThingsIterator(x, y, PIT_ChangeSector); + + return nofit; +} + +// Code to emulate the behavior of Vanilla Doom when encountering an overrun +// of the spechit array. This is by Andrey Budko (e6y) and comes from his +// PrBoom plus port. A big thanks to Andrey for this. + +static void SpechitOverrun(line_t *ld) { + static unsigned int baseaddr = 0; + unsigned int addr; + + if (baseaddr == 0) { + int p; + + // This is the first time we have had an overrun. Work out + // what base address we are going to use. + // Allow a spechit value to be specified on the command line. + + //! + // @category compat + // @arg + // + // Use the specified magic value when emulating spechit overruns. + // + + p = M_CheckParmWithArgs("-spechit", 1); + + if (p > 0) { + M_StrToInt(myargv[p + 1], (int *)&baseaddr); + } else { + baseaddr = DEFAULT_SPECHIT_MAGIC; + } + } + + // Calculate address used in doom2.exe + + addr = baseaddr + (ld - lines) * 0x3E; + + switch (numspechit) { + case 9: + case 10: + case 11: + case 12: + tmbbox[numspechit - 9] = addr; + break; + case 13: + crushchange = addr; + break; + case 14: + nofit = addr; + break; + default: + fprintf(stderr, "SpechitOverrun: Warning: unable to emulate" + "an overrun where numspechit=%i\n", + numspechit); + break; + } +} diff --git a/client/src/p_maputl.c b/client/src/p_maputl.c new file mode 100644 index 0000000..c8cb042 --- /dev/null +++ b/client/src/p_maputl.c @@ -0,0 +1,836 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// Copyright(C) 2005, 2006 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. +// +// DESCRIPTION: +// Movement/collision utility functions, +// as used by function in p_map.c. +// BLOCKMAP Iterator functions, +// and some PIT_* functions to use for iteration. +// + +#include +#include +#include + +#include "doomstat.h" +#include "doomtype.h" +#include "m_bbox.h" +#include "m_fixed.h" +#include "p_local.h" +#include "p_mobj.h" +#include "r_defs.h" +#include "r_main.h" +// State. +#include "r_state.h" + +// +// P_AproxDistance +// Gives an estimation of distance (not exact) +// + +fixed_t P_AproxDistance(fixed_t dx, fixed_t dy) { + dx = abs(dx); + dy = abs(dy); + if (dx < dy) + return dx + dy - (dx >> 1); + return dx + dy - (dy >> 1); +} + +// +// P_PointOnLineSide +// Returns 0 or 1 +// +int P_PointOnLineSide(fixed_t x, fixed_t y, line_t *line) { + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!line->dx) { + if (x <= line->v1->x) + return line->dy > 0; + + return line->dy < 0; + } + if (!line->dy) { + if (y <= line->v1->y) + return line->dx < 0; + + return line->dx > 0; + } + + dx = (x - line->v1->x); + dy = (y - line->v1->y); + + left = FixedMul(line->dy >> FRACBITS, dx); + right = FixedMul(dy, line->dx >> FRACBITS); + + if (right < left) + return 0; // front side + return 1; // back side +} + +// +// P_BoxOnLineSide +// Considers the line to be infinite +// Returns side 0 or 1, -1 if box crosses the line. +// +int P_BoxOnLineSide(fixed_t *tmbox, line_t *ld) { + int p1 = 0; + int p2 = 0; + + switch (ld->slopetype) { + case ST_HORIZONTAL: + p1 = tmbox[BOXTOP] > ld->v1->y; + p2 = tmbox[BOXBOTTOM] > ld->v1->y; + if (ld->dx < 0) { + p1 ^= 1; + p2 ^= 1; + } + break; + + case ST_VERTICAL: + p1 = tmbox[BOXRIGHT] < ld->v1->x; + p2 = tmbox[BOXLEFT] < ld->v1->x; + if (ld->dy < 0) { + p1 ^= 1; + p2 ^= 1; + } + break; + + case ST_POSITIVE: + p1 = P_PointOnLineSide(tmbox[BOXLEFT], tmbox[BOXTOP], ld); + p2 = P_PointOnLineSide(tmbox[BOXRIGHT], tmbox[BOXBOTTOM], ld); + break; + + case ST_NEGATIVE: + p1 = P_PointOnLineSide(tmbox[BOXRIGHT], tmbox[BOXTOP], ld); + p2 = P_PointOnLineSide(tmbox[BOXLEFT], tmbox[BOXBOTTOM], ld); + break; + } + + if (p1 == p2) + return p1; + return -1; +} + +// +// P_PointOnDivlineSide +// Returns 0 or 1. +// +int P_PointOnDivlineSide(fixed_t x, fixed_t y, divline_t *line) { + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!line->dx) { + if (x <= line->x) + return line->dy > 0; + + return line->dy < 0; + } + if (!line->dy) { + if (y <= line->y) + return line->dx < 0; + + return line->dx > 0; + } + + dx = (x - line->x); + dy = (y - line->y); + + // try to quickly decide by looking at sign bits + if ((line->dy ^ line->dx ^ dx ^ dy) & 0x80000000) { + if ((line->dy ^ dx) & 0x80000000) + return 1; // (left is negative) + return 0; + } + + left = FixedMul(line->dy >> 8, dx >> 8); + right = FixedMul(dy >> 8, line->dx >> 8); + + if (right < left) + return 0; // front side + return 1; // back side +} + +// +// P_MakeDivline +// +void P_MakeDivline(line_t *li, divline_t *dl) { + dl->x = li->v1->x; + dl->y = li->v1->y; + dl->dx = li->dx; + dl->dy = li->dy; +} + +// +// P_InterceptVector +// Returns the fractional intercept point +// along the first divline. +// This is only called by the addthings +// and addlines traversers. +// +fixed_t P_InterceptVector(divline_t *v2, divline_t *v1) { +#if 1 + fixed_t frac; + fixed_t num; + fixed_t den; + + den = FixedMul(v1->dy >> 8, v2->dx) - FixedMul(v1->dx >> 8, v2->dy); + + if (den == 0) + return 0; + // I_Error ("P_InterceptVector: parallel"); + + num = FixedMul((v1->x - v2->x) >> 8, v1->dy) + + FixedMul((v2->y - v1->y) >> 8, v1->dx); + + frac = FixedDiv(num, den); + + return frac; +#else // UNUSED, float debug. + float frac; + float num; + float den; + float v1x; + float v1y; + float v1dx; + float v1dy; + float v2x; + float v2y; + float v2dx; + float v2dy; + + v1x = (float)v1->x / FRACUNIT; + v1y = (float)v1->y / FRACUNIT; + v1dx = (float)v1->dx / FRACUNIT; + v1dy = (float)v1->dy / FRACUNIT; + v2x = (float)v2->x / FRACUNIT; + v2y = (float)v2->y / FRACUNIT; + v2dx = (float)v2->dx / FRACUNIT; + v2dy = (float)v2->dy / FRACUNIT; + + den = v1dy * v2dx - v1dx * v2dy; + + if (den == 0) + return 0; // parallel + + num = (v1x - v2x) * v1dy + (v2y - v1y) * v1dx; + frac = num / den; + + return frac * FRACUNIT; +#endif +} + +// +// P_LineOpening +// Sets opentop and openbottom to the window +// through a two sided line. +// OPTIMIZE: keep this precalculated +// +fixed_t opentop; +fixed_t openbottom; +fixed_t openrange; +fixed_t lowfloor; + +void P_LineOpening(line_t *linedef) { + sector_t *front; + sector_t *back; + + if (linedef->sidenum[1] == -1) { + // single sided line + openrange = 0; + return; + } + + front = linedef->frontsector; + back = linedef->backsector; + + if (front->ceilingheight < back->ceilingheight) + opentop = front->ceilingheight; + else + opentop = back->ceilingheight; + + if (front->floorheight > back->floorheight) { + openbottom = front->floorheight; + lowfloor = back->floorheight; + } else { + openbottom = back->floorheight; + lowfloor = front->floorheight; + } + + openrange = opentop - openbottom; +} + +// +// THING POSITION SETTING +// + +// +// P_UnsetThingPosition +// Unlinks a thing from block map and sectors. +// On each position change, BLOCKMAP and other +// lookups maintaining lists ot things inside +// these structures need to be updated. +// +void P_UnsetThingPosition(mobj_t *thing) { + int blockx; + int blocky; + + if (!(thing->flags & MF_NOSECTOR)) { + // inert things don't need to be in blockmap? + // unlink from subsector + if (thing->snext) + thing->snext->sprev = thing->sprev; + + if (thing->sprev) + thing->sprev->snext = thing->snext; + else + thing->subsector->sector->thinglist = thing->snext; + } + + if (!(thing->flags & MF_NOBLOCKMAP)) { + // inert things don't need to be in blockmap + // unlink from block map + if (thing->bnext) + thing->bnext->bprev = thing->bprev; + + if (thing->bprev) + thing->bprev->bnext = thing->bnext; + else { + blockx = (thing->x - bmaporgx) >> MAPBLOCKSHIFT; + blocky = (thing->y - bmaporgy) >> MAPBLOCKSHIFT; + + if (blockx >= 0 && blockx < bmapwidth && blocky >= 0 && + blocky < bmapheight) { + blocklinks[blocky * bmapwidth + blockx] = thing->bnext; + } + } + } +} + +// +// P_SetThingPosition +// Links a thing into both a block and a subsector +// based on it's x y. +// Sets thing->subsector properly +// +void P_SetThingPosition(mobj_t *thing) { + subsector_t *ss; + sector_t *sec; + int blockx; + int blocky; + mobj_t **link; + + // link into subsector + ss = R_PointInSubsector(thing->x, thing->y); + thing->subsector = ss; + + if (!(thing->flags & MF_NOSECTOR)) { + // invisible things don't go into the sector links + sec = ss->sector; + + thing->sprev = NULL; + thing->snext = sec->thinglist; + + if (sec->thinglist) + sec->thinglist->sprev = thing; + + sec->thinglist = thing; + } + + // link into blockmap + if (!(thing->flags & MF_NOBLOCKMAP)) { + // inert things don't need to be in blockmap + blockx = (thing->x - bmaporgx) >> MAPBLOCKSHIFT; + blocky = (thing->y - bmaporgy) >> MAPBLOCKSHIFT; + + if (blockx >= 0 && blockx < bmapwidth && blocky >= 0 && + blocky < bmapheight) { + link = &blocklinks[blocky * bmapwidth + blockx]; + thing->bprev = NULL; + thing->bnext = *link; + if (*link) + (*link)->bprev = thing; + + *link = thing; + } else { + // thing is off the map + thing->bnext = thing->bprev = NULL; + } + } +} + +// +// BLOCK MAP ITERATORS +// For each line/thing in the given mapblock, +// call the passed PIT_* function. +// If the function returns false, +// exit with false without checking anything else. +// + +// +// P_BlockLinesIterator +// The validcount flags are used to avoid checking lines +// that are marked in multiple mapblocks, +// so increment validcount before the first call +// to P_BlockLinesIterator, then make one or more calls +// to it. +// +boolean P_BlockLinesIterator(int x, int y, boolean (*func)(line_t *)) { + int offset; + short *list; + line_t *ld; + + if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight) { + return true; + } + + offset = y * bmapwidth + x; + + offset = *(blockmap + offset); + + for (list = blockmaplump + offset; *list != -1; list++) { + ld = &lines[*list]; + + if (ld->validcount == validcount) + continue; // line has already been checked + + ld->validcount = validcount; + + if (!func(ld)) + return false; + } + return true; // everything was checked +} + +// +// P_BlockThingsIterator +// +boolean P_BlockThingsIterator(int x, int y, boolean (*func)(mobj_t *)) { + mobj_t *mobj; + + if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight) { + return true; + } + + for (mobj = blocklinks[y * bmapwidth + x]; mobj; mobj = mobj->bnext) { + if (!func(mobj)) + return false; + } + return true; +} + +// +// INTERCEPT ROUTINES +// +intercept_t intercepts[MAXINTERCEPTS]; +intercept_t *intercept_p; + +divline_t trace; +boolean earlyout; +int ptflags; + +static void InterceptsOverrun(int num_intercepts, intercept_t *intercept); + +// +// PIT_AddLineIntercepts. +// Looks for lines in the given block +// that intercept the given trace +// to add to the intercepts list. +// +// A line is crossed if its endpoints +// are on opposite sides of the trace. +// Returns true if earlyout and a solid line hit. +// +boolean PIT_AddLineIntercepts(line_t *ld) { + int s1; + int s2; + fixed_t frac; + divline_t dl; + + // avoid precision problems with two routines + if (trace.dx > FRACUNIT * 16 || trace.dy > FRACUNIT * 16 || + trace.dx < -FRACUNIT * 16 || trace.dy < -FRACUNIT * 16) { + s1 = P_PointOnDivlineSide(ld->v1->x, ld->v1->y, &trace); + s2 = P_PointOnDivlineSide(ld->v2->x, ld->v2->y, &trace); + } else { + s1 = P_PointOnLineSide(trace.x, trace.y, ld); + s2 = P_PointOnLineSide(trace.x + trace.dx, trace.y + trace.dy, ld); + } + + if (s1 == s2) + return true; // line isn't crossed + + // hit the line + P_MakeDivline(ld, &dl); + frac = P_InterceptVector(&trace, &dl); + + if (frac < 0) + return true; // behind source + + // try to early out the check + if (earlyout && frac < FRACUNIT && !ld->backsector) { + return false; // stop checking + } + + intercept_p->frac = frac; + intercept_p->isaline = true; + intercept_p->d.line = ld; + InterceptsOverrun(intercept_p - intercepts, intercept_p); + intercept_p++; + + return true; // continue +} + +// +// PIT_AddThingIntercepts +// +boolean PIT_AddThingIntercepts(mobj_t *thing) { + fixed_t x1; + fixed_t y1; + fixed_t x2; + fixed_t y2; + + int s1; + int s2; + + boolean tracepositive; + + divline_t dl; + + fixed_t frac; + + tracepositive = (trace.dx ^ trace.dy) > 0; + + // check a corner to corner crossection for hit + if (tracepositive) { + x1 = thing->x - thing->radius; + y1 = thing->y + thing->radius; + + x2 = thing->x + thing->radius; + y2 = thing->y - thing->radius; + } else { + x1 = thing->x - thing->radius; + y1 = thing->y - thing->radius; + + x2 = thing->x + thing->radius; + y2 = thing->y + thing->radius; + } + + s1 = P_PointOnDivlineSide(x1, y1, &trace); + s2 = P_PointOnDivlineSide(x2, y2, &trace); + + if (s1 == s2) + return true; // line isn't crossed + + dl.x = x1; + dl.y = y1; + dl.dx = x2 - x1; + dl.dy = y2 - y1; + + frac = P_InterceptVector(&trace, &dl); + + if (frac < 0) + return true; // behind source + + intercept_p->frac = frac; + intercept_p->isaline = false; + intercept_p->d.thing = thing; + InterceptsOverrun(intercept_p - intercepts, intercept_p); + intercept_p++; + + return true; // keep going +} + +// +// P_TraverseIntercepts +// Returns true if the traverser function returns true +// for all lines. +// +boolean P_TraverseIntercepts(traverser_t func, fixed_t maxfrac) { + int count; + fixed_t dist; + intercept_t *scan; + intercept_t *in; + + count = intercept_p - intercepts; + + in = 0; // shut up compiler warning + + while (count--) { + dist = INT_MAX; + for (scan = intercepts; scan < intercept_p; scan++) { + if (scan->frac < dist) { + dist = scan->frac; + in = scan; + } + } + + if (dist > maxfrac) + return true; // checked everything in range + +#if 0 // UNUSED + { + // don't check these yet, there may be others inserted + in = scan = intercepts; + for ( scan = intercepts ; scanfrac > maxfrac) + *in++ = *scan; + intercept_p = in; + return false; + } +#endif + + if (!func(in)) + return false; // don't bother going farther + + in->frac = INT_MAX; + } + + return true; // everything was traversed +} + +extern fixed_t bulletslope; + +// Intercepts Overrun emulation, from PrBoom-plus. +// Thanks to Andrey Budko (entryway) for researching this and his +// implementation of Intercepts Overrun emulation in PrBoom-plus +// which this is based on. + +typedef struct { + int len; + void *addr; + boolean int16_array; +} intercepts_overrun_t; + +// Intercepts memory table. This is where various variables are located +// in memory in Vanilla Doom. When the intercepts table overflows, we +// need to write to them. +// +// Almost all of the values to overwrite are 32-bit integers, except for +// playerstarts, which is effectively an array of 16-bit integers and +// must be treated differently. + +static intercepts_overrun_t intercepts_overrun[] = { + {4, NULL, false}, + {4, NULL, /* &earlyout, */ false}, + {4, NULL, /* &intercept_p, */ false}, + {4, &lowfloor, false}, + {4, &openbottom, false}, + {4, &opentop, false}, + {4, &openrange, false}, + {4, NULL, false}, + {120, NULL, /* &activeplats, */ false}, + {8, NULL, false}, + {4, &bulletslope, false}, + {4, NULL, /* &swingx, */ false}, + {4, NULL, /* &swingy, */ false}, + {4, NULL, false}, + {40, &playerstarts, true}, + {4, NULL, /* &blocklinks, */ false}, + {4, &bmapwidth, false}, + {4, NULL, /* &blockmap, */ false}, + {4, &bmaporgx, false}, + {4, &bmaporgy, false}, + {4, NULL, /* &blockmaplump, */ false}, + {4, &bmapheight, false}, + {0, NULL, false}, +}; + +// Overwrite a specific memory location with a value. + +static void InterceptsMemoryOverrun(int location, int value) { + int i, offset; + int index; + void *addr; + + i = 0; + offset = 0; + + // Search down the array until we find the right entry + + while (intercepts_overrun[i].len != 0) { + if (offset + intercepts_overrun[i].len > location) { + addr = intercepts_overrun[i].addr; + + // Write the value to the memory location. + // 16-bit and 32-bit values are written differently. + + if (addr != NULL) { + if (intercepts_overrun[i].int16_array) { + index = (location - offset) / 2; + ((short *)addr)[index] = value & 0xffff; + ((short *)addr)[index + 1] = (value >> 16) & 0xffff; + } else { + index = (location - offset) / 4; + ((int *)addr)[index] = value; + } + } + + break; + } + + offset += intercepts_overrun[i].len; + ++i; + } +} + +// Emulate overruns of the intercepts[] array. + +static void InterceptsOverrun(int num_intercepts, intercept_t *intercept) { + int location; + + if (num_intercepts <= MAXINTERCEPTS_ORIGINAL) { + // No overrun + + return; + } + + location = (num_intercepts - MAXINTERCEPTS_ORIGINAL - 1) * 12; + + // Overwrite memory that is overwritten in Vanilla Doom, using + // the values from the intercept structure. + // + // Note: the ->d.{thing,line} member should really have its + // address translated into the correct address value for + // Vanilla Doom. + + InterceptsMemoryOverrun(location, intercept->frac); + InterceptsMemoryOverrun(location + 4, intercept->isaline); + InterceptsMemoryOverrun(location + 8, (intptr_t)intercept->d.thing); +} + +// +// P_PathTraverse +// Traces a line from x1,y1 to x2,y2, +// calling the traverser function for each. +// Returns true if the traverser function returns true +// for all lines. +// +boolean P_PathTraverse(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, + int flags, boolean (*trav)(intercept_t *)) { + fixed_t xt1; + fixed_t yt1; + fixed_t xt2; + fixed_t yt2; + + fixed_t xstep; + fixed_t ystep; + + fixed_t partial; + + fixed_t xintercept; + fixed_t yintercept; + + int mapx; + int mapy; + + int mapxstep; + int mapystep; + + int count; + + earlyout = (flags & PT_EARLYOUT) != 0; + + validcount++; + intercept_p = intercepts; + + if (((x1 - bmaporgx) & (MAPBLOCKSIZE - 1)) == 0) + x1 += FRACUNIT; // don't side exactly on a line + + if (((y1 - bmaporgy) & (MAPBLOCKSIZE - 1)) == 0) + y1 += FRACUNIT; // don't side exactly on a line + + trace.x = x1; + trace.y = y1; + trace.dx = x2 - x1; + trace.dy = y2 - y1; + + x1 -= bmaporgx; + y1 -= bmaporgy; + xt1 = x1 >> MAPBLOCKSHIFT; + yt1 = y1 >> MAPBLOCKSHIFT; + + x2 -= bmaporgx; + y2 -= bmaporgy; + xt2 = x2 >> MAPBLOCKSHIFT; + yt2 = y2 >> MAPBLOCKSHIFT; + + if (xt2 > xt1) { + mapxstep = 1; + partial = FRACUNIT - ((x1 >> MAPBTOFRAC) & (FRACUNIT - 1)); + ystep = FixedDiv(y2 - y1, abs(x2 - x1)); + } else if (xt2 < xt1) { + mapxstep = -1; + partial = (x1 >> MAPBTOFRAC) & (FRACUNIT - 1); + ystep = FixedDiv(y2 - y1, abs(x2 - x1)); + } else { + mapxstep = 0; + partial = FRACUNIT; + ystep = 256 * FRACUNIT; + } + + yintercept = (y1 >> MAPBTOFRAC) + FixedMul(partial, ystep); + + if (yt2 > yt1) { + mapystep = 1; + partial = FRACUNIT - ((y1 >> MAPBTOFRAC) & (FRACUNIT - 1)); + xstep = FixedDiv(x2 - x1, abs(y2 - y1)); + } else if (yt2 < yt1) { + mapystep = -1; + partial = (y1 >> MAPBTOFRAC) & (FRACUNIT - 1); + xstep = FixedDiv(x2 - x1, abs(y2 - y1)); + } else { + mapystep = 0; + partial = FRACUNIT; + xstep = 256 * FRACUNIT; + } + xintercept = (x1 >> MAPBTOFRAC) + FixedMul(partial, xstep); + + // Step through map blocks. + // Count is present to prevent a round off error + // from skipping the break. + mapx = xt1; + mapy = yt1; + + for (count = 0; count < 64; count++) { + if (flags & PT_ADDLINES) { + if (!P_BlockLinesIterator(mapx, mapy, PIT_AddLineIntercepts)) + return false; // early out + } + + if (flags & PT_ADDTHINGS) { + if (!P_BlockThingsIterator(mapx, mapy, PIT_AddThingIntercepts)) + return false; // early out + } + + if (mapx == xt2 && mapy == yt2) { + break; + } + + if ((yintercept >> FRACBITS) == mapy) { + yintercept += ystep; + mapx += mapxstep; + } else if ((xintercept >> FRACBITS) == mapx) { + xintercept += xstep; + mapy += mapystep; + } + } + // go through the sorted list + return P_TraverseIntercepts(trav, FRACUNIT); +} diff --git a/client/src/p_mobj.c b/client/src/p_mobj.c new file mode 100644 index 0000000..1e36f6e --- /dev/null +++ b/client/src/p_mobj.c @@ -0,0 +1,918 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Moving object handling. Spawn functions. +// + +#include +#include + +#include "d_mode.h" +#include "d_player.h" +#include "d_think.h" +#include "d_ticcmd.h" +#include "doomdata.h" +#include "doomdef.h" +#include "doomstat.h" +#include "doomtype.h" +#include "hu_stuff.h" +#include "i_system.h" +#include "i_timer.h" +#include "info.h" +#include "m_fixed.h" +#include "m_random.h" +#include "p_local.h" +#include "p_mobj.h" +#include "r_defs.h" +#include "r_main.h" +#include "s_sound.h" +#include "sounds.h" +#include "st_stuff.h" +#include "tables.h" +#include "z_zone.h" + +void G_PlayerReborn(int player); +void P_SpawnMapThing(mapthing_t *mthing); + +// +// P_SetMobjState +// Returns true if the mobj is still present. +// +int test; + +// Use a heuristic approach to detect infinite state cycles: Count the number +// of times the loop in P_SetMobjState() executes and exit with an error once +// an arbitrary very large limit is reached. + +#define MOBJ_CYCLE_LIMIT 1000000 + +boolean P_SetMobjState(mobj_t *mobj, statenum_t state) { + state_t *st; + int cycle_counter = 0; + + do { + if (state == S_NULL) { + mobj->state = (state_t *)S_NULL; + P_RemoveMobj(mobj); + return false; + } + + st = &states[state]; + mobj->state = st; + mobj->tics = st->tics; + mobj->sprite = st->sprite; + mobj->frame = st->frame; + + // Modified handling. + // Call action functions when the state is set + if (st->action.acp1) + st->action.acp1(mobj); + + state = st->nextstate; + + if (cycle_counter++ > MOBJ_CYCLE_LIMIT) { + I_Error("P_SetMobjState: Infinite state cycle detected!"); + } + } while (!mobj->tics); + + return true; +} + +// +// P_ExplodeMissile +// +void P_ExplodeMissile(mobj_t *mo) { + mo->momx = mo->momy = mo->momz = 0; + + P_SetMobjState(mo, mobjinfo[mo->type].deathstate); + + mo->tics -= P_Random() & 3; + + if (mo->tics < 1) + mo->tics = 1; + + mo->flags &= ~MF_MISSILE; + + if (mo->info->deathsound) + S_StartSound(mo, mo->info->deathsound); +} + +// +// P_XYMovement +// +#define STOPSPEED 0x1000 +#define FRICTION 0xe800 + +void P_XYMovement(mobj_t *mo) { + fixed_t ptryx; + fixed_t ptryy; + player_t *player; + fixed_t xmove; + fixed_t ymove; + + if (!mo->momx && !mo->momy) { + if (mo->flags & MF_SKULLFLY) { + // the skull slammed into something + mo->flags &= ~MF_SKULLFLY; + mo->momx = mo->momy = mo->momz = 0; + + P_SetMobjState(mo, mo->info->spawnstate); + } + return; + } + + player = mo->player; + + if (mo->momx > MAXMOVE) + mo->momx = MAXMOVE; + else if (mo->momx < -MAXMOVE) + mo->momx = -MAXMOVE; + + if (mo->momy > MAXMOVE) + mo->momy = MAXMOVE; + else if (mo->momy < -MAXMOVE) + mo->momy = -MAXMOVE; + + xmove = mo->momx; + ymove = mo->momy; + + do { + if (xmove > MAXMOVE / 2 || ymove > MAXMOVE / 2) { + ptryx = mo->x + xmove / 2; + ptryy = mo->y + ymove / 2; + xmove >>= 1; + ymove >>= 1; + } else { + ptryx = mo->x + xmove; + ptryy = mo->y + ymove; + xmove = ymove = 0; + } + + if (!P_TryMove(mo, ptryx, ptryy)) { + // blocked move + if (mo->player) { // try to slide along it + P_SlideMove(mo); + } else if (mo->flags & MF_MISSILE) { + // explode a missile + if (ceilingline && ceilingline->backsector && + ceilingline->backsector->ceilingpic == skyflatnum) { + // Hack to prevent missiles exploding + // against the sky. + // Does not handle sky floors. + P_RemoveMobj(mo); + return; + } + P_ExplodeMissile(mo); + } else + mo->momx = mo->momy = 0; + } + } while (xmove || ymove); + + // slow down + if (player && player->cheats & CF_NOMOMENTUM) { + // debug option for no sliding at all + mo->momx = mo->momy = 0; + return; + } + + if (mo->flags & (MF_MISSILE | MF_SKULLFLY)) + return; // no friction for missiles ever + + if (mo->z > mo->floorz) + return; // no friction when airborne + + if (mo->flags & MF_CORPSE) { + // do not stop sliding + // if halfway off a step with some momentum + if (mo->momx > FRACUNIT / 4 || mo->momx < -FRACUNIT / 4 || + mo->momy > FRACUNIT / 4 || mo->momy < -FRACUNIT / 4) { + if (mo->floorz != mo->subsector->sector->floorheight) + return; + } + } + + if (mo->momx > -STOPSPEED && mo->momx < STOPSPEED && mo->momy > -STOPSPEED && + mo->momy < STOPSPEED && (!player || (player->cmd.forwardmove == 0 && + player->cmd.sidemove == 0))) { + // if in a walking frame, stop moving + if (player && (unsigned)((player->mo->state - states) - S_PLAY_RUN1) < 4) + P_SetMobjState(player->mo, S_PLAY); + + mo->momx = 0; + mo->momy = 0; + } else { + mo->momx = FixedMul(mo->momx, FRICTION); + mo->momy = FixedMul(mo->momy, FRICTION); + } +} + +// +// P_ZMovement +// +void P_ZMovement(mobj_t *mo) { + fixed_t dist; + fixed_t delta; + + // check for smooth step up + if (mo->player && mo->z < mo->floorz) { + mo->player->viewheight -= mo->floorz - mo->z; + + mo->player->deltaviewheight = (VIEWHEIGHT - mo->player->viewheight) >> 3; + } + + // adjust height + mo->z += mo->momz; + + if (mo->flags & MF_FLOAT && mo->target) { + // float down towards target if too close + if (!(mo->flags & MF_SKULLFLY) && !(mo->flags & MF_INFLOAT)) { + dist = P_AproxDistance(mo->x - mo->target->x, mo->y - mo->target->y); + + delta = (mo->target->z + (mo->height >> 1)) - mo->z; + + if (delta < 0 && dist < -(delta * 3)) + mo->z -= FLOATSPEED; + else if (delta > 0 && dist < (delta * 3)) + mo->z += FLOATSPEED; + } + } + + // clip movement + if (mo->z <= mo->floorz) { + // hit the floor + + // Note (id): + // somebody left this after the setting momz to 0, + // kinda useless there. + // + // cph - This was the a bug in the linuxdoom-1.10 source which + // caused it not to sync Doom 2 v1.9 demos. Someone + // added the above comment and moved up the following code. So + // demos would desync in close lost soul fights. + // Note that this only applies to original Doom 1 or Doom2 demos - not + // Final Doom and Ultimate Doom. So we test demo_compatibility *and* + // gamemission. (Note we assume that Doom1 is always Ult Doom, which + // seems to hold for most published demos.) + // + // fraggle - cph got the logic here slightly wrong. There are three + // versions of Doom 1.9: + // + // * The version used in registered doom 1.9 + doom2 - no bounce + // * The version used in ultimate doom - has bounce + // * The version used in final doom - has bounce + // + // So we need to check that this is either retail or commercial + // (but not doom2) + + int correct_lost_soul_bounce = gameversion >= exe_ultimate; + + if (correct_lost_soul_bounce && mo->flags & MF_SKULLFLY) { + // the skull slammed into something + mo->momz = -mo->momz; + } + + if (mo->momz < 0) { + if (mo->player && mo->momz < -GRAVITY * 8) { + // Squat down. + // Decrease viewheight for a moment + // after hitting the ground (hard), + // and utter appropriate sound. + mo->player->deltaviewheight = mo->momz >> 3; + S_StartSound(mo, sfx_oof); + } + mo->momz = 0; + } + mo->z = mo->floorz; + + // cph 2001/05/26 - + // See lost soul bouncing comment above. We need this here for bug + // compatibility with original Doom2 v1.9 - if a soul is charging and + // hit by a raising floor this incorrectly reverses its Y momentum. + // + + if (!correct_lost_soul_bounce && mo->flags & MF_SKULLFLY) + mo->momz = -mo->momz; + + if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP)) { + P_ExplodeMissile(mo); + return; + } + } else if (!(mo->flags & MF_NOGRAVITY)) { + if (mo->momz == 0) + mo->momz = -GRAVITY * 2; + else + mo->momz -= GRAVITY; + } + + if (mo->z + mo->height > mo->ceilingz) { + // hit the ceiling + if (mo->momz > 0) + mo->momz = 0; + { mo->z = mo->ceilingz - mo->height; } + + if (mo->flags & MF_SKULLFLY) { // the skull slammed into something + mo->momz = -mo->momz; + } + + if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP)) { + P_ExplodeMissile(mo); + return; + } + } +} + +// +// P_NightmareRespawn +// +void P_NightmareRespawn(mobj_t *mobj) { + fixed_t x; + fixed_t y; + fixed_t z; + subsector_t *ss; + mobj_t *mo; + mapthing_t *mthing; + + x = mobj->spawnpoint.x << FRACBITS; + y = mobj->spawnpoint.y << FRACBITS; + + // somthing is occupying it's position? + if (!P_CheckPosition(mobj, x, y)) + return; // no respwan + + // spawn a teleport fog at old spot + // because of removal of the body? + mo = P_SpawnMobj(mobj->x, mobj->y, mobj->subsector->sector->floorheight, + MT_TFOG); + // initiate teleport sound + S_StartSound(mo, sfx_telept); + + // spawn a teleport fog at the new spot + ss = R_PointInSubsector(x, y); + + mo = P_SpawnMobj(x, y, ss->sector->floorheight, MT_TFOG); + + S_StartSound(mo, sfx_telept); + + // spawn the new monster + mthing = &mobj->spawnpoint; + + // spawn it + if (mobj->info->flags & MF_SPAWNCEILING) + z = ONCEILINGZ; + else + z = ONFLOORZ; + + // inherit attributes from deceased one + mo = P_SpawnMobj(x, y, z, mobj->type); + mo->spawnpoint = mobj->spawnpoint; + mo->angle = ANG45 * (mthing->angle / 45); + + if (mthing->options & MTF_AMBUSH) + mo->flags |= MF_AMBUSH; + + mo->reactiontime = 18; + + // remove the old monster, + P_RemoveMobj(mobj); +} + +// +// P_MobjThinker +// +void P_MobjThinker(mobj_t *mobj) { + // momentum movement + if (mobj->momx || mobj->momy || (mobj->flags & MF_SKULLFLY)) { + P_XYMovement(mobj); + + // FIXME: decent NOP/NULL/Nil function pointer please. + if (mobj->thinker.function.acv == (actionf_v)(-1)) + return; // mobj was removed + } + if ((mobj->z != mobj->floorz) || mobj->momz) { + P_ZMovement(mobj); + + // FIXME: decent NOP/NULL/Nil function pointer please. + if (mobj->thinker.function.acv == (actionf_v)(-1)) + return; // mobj was removed + } + + // cycle through states, + // calling action functions at transitions + if (mobj->tics != -1) { + mobj->tics--; + + // you can cycle through multiple states in a tic + if (!mobj->tics) + if (!P_SetMobjState(mobj, mobj->state->nextstate)) + return; // freed itself + } else { + // check for nightmare respawn + if (!(mobj->flags & MF_COUNTKILL)) + return; + + if (!respawnmonsters) + return; + + mobj->movecount++; + + if (mobj->movecount < 12 * TICRATE) + return; + + if (leveltime & 31) + return; + + if (P_Random() > 4) + return; + + P_NightmareRespawn(mobj); + } +} + +// +// P_SpawnMobj +// +mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) { + mobj_t *mobj; + state_t *st; + mobjinfo_t *info; + + mobj = Z_Malloc(sizeof(*mobj), PU_LEVEL, NULL); + memset(mobj, 0, sizeof(*mobj)); + info = &mobjinfo[type]; + + mobj->type = type; + mobj->info = info; + mobj->x = x; + mobj->y = y; + mobj->radius = info->radius; + mobj->height = info->height; + mobj->flags = info->flags; + mobj->health = info->spawnhealth; + + if (gameskill != sk_nightmare) + mobj->reactiontime = info->reactiontime; + + mobj->lastlook = P_Random() % MAXPLAYERS; + // do not set the state with P_SetMobjState, + // because action routines can not be called yet + st = &states[info->spawnstate]; + + mobj->state = st; + mobj->tics = st->tics; + mobj->sprite = st->sprite; + mobj->frame = st->frame; + + // set subsector and/or block links + P_SetThingPosition(mobj); + + mobj->floorz = mobj->subsector->sector->floorheight; + mobj->ceilingz = mobj->subsector->sector->ceilingheight; + + if (z == ONFLOORZ) + mobj->z = mobj->floorz; + else if (z == ONCEILINGZ) + mobj->z = mobj->ceilingz - mobj->info->height; + else + mobj->z = z; + + mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; + + P_AddThinker(&mobj->thinker); + + return mobj; +} + +// +// P_RemoveMobj +// +mapthing_t itemrespawnque[ITEMQUESIZE]; +int itemrespawntime[ITEMQUESIZE]; +int iquehead; +int iquetail; + +void P_RemoveMobj(mobj_t *mobj) { + if ((mobj->flags & MF_SPECIAL) && !(mobj->flags & MF_DROPPED) && + (mobj->type != MT_INV) && (mobj->type != MT_INS)) { + itemrespawnque[iquehead] = mobj->spawnpoint; + itemrespawntime[iquehead] = leveltime; + iquehead = (iquehead + 1) & (ITEMQUESIZE - 1); + + // lose one off the end? + if (iquehead == iquetail) + iquetail = (iquetail + 1) & (ITEMQUESIZE - 1); + } + + // unlink from sector and block lists + P_UnsetThingPosition(mobj); + + // stop any playing sound + S_StopSound(mobj); + + // free block + P_RemoveThinker((thinker_t *)mobj); +} + +// +// P_RespawnSpecials +// +void P_RespawnSpecials(void) { + fixed_t x; + fixed_t y; + fixed_t z; + + subsector_t *ss; + mobj_t *mo; + mapthing_t *mthing; + + int i; + + // only respawn items in deathmatch + if (deathmatch != 2) + return; // + + // nothing left to respawn? + if (iquehead == iquetail) + return; + + // wait at least 30 seconds + if (leveltime - itemrespawntime[iquetail] < 30 * TICRATE) + return; + + mthing = &itemrespawnque[iquetail]; + + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + + // spawn a teleport fog at the new spot + ss = R_PointInSubsector(x, y); + mo = P_SpawnMobj(x, y, ss->sector->floorheight, MT_IFOG); + S_StartSound(mo, sfx_itmbk); + + // find which type to spawn + for (i = 0; i < NUMMOBJTYPES; i++) { + if (mthing->type == mobjinfo[i].doomednum) + break; + } + + // spawn it + if (mobjinfo[i].flags & MF_SPAWNCEILING) + z = ONCEILINGZ; + else + z = ONFLOORZ; + + mo = P_SpawnMobj(x, y, z, i); + mo->spawnpoint = *mthing; + mo->angle = ANG45 * (mthing->angle / 45); + + // pull it from the que + iquetail = (iquetail + 1) & (ITEMQUESIZE - 1); +} + +// +// P_SpawnPlayer +// Called when a player is spawned on the level. +// Most of the player structure stays unchanged +// between levels. +// +void P_SpawnPlayer(mapthing_t *mthing) { + player_t *p; + fixed_t x; + fixed_t y; + fixed_t z; + + mobj_t *mobj; + + int i; + + if (mthing->type == 0) { + return; + } + + // not playing? + if (!playeringame[mthing->type - 1]) + return; + + p = &players[mthing->type - 1]; + + if (p->playerstate == PST_REBORN) + G_PlayerReborn(mthing->type - 1); + + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + z = ONFLOORZ; + mobj = P_SpawnMobj(x, y, z, MT_PLAYER); + + // set color translations for player sprites + if (mthing->type > 1) + mobj->flags |= (mthing->type - 1) << MF_TRANSSHIFT; + + mobj->angle = ANG45 * (mthing->angle / 45); + mobj->player = p; + mobj->health = p->health; + + p->mo = mobj; + p->playerstate = PST_LIVE; + p->refire = 0; + p->message = NULL; + p->damagecount = 0; + p->bonuscount = 0; + p->extralight = 0; + p->fixedcolormap = 0; + p->viewheight = VIEWHEIGHT; + + // setup gun psprite + P_SetupPsprites(p); + + // give all cards in death match mode + if (deathmatch) + for (i = 0; i < NUMCARDS; i++) + p->cards[i] = true; + + if (mthing->type - 1 == consoleplayer) { + // wake up the status bar + ST_Start(); + // wake up the heads up text + HU_Start(); + } +} + +// +// P_SpawnMapThing +// The fields of the mapthing should +// already be in host byte order. +// +void P_SpawnMapThing(mapthing_t *mthing) { + int i; + int bit; + mobj_t *mobj; + fixed_t x; + fixed_t y; + fixed_t z; + + // count deathmatch start positions + if (mthing->type == 11) { + if (deathmatch_p < &deathmatchstarts[10]) { + memcpy(deathmatch_p, mthing, sizeof(*mthing)); + deathmatch_p++; + } + return; + } + + if (mthing->type <= 0) { + // Thing type 0 is actually "player -1 start". + // For some reason, Vanilla Doom accepts/ignores this. + + return; + } + + // check for players specially + if (mthing->type <= 4) { + // save spots for respawning in network games + playerstarts[mthing->type - 1] = *mthing; + if (!deathmatch) + P_SpawnPlayer(mthing); + + return; + } + + // check for apropriate skill level + if (!netgame && (mthing->options & 16)) + return; + + if (gameskill == sk_baby) + bit = 1; + else if (gameskill == sk_nightmare) + bit = 4; + else + bit = 1 << (gameskill - 1); + + if (!(mthing->options & bit)) + return; + + // find which type to spawn + for (i = 0; i < NUMMOBJTYPES; i++) + if (mthing->type == mobjinfo[i].doomednum) + break; + + if (i == NUMMOBJTYPES) + I_Error("P_SpawnMapThing: Unknown type %i at (%i, %i)", mthing->type, + mthing->x, mthing->y); + + // don't spawn keycards and players in deathmatch + if (deathmatch && mobjinfo[i].flags & MF_NOTDMATCH) + return; + + // don't spawn any monsters if -nomonsters + if (nomonsters && (i == MT_SKULL || (mobjinfo[i].flags & MF_COUNTKILL))) { + return; + } + + // spawn it + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + + if (mobjinfo[i].flags & MF_SPAWNCEILING) + z = ONCEILINGZ; + else + z = ONFLOORZ; + + mobj = P_SpawnMobj(x, y, z, i); + mobj->spawnpoint = *mthing; + + if (mobj->tics > 0) + mobj->tics = 1 + (P_Random() % mobj->tics); + if (mobj->flags & MF_COUNTKILL) + totalkills++; + if (mobj->flags & MF_COUNTITEM) + totalitems++; + + mobj->angle = ANG45 * (mthing->angle / 45); + if (mthing->options & MTF_AMBUSH) + mobj->flags |= MF_AMBUSH; +} + +// +// GAME SPAWN FUNCTIONS +// + +// +// P_SpawnPuff +// +extern fixed_t attackrange; + +void P_SpawnPuff(fixed_t x, fixed_t y, fixed_t z) { + mobj_t *th; + + z += (P_SubRandom() << 10); + + th = P_SpawnMobj(x, y, z, MT_PUFF); + th->momz = FRACUNIT; + th->tics -= P_Random() & 3; + + if (th->tics < 1) + th->tics = 1; + + // don't make punches spark on the wall + if (attackrange == MELEERANGE) + P_SetMobjState(th, S_PUFF3); +} + +// +// P_SpawnBlood +// +void P_SpawnBlood(fixed_t x, fixed_t y, fixed_t z, int damage) { + mobj_t *th; + + z += (P_SubRandom() << 10); + th = P_SpawnMobj(x, y, z, MT_BLOOD); + th->momz = FRACUNIT * 2; + th->tics -= P_Random() & 3; + + if (th->tics < 1) + th->tics = 1; + + if (damage <= 12 && damage >= 9) + P_SetMobjState(th, S_BLOOD2); + else if (damage < 9) + P_SetMobjState(th, S_BLOOD3); +} + +// +// P_CheckMissileSpawn +// Moves the missile forward a bit +// and possibly explodes it right there. +// +void P_CheckMissileSpawn(mobj_t *th) { + th->tics -= P_Random() & 3; + if (th->tics < 1) + th->tics = 1; + + // move a little forward so an angle can + // be computed if it immediately explodes + th->x += (th->momx >> 1); + th->y += (th->momy >> 1); + th->z += (th->momz >> 1); + + if (!P_TryMove(th, th->x, th->y)) + P_ExplodeMissile(th); +} + +// Certain functions assume that a mobj_t pointer is non-NULL, +// causing a crash in some situations where it is NULL. Vanilla +// Doom did not crash because of the lack of proper memory +// protection. This function substitutes NULL pointers for +// pointers to a dummy mobj, to avoid a crash. + +mobj_t *P_SubstNullMobj(mobj_t *mobj) { + if (mobj == NULL) { + static mobj_t dummy_mobj; + + dummy_mobj.x = 0; + dummy_mobj.y = 0; + dummy_mobj.z = 0; + dummy_mobj.flags = 0; + + mobj = &dummy_mobj; + } + + return mobj; +} + +// +// P_SpawnMissile +// +mobj_t *P_SpawnMissile(mobj_t *source, mobj_t *dest, mobjtype_t type) { + mobj_t *th; + angle_t an; + int dist; + + th = P_SpawnMobj(source->x, source->y, source->z + 4 * 8 * FRACUNIT, type); + + if (th->info->seesound) + S_StartSound(th, th->info->seesound); + + th->target = source; // where it came from + an = R_PointToAngle2(source->x, source->y, dest->x, dest->y); + + // fuzzy player + if (dest->flags & MF_SHADOW) + an += P_SubRandom() << 20; + + th->angle = an; + an >>= ANGLETOFINESHIFT; + th->momx = FixedMul(th->info->speed, finecosine[an]); + th->momy = FixedMul(th->info->speed, finesine[an]); + + dist = P_AproxDistance(dest->x - source->x, dest->y - source->y); + dist = dist / th->info->speed; + + if (dist < 1) + dist = 1; + + th->momz = (dest->z - source->z) / dist; + P_CheckMissileSpawn(th); + + return th; +} + +// +// P_SpawnPlayerMissile +// Tries to aim at a nearby monster +// +void P_SpawnPlayerMissile(mobj_t *source, mobjtype_t type) { + mobj_t *th; + angle_t an; + + fixed_t x; + fixed_t y; + fixed_t z; + fixed_t slope; + + // see which target is to be aimed at + an = source->angle; + slope = P_AimLineAttack(source, an, 16 * 64 * FRACUNIT); + + if (!linetarget) { + an += 1 << 26; + slope = P_AimLineAttack(source, an, 16 * 64 * FRACUNIT); + + if (!linetarget) { + an -= 2 << 26; + slope = P_AimLineAttack(source, an, 16 * 64 * FRACUNIT); + } + + if (!linetarget) { + an = source->angle; + slope = 0; + } + } + + x = source->x; + y = source->y; + z = source->z + 4 * 8 * FRACUNIT; + + th = P_SpawnMobj(x, y, z, type); + + if (th->info->seesound) + S_StartSound(th, th->info->seesound); + + th->target = source; + th->angle = an; + th->momx = FixedMul(th->info->speed, finecosine[an >> ANGLETOFINESHIFT]); + th->momy = FixedMul(th->info->speed, finesine[an >> ANGLETOFINESHIFT]); + th->momz = FixedMul(th->info->speed, slope); + + P_CheckMissileSpawn(th); +} diff --git a/client/src/p_mobj.h b/client/src/p_mobj.h new file mode 100644 index 0000000..37c439f --- /dev/null +++ b/client/src/p_mobj.h @@ -0,0 +1,273 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Map Objects, MObj, definition and handling. +// + +#ifndef __P_MOBJ__ +#define __P_MOBJ__ + +// Basics. +#include "m_fixed.h" +#include "tables.h" + +// We need the thinker_t stuff. +#include "d_think.h" + +// We need the WAD data structure for Map things, +// from the THINGS lump. +#include "doomdata.h" + +// States are tied to finite states are +// tied to animation frames. +// Needs precompiled tables/data structures. +#include "info.h" + +// +// NOTES: mobj_t +// +// mobj_ts are used to tell the refresh where to draw an image, +// tell the world simulation when objects are contacted, +// and tell the sound driver how to position a sound. +// +// The refresh uses the next and prev links to follow +// lists of things in sectors as they are being drawn. +// The sprite, frame, and angle elements determine which patch_t +// is used to draw the sprite if it is visible. +// The sprite and frame values are allmost allways set +// from state_t structures. +// The statescr.exe utility generates the states.h and states.c +// files that contain the sprite/frame numbers from the +// statescr.txt source file. +// The xyz origin point represents a point at the bottom middle +// of the sprite (between the feet of a biped). +// This is the default origin position for patch_ts grabbed +// with lumpy.exe. +// A walking creature will have its z equal to the floor +// it is standing on. +// +// The sound code uses the x,y, and subsector fields +// to do stereo positioning of any sound effited by the mobj_t. +// +// The play simulation uses the blocklinks, x,y,z, radius, height +// to determine when mobj_ts are touching each other, +// touching lines in the map, or hit by trace lines (gunshots, +// lines of sight, etc). +// The mobj_t->flags element has various bit flags +// used by the simulation. +// +// Every mobj_t is linked into a single sector +// based on its origin coordinates. +// The subsector_t is found with R_PointInSubsector(x,y), +// and the sector_t can be found with subsector->sector. +// The sector links are only used by the rendering code, +// the play simulation does not care about them at all. +// +// Any mobj_t that needs to be acted upon by something else +// in the play world (block movement, be shot, etc) will also +// need to be linked into the blockmap. +// If the thing has the MF_NOBLOCK flag set, it will not use +// the block links. It can still interact with other things, +// but only as the instigator (missiles will run into other +// things, but nothing can run into a missile). +// Each block in the grid is 128*128 units, and knows about +// every line_t that it contains a piece of, and every +// interactable mobj_t that has its origin contained. +// +// A valid mobj_t is a mobj_t that has the proper subsector_t +// filled in for its xy coordinates and is linked into the +// sector from which the subsector was made, or has the +// MF_NOSECTOR flag set (the subsector_t needs to be valid +// even if MF_NOSECTOR is set), and is linked into a blockmap +// block or has the MF_NOBLOCKMAP flag set. +// Links should only be modified by the P_[Un]SetThingPosition() +// functions. +// Do not change the MF_NO? flags while a thing is valid. +// +// Any questions? +// + +// +// Misc. mobj flags +// +typedef enum { + // Call P_SpecialThing when touched. + MF_SPECIAL = 1, + // Blocks. + MF_SOLID = 2, + // Can be hit. + MF_SHOOTABLE = 4, + // Don't use the sector links (invisible but touchable). + MF_NOSECTOR = 8, + // Don't use the blocklinks (inert but displayable) + MF_NOBLOCKMAP = 16, + + // Not to be activated by sound, deaf monster. + MF_AMBUSH = 32, + // Will try to attack right back. + MF_JUSTHIT = 64, + // Will take at least one step before attacking. + MF_JUSTATTACKED = 128, + // On level spawning (initial position), + // hang from ceiling instead of stand on floor. + MF_SPAWNCEILING = 256, + // Don't apply gravity (every tic), + // that is, object will float, keeping current height + // or changing it actively. + MF_NOGRAVITY = 512, + + // Movement flags. + // This allows jumps from high places. + MF_DROPOFF = 0x400, + // For players, will pick up items. + MF_PICKUP = 0x800, + // Player cheat. ??? + MF_NOCLIP = 0x1000, + // Player: keep info about sliding along walls. + MF_SLIDE = 0x2000, + // Allow moves to any height, no gravity. + // For active floaters, e.g. cacodemons, pain elementals. + MF_FLOAT = 0x4000, + // Don't cross lines + // ??? or look at heights on teleport. + MF_TELEPORT = 0x8000, + // Don't hit same species, explode on block. + // Player missiles as well as fireballs of various kinds. + MF_MISSILE = 0x10000, + // Dropped by a demon, not level spawned. + // E.g. ammo clips dropped by dying former humans. + MF_DROPPED = 0x20000, + // Use fuzzy draw (shadow demons or spectres), + // temporary player invisibility powerup. + MF_SHADOW = 0x40000, + // Flag: don't bleed when shot (use puff), + // barrels and shootable furniture shall not bleed. + MF_NOBLOOD = 0x80000, + // Don't stop moving halfway off a step, + // that is, have dead bodies slide down all the way. + MF_CORPSE = 0x100000, + // Floating to a height for a move, ??? + // don't auto float to target's height. + MF_INFLOAT = 0x200000, + + // On kill, count this enemy object + // towards intermission kill total. + // Happy gathering. + MF_COUNTKILL = 0x400000, + + // On picking up, count this item object + // towards intermission item total. + MF_COUNTITEM = 0x800000, + + // Special handling: skull in flight. + // Neither a cacodemon nor a missile. + MF_SKULLFLY = 0x1000000, + + // Don't spawn this object + // in death match mode (e.g. key cards). + MF_NOTDMATCH = 0x2000000, + + // Player sprites in multiplayer modes are modified + // using an internal color lookup table for re-indexing. + // If 0x4 0x8 or 0xc, + // use a translation table for player colormaps + MF_TRANSLATION = 0xc000000, + // Hmm ???. + MF_TRANSSHIFT = 26 + +} mobjflag_t; + +// Map Object definition. +typedef struct mobj_s { + // List: thinker links. + thinker_t thinker; + + // Info for drawing: position. + fixed_t x; + fixed_t y; + fixed_t z; + + // More list: links in sector (if needed) + struct mobj_s *snext; + struct mobj_s *sprev; + + // More drawing info: to determine current sprite. + angle_t angle; // orientation + spritenum_t sprite; // used to find patch_t and flip value + int frame; // might be ORed with FF_FULLBRIGHT + + // Interaction info, by BLOCKMAP. + // Links in blocks (if needed). + struct mobj_s *bnext; + struct mobj_s *bprev; + + struct subsector_s *subsector; + + // The closest interval over all contacted Sectors. + fixed_t floorz; + fixed_t ceilingz; + + // For movement checking. + fixed_t radius; + fixed_t height; + + // Momentums, used to update position. + fixed_t momx; + fixed_t momy; + fixed_t momz; + + // If == validcount, already checked. + int validcount; + + mobjtype_t type; + mobjinfo_t *info; // &mobjinfo[mobj->type] + + int tics; // state tic counter + state_t *state; + int flags; + int health; + + // Movement direction, movement generation (zig-zagging). + int movedir; // 0-7 + int movecount; // when 0, select a new dir + + // Thing being chased/attacked (or NULL), + // also the originator for missiles. + struct mobj_s *target; + + // Reaction time: if non 0, don't attack yet. + // Used by player to freeze a bit after teleporting. + int reactiontime; + + // If >0, the target will be chased + // no matter what (even if shot) + int threshold; + + // Additional info record for player avatars only. + // Only valid if type == MT_PLAYER + struct player_s *player; + + // Player number last looked for. + int lastlook; + + // For nightmare respawn. + mapthing_t spawnpoint; + + // Thing being chased/attacked for tracers. + struct mobj_s *tracer; + +} mobj_t; + +#endif diff --git a/client/src/p_plats.c b/client/src/p_plats.c new file mode 100644 index 0000000..fb71599 --- /dev/null +++ b/client/src/p_plats.c @@ -0,0 +1,264 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Plats (i.e. elevator platforms) code, raising/lowering. +// + +#include + +#include "d_think.h" +// State. +#include "doomstat.h" +#include "doomtype.h" +#include "i_system.h" +#include "i_timer.h" +#include "m_fixed.h" +#include "m_random.h" +#include "p_local.h" +#include "p_spec.h" +#include "r_defs.h" +#include "r_state.h" +#include "s_sound.h" +// Data. +#include "sounds.h" +#include "z_zone.h" + +plat_t *activeplats[MAXPLATS]; + +// +// Move a plat up and down +// +void T_PlatRaise(plat_t *plat) { + result_e res; + + switch (plat->status) { + case up: + res = T_MovePlane(plat->sector, plat->speed, plat->high, plat->crush, 0, 1); + + if (plat->type == raiseAndChange || plat->type == raiseToNearestAndChange) { + if (!(leveltime & 7)) + S_StartSound(&plat->sector->soundorg, sfx_stnmov); + } + + if (res == crushed && (!plat->crush)) { + plat->count = plat->wait; + plat->status = down; + S_StartSound(&plat->sector->soundorg, sfx_pstart); + } else { + if (res == pastdest) { + plat->count = plat->wait; + plat->status = waiting; + S_StartSound(&plat->sector->soundorg, sfx_pstop); + + switch (plat->type) { + case blazeDWUS: + case downWaitUpStay: + P_RemoveActivePlat(plat); + break; + + case raiseAndChange: + case raiseToNearestAndChange: + P_RemoveActivePlat(plat); + break; + + default: + break; + } + } + } + break; + + case down: + res = T_MovePlane(plat->sector, plat->speed, plat->low, false, 0, -1); + + if (res == pastdest) { + plat->count = plat->wait; + plat->status = waiting; + S_StartSound(&plat->sector->soundorg, sfx_pstop); + } + break; + + case waiting: + if (!--plat->count) { + if (plat->sector->floorheight == plat->low) + plat->status = up; + else + plat->status = down; + S_StartSound(&plat->sector->soundorg, sfx_pstart); + } + case in_stasis: + break; + } +} + +// +// Do Platforms +// "amount" is only used for SOME platforms. +// +int EV_DoPlat(line_t *line, plattype_e type, int amount) { + plat_t *plat; + int secnum; + int rtn; + sector_t *sec; + + secnum = -1; + rtn = 0; + + // Activate all plats that are in_stasis + switch (type) { + case perpetualRaise: + P_ActivateInStasis(line->tag); + break; + + default: + break; + } + + while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0) { + sec = §ors[secnum]; + + if (sec->specialdata) + continue; + + // Find lowest & highest floors around sector + rtn = 1; + plat = Z_Malloc(sizeof(*plat), PU_LEVSPEC, 0); + P_AddThinker(&plat->thinker); + + plat->type = type; + plat->sector = sec; + plat->sector->specialdata = plat; + plat->thinker.function.acp1 = (actionf_p1)T_PlatRaise; + plat->crush = false; + plat->tag = line->tag; + + switch (type) { + case raiseToNearestAndChange: + plat->speed = PLATSPEED / 2; + sec->floorpic = sides[line->sidenum[0]].sector->floorpic; + plat->high = P_FindNextHighestFloor(sec, sec->floorheight); + plat->wait = 0; + plat->status = up; + // NO MORE DAMAGE, IF APPLICABLE + sec->special = 0; + + S_StartSound(&sec->soundorg, sfx_stnmov); + break; + + case raiseAndChange: + plat->speed = PLATSPEED / 2; + sec->floorpic = sides[line->sidenum[0]].sector->floorpic; + plat->high = sec->floorheight + amount * FRACUNIT; + plat->wait = 0; + plat->status = up; + + S_StartSound(&sec->soundorg, sfx_stnmov); + break; + + case downWaitUpStay: + plat->speed = PLATSPEED * 4; + plat->low = P_FindLowestFloorSurrounding(sec); + + if (plat->low > sec->floorheight) + plat->low = sec->floorheight; + + plat->high = sec->floorheight; + plat->wait = TICRATE * PLATWAIT; + plat->status = down; + S_StartSound(&sec->soundorg, sfx_pstart); + break; + + case blazeDWUS: + plat->speed = PLATSPEED * 8; + plat->low = P_FindLowestFloorSurrounding(sec); + + if (plat->low > sec->floorheight) + plat->low = sec->floorheight; + + plat->high = sec->floorheight; + plat->wait = TICRATE * PLATWAIT; + plat->status = down; + S_StartSound(&sec->soundorg, sfx_pstart); + break; + + case perpetualRaise: + plat->speed = PLATSPEED; + plat->low = P_FindLowestFloorSurrounding(sec); + + if (plat->low > sec->floorheight) + plat->low = sec->floorheight; + + plat->high = P_FindHighestFloorSurrounding(sec); + + if (plat->high < sec->floorheight) + plat->high = sec->floorheight; + + plat->wait = TICRATE * PLATWAIT; + plat->status = P_Random() & 1; + + S_StartSound(&sec->soundorg, sfx_pstart); + break; + } + P_AddActivePlat(plat); + } + return rtn; +} + +void P_ActivateInStasis(int tag) { + int i; + + for (i = 0; i < MAXPLATS; i++) + if (activeplats[i] && (activeplats[i])->tag == tag && + (activeplats[i])->status == in_stasis) { + (activeplats[i])->status = (activeplats[i])->oldstatus; + (activeplats[i])->thinker.function.acp1 = (actionf_p1)T_PlatRaise; + } +} + +void EV_StopPlat(line_t *line) { + int j; + + for (j = 0; j < MAXPLATS; j++) + if (activeplats[j] && ((activeplats[j])->status != in_stasis) && + ((activeplats[j])->tag == line->tag)) { + (activeplats[j])->oldstatus = (activeplats[j])->status; + (activeplats[j])->status = in_stasis; + (activeplats[j])->thinker.function.acv = (actionf_v)NULL; + } +} + +void P_AddActivePlat(plat_t *plat) { + int i; + + for (i = 0; i < MAXPLATS; i++) + if (activeplats[i] == NULL) { + activeplats[i] = plat; + return; + } + I_Error("P_AddActivePlat: no more plats!"); +} + +void P_RemoveActivePlat(plat_t *plat) { + int i; + for (i = 0; i < MAXPLATS; i++) + if (plat == activeplats[i]) { + (activeplats[i])->sector->specialdata = NULL; + P_RemoveThinker(&(activeplats[i])->thinker); + activeplats[i] = NULL; + + return; + } + I_Error("P_RemoveActivePlat: can't find plat!"); +} diff --git a/client/src/p_pspr.c b/client/src/p_pspr.c new file mode 100644 index 0000000..736608d --- /dev/null +++ b/client/src/p_pspr.c @@ -0,0 +1,676 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Weapon sprite animation, weapon objects. +// Action functions for weapons. +// + +#include + +#include "d_event.h" +#include "d_items.h" +#include "d_mode.h" +#include "d_player.h" +#include "d_think.h" +#include "d_ticcmd.h" +#include "doomdef.h" +// State. +#include "doomstat.h" +#include "doomtype.h" +#include "m_random.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_pspr.h" +#include "r_main.h" +#include "s_sound.h" +// Data. +#include "sounds.h" +#include "tables.h" + +#define LOWERSPEED FRACUNIT * 6 +#define RAISESPEED FRACUNIT * 6 + +#define WEAPONBOTTOM 128 * FRACUNIT +#define WEAPONTOP 32 * FRACUNIT + +// +// P_SetPsprite +// +void P_SetPsprite(player_t *player, int position, statenum_t stnum) { + pspdef_t *psp; + state_t *state; + + psp = &player->psprites[position]; + + do { + if (!stnum) { + // object removed itself + psp->state = NULL; + break; + } + + state = &states[stnum]; + psp->state = state; + psp->tics = state->tics; // could be 0 + + if (state->misc1) { + // coordinate set + psp->sx = state->misc1 << FRACBITS; + psp->sy = state->misc2 << FRACBITS; + } + + // Call action routine. + // Modified handling. + if (state->action.acp2) { + state->action.acp2(player, psp); + if (!psp->state) + break; + } + + stnum = psp->state->nextstate; + + } while (!psp->tics); + // an initial state of 0 could cycle through +} + +// +// P_CalcSwing +// +fixed_t swingx; +fixed_t swingy; + +void P_CalcSwing(player_t *player) { + fixed_t swing; + int angle; + + // OPTIMIZE: tablify this. + // A LUT would allow for different modes, + // and add flexibility. + + swing = player->bob; + + angle = (FINEANGLES / 70 * leveltime) & FINEMASK; + swingx = FixedMul(swing, finesine[angle]); + + angle = (FINEANGLES / 70 * leveltime + FINEANGLES / 2) & FINEMASK; + swingy = -FixedMul(swingx, finesine[angle]); +} + +// +// P_BringUpWeapon +// Starts bringing the pending weapon up +// from the bottom of the screen. +// Uses player +// +void P_BringUpWeapon(player_t *player) { + statenum_t newstate; + + if (player->pendingweapon == wp_nochange) + player->pendingweapon = player->readyweapon; + + if (player->pendingweapon == wp_chainsaw) + S_StartSound(player->mo, sfx_sawup); + + newstate = weaponinfo[player->pendingweapon].upstate; + + player->pendingweapon = wp_nochange; + player->psprites[ps_weapon].sy = WEAPONBOTTOM; + + P_SetPsprite(player, ps_weapon, newstate); +} + +// +// P_CheckAmmo +// Returns true if there is enough ammo to shoot. +// If not, selects the next weapon to use. +// +boolean P_CheckAmmo(player_t *player) { + ammotype_t ammo; + int count; + + ammo = weaponinfo[player->readyweapon].ammo; + + // Minimal amount for one shot varies. + if (player->readyweapon == wp_bfg) + count = 40; + else if (player->readyweapon == wp_supershotgun) + count = 2; // Double barrel. + else + count = 1; // Regular. + + // Some do not need ammunition anyway. + // Return if current ammunition sufficient. + if (ammo == am_noammo || player->ammo[ammo] >= count) + return true; + + // Out of ammo, pick a weapon to change to. + // Preferences are set here. + do { + if (player->weaponowned[wp_plasma] && player->ammo[am_cell] && + (gamemode != shareware)) { + player->pendingweapon = wp_plasma; + } else if (player->weaponowned[wp_supershotgun] && + player->ammo[am_shell] > 2 && (gamemode == commercial)) { + player->pendingweapon = wp_supershotgun; + } else if (player->weaponowned[wp_chaingun] && player->ammo[am_clip]) { + player->pendingweapon = wp_chaingun; + } else if (player->weaponowned[wp_shotgun] && player->ammo[am_shell]) { + player->pendingweapon = wp_shotgun; + } else if (player->ammo[am_clip]) { + player->pendingweapon = wp_pistol; + } else if (player->weaponowned[wp_chainsaw]) { + player->pendingweapon = wp_chainsaw; + } else if (player->weaponowned[wp_missile] && player->ammo[am_misl]) { + player->pendingweapon = wp_missile; + } else if (player->weaponowned[wp_bfg] && player->ammo[am_cell] > 40 && + (gamemode != shareware)) { + player->pendingweapon = wp_bfg; + } else { + // If everything fails. + player->pendingweapon = wp_fist; + } + + } while (player->pendingweapon == wp_nochange); + + // Now set appropriate weapon overlay. + P_SetPsprite(player, ps_weapon, weaponinfo[player->readyweapon].downstate); + + return false; +} + +// +// P_FireWeapon. +// +void P_FireWeapon(player_t *player) { + statenum_t newstate; + + if (!P_CheckAmmo(player)) + return; + + P_SetMobjState(player->mo, S_PLAY_ATK1); + newstate = weaponinfo[player->readyweapon].atkstate; + P_SetPsprite(player, ps_weapon, newstate); + P_NoiseAlert(player->mo, player->mo); +} + +// +// P_DropWeapon +// Player died, so put the weapon away. +// +void P_DropWeapon(player_t *player) { + P_SetPsprite(player, ps_weapon, weaponinfo[player->readyweapon].downstate); +} + +// +// A_WeaponReady +// The player can fire the weapon +// or change to another weapon at this time. +// Follows after getting weapon up, +// or after previous attack/fire sequence. +// +void A_WeaponReady(player_t *player, pspdef_t *psp) { + statenum_t newstate; + int angle; + + // get out of attack state + if (player->mo->state == &states[S_PLAY_ATK1] || + player->mo->state == &states[S_PLAY_ATK2]) { + P_SetMobjState(player->mo, S_PLAY); + } + + if (player->readyweapon == wp_chainsaw && psp->state == &states[S_SAW]) { + S_StartSound(player->mo, sfx_sawidl); + } + + // check for change + // if player is dead, put the weapon away + if (player->pendingweapon != wp_nochange || !player->health) { + // change weapon + // (pending weapon should allready be validated) + newstate = weaponinfo[player->readyweapon].downstate; + P_SetPsprite(player, ps_weapon, newstate); + return; + } + + // check for fire + // the missile launcher and bfg do not auto fire + if (player->cmd.buttons & BT_ATTACK) { + if (!player->attackdown || + (player->readyweapon != wp_missile && player->readyweapon != wp_bfg)) { + player->attackdown = true; + P_FireWeapon(player); + return; + } + } else + player->attackdown = false; + + // bob the weapon based on movement speed + angle = (128 * leveltime) & FINEMASK; + psp->sx = FRACUNIT + FixedMul(player->bob, finecosine[angle]); + angle &= FINEANGLES / 2 - 1; + psp->sy = WEAPONTOP + FixedMul(player->bob, finesine[angle]); +} + +// +// A_ReFire +// The player can re-fire the weapon +// without lowering it entirely. +// +void A_ReFire(player_t *player, pspdef_t *psp) { + + // check for fire + // (if a weaponchange is pending, let it go through instead) + if ((player->cmd.buttons & BT_ATTACK) && + player->pendingweapon == wp_nochange && player->health) { + player->refire++; + P_FireWeapon(player); + } else { + player->refire = 0; + P_CheckAmmo(player); + } +} + +void A_CheckReload(player_t *player, pspdef_t *psp) { + P_CheckAmmo(player); +#if 0 + if (player->ammo[am_shell]<2) + P_SetPsprite (player, ps_weapon, S_DSNR1); +#endif +} + +// +// A_Lower +// Lowers current weapon, +// and changes weapon at bottom. +// +void A_Lower(player_t *player, pspdef_t *psp) { + psp->sy += LOWERSPEED; + + // Is already down. + if (psp->sy < WEAPONBOTTOM) + return; + + // Player is dead. + if (player->playerstate == PST_DEAD) { + psp->sy = WEAPONBOTTOM; + + // don't bring weapon back up + return; + } + + // The old weapon has been lowered off the screen, + // so change the weapon and start raising it + if (!player->health) { + // Player is dead, so keep the weapon off screen. + P_SetPsprite(player, ps_weapon, S_NULL); + return; + } + + player->readyweapon = player->pendingweapon; + + P_BringUpWeapon(player); +} + +// +// A_Raise +// +void A_Raise(player_t *player, pspdef_t *psp) { + statenum_t newstate; + + psp->sy -= RAISESPEED; + + if (psp->sy > WEAPONTOP) + return; + + psp->sy = WEAPONTOP; + + // The weapon has been raised all the way, + // so change to the ready state. + newstate = weaponinfo[player->readyweapon].readystate; + + P_SetPsprite(player, ps_weapon, newstate); +} + +// +// A_GunFlash +// +void A_GunFlash(player_t *player, pspdef_t *psp) { + P_SetMobjState(player->mo, S_PLAY_ATK2); + P_SetPsprite(player, ps_flash, weaponinfo[player->readyweapon].flashstate); +} + +// +// WEAPON ATTACKS +// + +// +// A_Punch +// +void A_Punch(player_t *player, pspdef_t *psp) { + angle_t angle; + int damage; + int slope; + + damage = (P_Random() % 10 + 1) << 1; + + if (player->powers[pw_strength]) + damage *= 10; + + angle = player->mo->angle; + angle += P_SubRandom() << 18; + slope = P_AimLineAttack(player->mo, angle, MELEERANGE); + P_LineAttack(player->mo, angle, MELEERANGE, slope, damage); + + // turn to face target + if (linetarget) { + S_StartSound(player->mo, sfx_punch); + player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, + linetarget->x, linetarget->y); + } +} + +// +// A_Saw +// +void A_Saw(player_t *player, pspdef_t *psp) { + angle_t angle; + int damage; + int slope; + + damage = 2 * (P_Random() % 10 + 1); + angle = player->mo->angle; + angle += P_SubRandom() << 18; + + // use meleerange + 1 se the puff doesn't skip the flash + slope = P_AimLineAttack(player->mo, angle, MELEERANGE + 1); + P_LineAttack(player->mo, angle, MELEERANGE + 1, slope, damage); + + if (!linetarget) { + S_StartSound(player->mo, sfx_sawful); + return; + } + S_StartSound(player->mo, sfx_sawhit); + + // turn to face target + angle = R_PointToAngle2(player->mo->x, player->mo->y, linetarget->x, + linetarget->y); + if (angle - player->mo->angle > ANG180) { + if ((signed int)(angle - player->mo->angle) < -ANG90 / 20) + player->mo->angle = angle + ANG90 / 21; + else + player->mo->angle -= ANG90 / 20; + } else { + if (angle - player->mo->angle > ANG90 / 20) + player->mo->angle = angle - ANG90 / 21; + else + player->mo->angle += ANG90 / 20; + } + player->mo->flags |= MF_JUSTATTACKED; +} + +// Doom does not check the bounds of the ammo array. As a result, +// it is possible to use an ammo type > 4 that overflows into the +// maxammo array and affects that instead. Through dehacked, for +// example, it is possible to make a weapon that decreases the max +// number of ammo for another weapon. Emulate this. + +static void DecreaseAmmo(player_t *player, int ammonum, int amount) { + if (ammonum < NUMAMMO) { + player->ammo[ammonum] -= amount; + } else { + player->maxammo[ammonum - NUMAMMO] -= amount; + } +} + +// +// A_FireMissile +// +void A_FireMissile(player_t *player, pspdef_t *psp) { + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); + P_SpawnPlayerMissile(player->mo, MT_ROCKET); +} + +// +// A_FireBFG +// +void A_FireBFG(player_t *player, pspdef_t *psp) { + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 40); + P_SpawnPlayerMissile(player->mo, MT_BFG); +} + +// +// A_FirePlasma +// +void A_FirePlasma(player_t *player, pspdef_t *psp) { + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); + + P_SetPsprite(player, ps_flash, + weaponinfo[player->readyweapon].flashstate + (P_Random() & 1)); + + P_SpawnPlayerMissile(player->mo, MT_PLASMA); +} + +// +// P_BulletSlope +// Sets a slope so a near miss is at aproximately +// the height of the intended target +// +fixed_t bulletslope; + +void P_BulletSlope(mobj_t *mo) { + angle_t an; + + // see which target is to be aimed at + an = mo->angle; + bulletslope = P_AimLineAttack(mo, an, 16 * 64 * FRACUNIT); + + if (!linetarget) { + an += 1 << 26; + bulletslope = P_AimLineAttack(mo, an, 16 * 64 * FRACUNIT); + if (!linetarget) { + an -= 2 << 26; + bulletslope = P_AimLineAttack(mo, an, 16 * 64 * FRACUNIT); + } + } +} + +// +// P_GunShot +// +void P_GunShot(mobj_t *mo, boolean accurate) { + angle_t angle; + int damage; + + damage = 5 * (P_Random() % 3 + 1); + angle = mo->angle; + + if (!accurate) + angle += P_SubRandom() << 18; + + P_LineAttack(mo, angle, MISSILERANGE, bulletslope, damage); +} + +// +// A_FirePistol +// +void A_FirePistol(player_t *player, pspdef_t *psp) { + S_StartSound(player->mo, sfx_pistol); + + P_SetMobjState(player->mo, S_PLAY_ATK2); + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); + + P_SetPsprite(player, ps_flash, weaponinfo[player->readyweapon].flashstate); + + P_BulletSlope(player->mo); + P_GunShot(player->mo, !player->refire); +} + +// +// A_FireShotgun +// +void A_FireShotgun(player_t *player, pspdef_t *psp) { + int i; + + S_StartSound(player->mo, sfx_shotgn); + P_SetMobjState(player->mo, S_PLAY_ATK2); + + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); + + P_SetPsprite(player, ps_flash, weaponinfo[player->readyweapon].flashstate); + + P_BulletSlope(player->mo); + + for (i = 0; i < 7; i++) + P_GunShot(player->mo, false); +} + +// +// A_FireShotgun2 +// +void A_FireShotgun2(player_t *player, pspdef_t *psp) { + int i; + angle_t angle; + int damage; + + S_StartSound(player->mo, sfx_dshtgn); + P_SetMobjState(player->mo, S_PLAY_ATK2); + + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 2); + + P_SetPsprite(player, ps_flash, weaponinfo[player->readyweapon].flashstate); + + P_BulletSlope(player->mo); + + for (i = 0; i < 20; i++) { + damage = 5 * (P_Random() % 3 + 1); + angle = player->mo->angle; + angle += P_SubRandom() << ANGLETOFINESHIFT; + P_LineAttack(player->mo, angle, MISSILERANGE, + bulletslope + (P_SubRandom() << 5), damage); + } +} + +// +// A_FireCGun +// +void A_FireCGun(player_t *player, pspdef_t *psp) { + S_StartSound(player->mo, sfx_pistol); + + if (!player->ammo[weaponinfo[player->readyweapon].ammo]) + return; + + P_SetMobjState(player->mo, S_PLAY_ATK2); + DecreaseAmmo(player, weaponinfo[player->readyweapon].ammo, 1); + + P_SetPsprite(player, ps_flash, + weaponinfo[player->readyweapon].flashstate + psp->state - + &states[S_CHAIN1]); + + P_BulletSlope(player->mo); + + P_GunShot(player->mo, !player->refire); +} + +// +// ? +// +void A_Light0(player_t *player, pspdef_t *psp) { player->extralight = 0; } + +void A_Light1(player_t *player, pspdef_t *psp) { player->extralight = 1; } + +void A_Light2(player_t *player, pspdef_t *psp) { player->extralight = 2; } + +// +// A_BFGSpray +// Spawn a BFG explosion on every monster in view +// +void A_BFGSpray(mobj_t *mo) { + int i; + int j; + int damage; + angle_t an; + + // offset angles from its attack angle + for (i = 0; i < 40; i++) { + an = mo->angle - ANG90 / 2 + ANG90 / 40 * i; + + // mo->target is the originator (player) + // of the missile + P_AimLineAttack(mo->target, an, 16 * 64 * FRACUNIT); + + if (!linetarget) + continue; + + P_SpawnMobj(linetarget->x, linetarget->y, + linetarget->z + (linetarget->height >> 2), MT_EXTRABFG); + + damage = 0; + for (j = 0; j < 15; j++) + damage += (P_Random() & 7) + 1; + + P_DamageMobj(linetarget, mo->target, mo->target, damage); + } +} + +// +// A_BFGsound +// +void A_BFGsound(player_t *player, pspdef_t *psp) { + S_StartSound(player->mo, sfx_bfg); +} + +// +// P_SetupPsprites +// Called at start of level for each player. +// +void P_SetupPsprites(player_t *player) { + int i; + + // remove all psprites + for (i = 0; i < NUMPSPRITES; i++) + player->psprites[i].state = NULL; + + // spawn the gun + player->pendingweapon = player->readyweapon; + P_BringUpWeapon(player); +} + +// +// P_MovePsprites +// Called every tic by player thinking routine. +// +void P_MovePsprites(player_t *player) { + int i; + pspdef_t *psp; + state_t *state; + + psp = &player->psprites[0]; + for (i = 0; i < NUMPSPRITES; i++, psp++) { + // a null state means not active + if ((state = psp->state)) { + // drop tic count and possibly change state + + // a -1 tic count never changes + if (psp->tics != -1) { + psp->tics--; + if (!psp->tics) + P_SetPsprite(player, i, psp->state->nextstate); + } + } + } + + player->psprites[ps_flash].sx = player->psprites[ps_weapon].sx; + player->psprites[ps_flash].sy = player->psprites[ps_weapon].sy; +} diff --git a/client/src/p_pspr.h b/client/src/p_pspr.h new file mode 100644 index 0000000..23f33c6 --- /dev/null +++ b/client/src/p_pspr.h @@ -0,0 +1,61 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Sprite animation. +// + +#ifndef __P_PSPR__ +#define __P_PSPR__ + +// +// Needs to include the precompiled +// sprite animation tables. +// Header generated by multigen utility. +// This includes all the data for thing animation, +// i.e. the Thing Atrributes table +// and the Frame Sequence table. +#include "info.h" +// Basic data types. +// Needs fixed point, and BAM angles. +#include "m_fixed.h" + +// +// Frame flags: +// handles maximum brightness (torches, muzzle flare, light sources) +// +#define FF_FULLBRIGHT 0x8000 // flag in thing->frame +#define FF_FRAMEMASK 0x7fff + +// +// Overlay psprites are scaled shapes +// drawn directly on the view screen, +// coordinates are given for a 320*200 view screen. +// +typedef enum { + ps_weapon, + ps_flash, + NUMPSPRITES + +} psprnum_t; + +typedef struct { + state_t *state; // a NULL state means not active + int tics; + fixed_t sx; + fixed_t sy; + +} pspdef_t; + +#endif diff --git a/client/src/p_saveg.c b/client/src/p_saveg.c new file mode 100644 index 0000000..0cabb5c --- /dev/null +++ b/client/src/p_saveg.c @@ -0,0 +1,1753 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Archiving: SaveGame I/O. +// + +#include +#include +#include +#include + +#include "d_player.h" +#include "d_think.h" +#include "d_ticcmd.h" +#include "doomdata.h" +#include "doomdef.h" +// State. +#include "doomstat.h" +#include "dstrings.h" +#include "g_game.h" +#include "i_system.h" +#include "info.h" +#include "m_fixed.h" +#include "m_misc.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_pspr.h" +#include "p_saveg.h" +#include "p_spec.h" +#include "r_defs.h" +#include "r_state.h" +#include "z_zone.h" + +FILE *save_stream; +int savegamelength; +boolean savegame_error; + +// Get the filename of a temporary file to write the savegame to. After +// the file has been successfully saved, it will be renamed to the +// real file. + +char *P_TempSaveGameFile(void) { + static char *filename = NULL; + + if (filename == NULL) { + filename = M_StringJoin(savegamedir, "temp.dsg", NULL); + } + + return filename; +} + +// Get the filename of the save game file to use for the specified slot. + +char *P_SaveGameFile(int slot) { + static char *filename = NULL; + static size_t filename_size = 0; + char basename[32]; + + if (filename == NULL) { + filename_size = strlen(savegamedir) + 32; + filename = malloc(filename_size); + } + + M_snprintf(basename, 32, SAVEGAMENAME "%d.dsg", slot); + M_snprintf(filename, filename_size, "%s%s", savegamedir, basename); + + return filename; +} + +// Endian-safe integer read/write functions + +static byte saveg_read8(void) { + byte result = -1; + + if (fread(&result, 1, 1, save_stream) < 1) { + if (!savegame_error) { + fprintf(stderr, "saveg_read8: Unexpected end of file while " + "reading save game\n"); + + savegame_error = true; + } + } + + return result; +} + +static void saveg_write8(byte value) { + if (fwrite(&value, 1, 1, save_stream) < 1) { + if (!savegame_error) { + fprintf(stderr, "saveg_write8: Error while writing save game\n"); + + savegame_error = true; + } + } +} + +static short saveg_read16(void) { + int result; + + result = saveg_read8(); + result |= saveg_read8() << 8; + + return result; +} + +static void saveg_write16(short value) { + saveg_write8(value & 0xff); + saveg_write8((value >> 8) & 0xff); +} + +static int saveg_read32(void) { + int result; + + result = saveg_read8(); + result |= saveg_read8() << 8; + result |= saveg_read8() << 16; + result |= saveg_read8() << 24; + + return result; +} + +static void saveg_write32(int value) { + saveg_write8(value & 0xff); + saveg_write8((value >> 8) & 0xff); + saveg_write8((value >> 16) & 0xff); + saveg_write8((value >> 24) & 0xff); +} + +// Pad to 4-byte boundaries + +static void saveg_read_pad(void) { + unsigned long pos; + int padding; + int i; + + pos = ftell(save_stream); + + padding = (4 - (pos & 3)) & 3; + + for (i = 0; i < padding; ++i) { + saveg_read8(); + } +} + +static void saveg_write_pad(void) { + unsigned long pos; + int padding; + int i; + + pos = ftell(save_stream); + + padding = (4 - (pos & 3)) & 3; + + for (i = 0; i < padding; ++i) { + saveg_write8(0); + } +} + +// Pointers + +static void *saveg_readp(void) { return (void *)(intptr_t)saveg_read32(); } + +static void saveg_writep(void *p) { saveg_write32((intptr_t)p); } + +// Enum values are 32-bit integers. + +#define saveg_read_enum saveg_read32 +#define saveg_write_enum saveg_write32 + +// +// Structure read/write functions +// + +// +// mapthing_t +// + +static void saveg_read_mapthing_t(mapthing_t *str) { + // short x; + str->x = saveg_read16(); + + // short y; + str->y = saveg_read16(); + + // short angle; + str->angle = saveg_read16(); + + // short type; + str->type = saveg_read16(); + + // short options; + str->options = saveg_read16(); +} + +static void saveg_write_mapthing_t(mapthing_t *str) { + // short x; + saveg_write16(str->x); + + // short y; + saveg_write16(str->y); + + // short angle; + saveg_write16(str->angle); + + // short type; + saveg_write16(str->type); + + // short options; + saveg_write16(str->options); +} + +// +// actionf_t +// + +static void saveg_read_actionf_t(actionf_t *str) { + // actionf_p1 acp1; + str->acp1 = saveg_readp(); +} + +static void saveg_write_actionf_t(actionf_t *str) { + // actionf_p1 acp1; + saveg_writep(str->acp1); +} + +// +// think_t +// +// This is just an actionf_t. +// + +#define saveg_read_think_t saveg_read_actionf_t +#define saveg_write_think_t saveg_write_actionf_t + +// +// thinker_t +// + +static void saveg_read_thinker_t(thinker_t *str) { + // struct thinker_s* prev; + str->prev = saveg_readp(); + + // struct thinker_s* next; + str->next = saveg_readp(); + + // think_t function; + saveg_read_think_t(&str->function); +} + +static void saveg_write_thinker_t(thinker_t *str) { + // struct thinker_s* prev; + saveg_writep(str->prev); + + // struct thinker_s* next; + saveg_writep(str->next); + + // think_t function; + saveg_write_think_t(&str->function); +} + +// +// mobj_t +// + +static void saveg_read_mobj_t(mobj_t *str) { + int pl; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // fixed_t x; + str->x = saveg_read32(); + + // fixed_t y; + str->y = saveg_read32(); + + // fixed_t z; + str->z = saveg_read32(); + + // struct mobj_s* snext; + str->snext = saveg_readp(); + + // struct mobj_s* sprev; + str->sprev = saveg_readp(); + + // angle_t angle; + str->angle = saveg_read32(); + + // spritenum_t sprite; + str->sprite = saveg_read_enum(); + + // int frame; + str->frame = saveg_read32(); + + // struct mobj_s* bnext; + str->bnext = saveg_readp(); + + // struct mobj_s* bprev; + str->bprev = saveg_readp(); + + // struct subsector_s* subsector; + str->subsector = saveg_readp(); + + // fixed_t floorz; + str->floorz = saveg_read32(); + + // fixed_t ceilingz; + str->ceilingz = saveg_read32(); + + // fixed_t radius; + str->radius = saveg_read32(); + + // fixed_t height; + str->height = saveg_read32(); + + // fixed_t momx; + str->momx = saveg_read32(); + + // fixed_t momy; + str->momy = saveg_read32(); + + // fixed_t momz; + str->momz = saveg_read32(); + + // int validcount; + str->validcount = saveg_read32(); + + // mobjtype_t type; + str->type = saveg_read_enum(); + + // mobjinfo_t* info; + str->info = saveg_readp(); + + // int tics; + str->tics = saveg_read32(); + + // state_t* state; + str->state = &states[saveg_read32()]; + + // int flags; + str->flags = saveg_read32(); + + // int health; + str->health = saveg_read32(); + + // int movedir; + str->movedir = saveg_read32(); + + // int movecount; + str->movecount = saveg_read32(); + + // struct mobj_s* target; + str->target = saveg_readp(); + + // int reactiontime; + str->reactiontime = saveg_read32(); + + // int threshold; + str->threshold = saveg_read32(); + + // struct player_s* player; + pl = saveg_read32(); + + if (pl > 0) { + str->player = &players[pl - 1]; + str->player->mo = str; + } else { + str->player = NULL; + } + + // int lastlook; + str->lastlook = saveg_read32(); + + // mapthing_t spawnpoint; + saveg_read_mapthing_t(&str->spawnpoint); + + // struct mobj_s* tracer; + str->tracer = saveg_readp(); +} + +static void saveg_write_mobj_t(mobj_t *str) { + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // fixed_t x; + saveg_write32(str->x); + + // fixed_t y; + saveg_write32(str->y); + + // fixed_t z; + saveg_write32(str->z); + + // struct mobj_s* snext; + saveg_writep(str->snext); + + // struct mobj_s* sprev; + saveg_writep(str->sprev); + + // angle_t angle; + saveg_write32(str->angle); + + // spritenum_t sprite; + saveg_write_enum(str->sprite); + + // int frame; + saveg_write32(str->frame); + + // struct mobj_s* bnext; + saveg_writep(str->bnext); + + // struct mobj_s* bprev; + saveg_writep(str->bprev); + + // struct subsector_s* subsector; + saveg_writep(str->subsector); + + // fixed_t floorz; + saveg_write32(str->floorz); + + // fixed_t ceilingz; + saveg_write32(str->ceilingz); + + // fixed_t radius; + saveg_write32(str->radius); + + // fixed_t height; + saveg_write32(str->height); + + // fixed_t momx; + saveg_write32(str->momx); + + // fixed_t momy; + saveg_write32(str->momy); + + // fixed_t momz; + saveg_write32(str->momz); + + // int validcount; + saveg_write32(str->validcount); + + // mobjtype_t type; + saveg_write_enum(str->type); + + // mobjinfo_t* info; + saveg_writep(str->info); + + // int tics; + saveg_write32(str->tics); + + // state_t* state; + saveg_write32(str->state - states); + + // int flags; + saveg_write32(str->flags); + + // int health; + saveg_write32(str->health); + + // int movedir; + saveg_write32(str->movedir); + + // int movecount; + saveg_write32(str->movecount); + + // struct mobj_s* target; + saveg_writep(str->target); + + // int reactiontime; + saveg_write32(str->reactiontime); + + // int threshold; + saveg_write32(str->threshold); + + // struct player_s* player; + if (str->player) { + saveg_write32(str->player - players + 1); + } else { + saveg_write32(0); + } + + // int lastlook; + saveg_write32(str->lastlook); + + // mapthing_t spawnpoint; + saveg_write_mapthing_t(&str->spawnpoint); + + // struct mobj_s* tracer; + saveg_writep(str->tracer); +} + +// +// ticcmd_t +// + +static void saveg_read_ticcmd_t(ticcmd_t *str) { + + // signed char forwardmove; + str->forwardmove = saveg_read8(); + + // signed char sidemove; + str->sidemove = saveg_read8(); + + // short angleturn; + str->angleturn = saveg_read16(); + + // short consistancy; + str->consistancy = saveg_read16(); + + // byte chatchar; + str->chatchar = saveg_read8(); + + // byte buttons; + str->buttons = saveg_read8(); +} + +static void saveg_write_ticcmd_t(ticcmd_t *str) { + + // signed char forwardmove; + saveg_write8(str->forwardmove); + + // signed char sidemove; + saveg_write8(str->sidemove); + + // short angleturn; + saveg_write16(str->angleturn); + + // short consistancy; + saveg_write16(str->consistancy); + + // byte chatchar; + saveg_write8(str->chatchar); + + // byte buttons; + saveg_write8(str->buttons); +} + +// +// pspdef_t +// + +static void saveg_read_pspdef_t(pspdef_t *str) { + int state; + + // state_t* state; + state = saveg_read32(); + + if (state > 0) { + str->state = &states[state]; + } else { + str->state = NULL; + } + + // int tics; + str->tics = saveg_read32(); + + // fixed_t sx; + str->sx = saveg_read32(); + + // fixed_t sy; + str->sy = saveg_read32(); +} + +static void saveg_write_pspdef_t(pspdef_t *str) { + // state_t* state; + if (str->state) { + saveg_write32(str->state - states); + } else { + saveg_write32(0); + } + + // int tics; + saveg_write32(str->tics); + + // fixed_t sx; + saveg_write32(str->sx); + + // fixed_t sy; + saveg_write32(str->sy); +} + +// +// player_t +// + +static void saveg_read_player_t(player_t *str) { + int i; + + // mobj_t* mo; + str->mo = saveg_readp(); + + // playerstate_t playerstate; + str->playerstate = saveg_read_enum(); + + // ticcmd_t cmd; + saveg_read_ticcmd_t(&str->cmd); + + // fixed_t viewz; + str->viewz = saveg_read32(); + + // fixed_t viewheight; + str->viewheight = saveg_read32(); + + // fixed_t deltaviewheight; + str->deltaviewheight = saveg_read32(); + + // fixed_t bob; + str->bob = saveg_read32(); + + // int health; + str->health = saveg_read32(); + + // int armorpoints; + str->armorpoints = saveg_read32(); + + // int armortype; + str->armortype = saveg_read32(); + + // int powers[NUMPOWERS]; + for (i = 0; i < NUMPOWERS; ++i) { + str->powers[i] = saveg_read32(); + } + + // boolean cards[NUMCARDS]; + for (i = 0; i < NUMCARDS; ++i) { + str->cards[i] = saveg_read32(); + } + + // boolean backpack; + str->backpack = saveg_read32(); + + // int frags[MAXPLAYERS]; + for (i = 0; i < MAXPLAYERS; ++i) { + str->frags[i] = saveg_read32(); + } + + // weapontype_t readyweapon; + str->readyweapon = saveg_read_enum(); + + // weapontype_t pendingweapon; + str->pendingweapon = saveg_read_enum(); + + // boolean weaponowned[NUMWEAPONS]; + for (i = 0; i < NUMWEAPONS; ++i) { + str->weaponowned[i] = saveg_read32(); + } + + // int ammo[NUMAMMO]; + for (i = 0; i < NUMAMMO; ++i) { + str->ammo[i] = saveg_read32(); + } + + // int maxammo[NUMAMMO]; + for (i = 0; i < NUMAMMO; ++i) { + str->maxammo[i] = saveg_read32(); + } + + // int attackdown; + str->attackdown = saveg_read32(); + + // int usedown; + str->usedown = saveg_read32(); + + // int cheats; + str->cheats = saveg_read32(); + + // int refire; + str->refire = saveg_read32(); + + // int killcount; + str->killcount = saveg_read32(); + + // int itemcount; + str->itemcount = saveg_read32(); + + // int secretcount; + str->secretcount = saveg_read32(); + + // char* message; + str->message = saveg_readp(); + + // int damagecount; + str->damagecount = saveg_read32(); + + // int bonuscount; + str->bonuscount = saveg_read32(); + + // mobj_t* attacker; + str->attacker = saveg_readp(); + + // int extralight; + str->extralight = saveg_read32(); + + // int fixedcolormap; + str->fixedcolormap = saveg_read32(); + + // int colormap; + str->colormap = saveg_read32(); + + // pspdef_t psprites[NUMPSPRITES]; + for (i = 0; i < NUMPSPRITES; ++i) { + saveg_read_pspdef_t(&str->psprites[i]); + } + + // boolean didsecret; + str->didsecret = saveg_read32(); +} + +static void saveg_write_player_t(player_t *str) { + int i; + + // mobj_t* mo; + saveg_writep(str->mo); + + // playerstate_t playerstate; + saveg_write_enum(str->playerstate); + + // ticcmd_t cmd; + saveg_write_ticcmd_t(&str->cmd); + + // fixed_t viewz; + saveg_write32(str->viewz); + + // fixed_t viewheight; + saveg_write32(str->viewheight); + + // fixed_t deltaviewheight; + saveg_write32(str->deltaviewheight); + + // fixed_t bob; + saveg_write32(str->bob); + + // int health; + saveg_write32(str->health); + + // int armorpoints; + saveg_write32(str->armorpoints); + + // int armortype; + saveg_write32(str->armortype); + + // int powers[NUMPOWERS]; + for (i = 0; i < NUMPOWERS; ++i) { + saveg_write32(str->powers[i]); + } + + // boolean cards[NUMCARDS]; + for (i = 0; i < NUMCARDS; ++i) { + saveg_write32(str->cards[i]); + } + + // boolean backpack; + saveg_write32(str->backpack); + + // int frags[MAXPLAYERS]; + for (i = 0; i < MAXPLAYERS; ++i) { + saveg_write32(str->frags[i]); + } + + // weapontype_t readyweapon; + saveg_write_enum(str->readyweapon); + + // weapontype_t pendingweapon; + saveg_write_enum(str->pendingweapon); + + // boolean weaponowned[NUMWEAPONS]; + for (i = 0; i < NUMWEAPONS; ++i) { + saveg_write32(str->weaponowned[i]); + } + + // int ammo[NUMAMMO]; + for (i = 0; i < NUMAMMO; ++i) { + saveg_write32(str->ammo[i]); + } + + // int maxammo[NUMAMMO]; + for (i = 0; i < NUMAMMO; ++i) { + saveg_write32(str->maxammo[i]); + } + + // int attackdown; + saveg_write32(str->attackdown); + + // int usedown; + saveg_write32(str->usedown); + + // int cheats; + saveg_write32(str->cheats); + + // int refire; + saveg_write32(str->refire); + + // int killcount; + saveg_write32(str->killcount); + + // int itemcount; + saveg_write32(str->itemcount); + + // int secretcount; + saveg_write32(str->secretcount); + + // char* message; + saveg_writep(str->message); + + // int damagecount; + saveg_write32(str->damagecount); + + // int bonuscount; + saveg_write32(str->bonuscount); + + // mobj_t* attacker; + saveg_writep(str->attacker); + + // int extralight; + saveg_write32(str->extralight); + + // int fixedcolormap; + saveg_write32(str->fixedcolormap); + + // int colormap; + saveg_write32(str->colormap); + + // pspdef_t psprites[NUMPSPRITES]; + for (i = 0; i < NUMPSPRITES; ++i) { + saveg_write_pspdef_t(&str->psprites[i]); + } + + // boolean didsecret; + saveg_write32(str->didsecret); +} + +// +// ceiling_t +// + +static void saveg_read_ceiling_t(ceiling_t *str) { + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // ceiling_e type; + str->type = saveg_read_enum(); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // fixed_t bottomheight; + str->bottomheight = saveg_read32(); + + // fixed_t topheight; + str->topheight = saveg_read32(); + + // fixed_t speed; + str->speed = saveg_read32(); + + // boolean crush; + str->crush = saveg_read32(); + + // int direction; + str->direction = saveg_read32(); + + // int tag; + str->tag = saveg_read32(); + + // int olddirection; + str->olddirection = saveg_read32(); +} + +static void saveg_write_ceiling_t(ceiling_t *str) { + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // ceiling_e type; + saveg_write_enum(str->type); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // fixed_t bottomheight; + saveg_write32(str->bottomheight); + + // fixed_t topheight; + saveg_write32(str->topheight); + + // fixed_t speed; + saveg_write32(str->speed); + + // boolean crush; + saveg_write32(str->crush); + + // int direction; + saveg_write32(str->direction); + + // int tag; + saveg_write32(str->tag); + + // int olddirection; + saveg_write32(str->olddirection); +} + +// +// vldoor_t +// + +static void saveg_read_vldoor_t(vldoor_t *str) { + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // vldoor_e type; + str->type = saveg_read_enum(); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // fixed_t topheight; + str->topheight = saveg_read32(); + + // fixed_t speed; + str->speed = saveg_read32(); + + // int direction; + str->direction = saveg_read32(); + + // int topwait; + str->topwait = saveg_read32(); + + // int topcountdown; + str->topcountdown = saveg_read32(); +} + +static void saveg_write_vldoor_t(vldoor_t *str) { + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // vldoor_e type; + saveg_write_enum(str->type); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // fixed_t topheight; + saveg_write32(str->topheight); + + // fixed_t speed; + saveg_write32(str->speed); + + // int direction; + saveg_write32(str->direction); + + // int topwait; + saveg_write32(str->topwait); + + // int topcountdown; + saveg_write32(str->topcountdown); +} + +// +// floormove_t +// + +static void saveg_read_floormove_t(floormove_t *str) { + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // floor_e type; + str->type = saveg_read_enum(); + + // boolean crush; + str->crush = saveg_read32(); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // int direction; + str->direction = saveg_read32(); + + // int newspecial; + str->newspecial = saveg_read32(); + + // short texture; + str->texture = saveg_read16(); + + // fixed_t floordestheight; + str->floordestheight = saveg_read32(); + + // fixed_t speed; + str->speed = saveg_read32(); +} + +static void saveg_write_floormove_t(floormove_t *str) { + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // floor_e type; + saveg_write_enum(str->type); + + // boolean crush; + saveg_write32(str->crush); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // int direction; + saveg_write32(str->direction); + + // int newspecial; + saveg_write32(str->newspecial); + + // short texture; + saveg_write16(str->texture); + + // fixed_t floordestheight; + saveg_write32(str->floordestheight); + + // fixed_t speed; + saveg_write32(str->speed); +} + +// +// plat_t +// + +static void saveg_read_plat_t(plat_t *str) { + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // fixed_t speed; + str->speed = saveg_read32(); + + // fixed_t low; + str->low = saveg_read32(); + + // fixed_t high; + str->high = saveg_read32(); + + // int wait; + str->wait = saveg_read32(); + + // int count; + str->count = saveg_read32(); + + // plat_e status; + str->status = saveg_read_enum(); + + // plat_e oldstatus; + str->oldstatus = saveg_read_enum(); + + // boolean crush; + str->crush = saveg_read32(); + + // int tag; + str->tag = saveg_read32(); + + // plattype_e type; + str->type = saveg_read_enum(); +} + +static void saveg_write_plat_t(plat_t *str) { + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // fixed_t speed; + saveg_write32(str->speed); + + // fixed_t low; + saveg_write32(str->low); + + // fixed_t high; + saveg_write32(str->high); + + // int wait; + saveg_write32(str->wait); + + // int count; + saveg_write32(str->count); + + // plat_e status; + saveg_write_enum(str->status); + + // plat_e oldstatus; + saveg_write_enum(str->oldstatus); + + // boolean crush; + saveg_write32(str->crush); + + // int tag; + saveg_write32(str->tag); + + // plattype_e type; + saveg_write_enum(str->type); +} + +// +// lightflash_t +// + +static void saveg_read_lightflash_t(lightflash_t *str) { + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // int count; + str->count = saveg_read32(); + + // int maxlight; + str->maxlight = saveg_read32(); + + // int minlight; + str->minlight = saveg_read32(); + + // int maxtime; + str->maxtime = saveg_read32(); + + // int mintime; + str->mintime = saveg_read32(); +} + +static void saveg_write_lightflash_t(lightflash_t *str) { + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // int count; + saveg_write32(str->count); + + // int maxlight; + saveg_write32(str->maxlight); + + // int minlight; + saveg_write32(str->minlight); + + // int maxtime; + saveg_write32(str->maxtime); + + // int mintime; + saveg_write32(str->mintime); +} + +// +// strobe_t +// + +static void saveg_read_strobe_t(strobe_t *str) { + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // int count; + str->count = saveg_read32(); + + // int minlight; + str->minlight = saveg_read32(); + + // int maxlight; + str->maxlight = saveg_read32(); + + // int darktime; + str->darktime = saveg_read32(); + + // int brighttime; + str->brighttime = saveg_read32(); +} + +static void saveg_write_strobe_t(strobe_t *str) { + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // int count; + saveg_write32(str->count); + + // int minlight; + saveg_write32(str->minlight); + + // int maxlight; + saveg_write32(str->maxlight); + + // int darktime; + saveg_write32(str->darktime); + + // int brighttime; + saveg_write32(str->brighttime); +} + +// +// glow_t +// + +static void saveg_read_glow_t(glow_t *str) { + int sector; + + // thinker_t thinker; + saveg_read_thinker_t(&str->thinker); + + // sector_t* sector; + sector = saveg_read32(); + str->sector = §ors[sector]; + + // int minlight; + str->minlight = saveg_read32(); + + // int maxlight; + str->maxlight = saveg_read32(); + + // int direction; + str->direction = saveg_read32(); +} + +static void saveg_write_glow_t(glow_t *str) { + // thinker_t thinker; + saveg_write_thinker_t(&str->thinker); + + // sector_t* sector; + saveg_write32(str->sector - sectors); + + // int minlight; + saveg_write32(str->minlight); + + // int maxlight; + saveg_write32(str->maxlight); + + // int direction; + saveg_write32(str->direction); +} + +// +// Write the header for a savegame +// + +void P_WriteSaveGameHeader(char *description) { + char name[VERSIONSIZE]; + int i; + + for (i = 0; description[i] != '\0'; ++i) + saveg_write8(description[i]); + for (; i < SAVESTRINGSIZE; ++i) + saveg_write8(0); + + memset(name, 0, sizeof(name)); + M_snprintf(name, sizeof(name), "version %i", G_VanillaVersionCode()); + + for (i = 0; i < VERSIONSIZE; ++i) + saveg_write8(name[i]); + + saveg_write8(gameskill); + saveg_write8(gameepisode); + saveg_write8(gamemap); + + for (i = 0; i < MAXPLAYERS; i++) + saveg_write8(playeringame[i]); + + saveg_write8((leveltime >> 16) & 0xff); + saveg_write8((leveltime >> 8) & 0xff); + saveg_write8(leveltime & 0xff); +} + +// +// Read the header for a savegame +// + +boolean P_ReadSaveGameHeader(void) { + int i; + byte a, b, c; + char vcheck[VERSIONSIZE]; + char read_vcheck[VERSIONSIZE]; + + // skip the description field + + for (i = 0; i < SAVESTRINGSIZE; ++i) + saveg_read8(); + + for (i = 0; i < VERSIONSIZE; ++i) + read_vcheck[i] = saveg_read8(); + + memset(vcheck, 0, sizeof(vcheck)); + M_snprintf(vcheck, sizeof(vcheck), "version %i", G_VanillaVersionCode()); + if (strcmp(read_vcheck, vcheck) != 0) + return false; // bad version + + gameskill = saveg_read8(); + gameepisode = saveg_read8(); + gamemap = saveg_read8(); + + for (i = 0; i < MAXPLAYERS; i++) + playeringame[i] = saveg_read8(); + + // get the times + a = saveg_read8(); + b = saveg_read8(); + c = saveg_read8(); + leveltime = (a << 16) + (b << 8) + c; + + return true; +} + +// +// Read the end of file marker. Returns true if read successfully. +// + +boolean P_ReadSaveGameEOF(void) { + int value; + + value = saveg_read8(); + + return value == SAVEGAME_EOF; +} + +// +// Write the end of file marker +// + +void P_WriteSaveGameEOF(void) { saveg_write8(SAVEGAME_EOF); } + +// +// P_ArchivePlayers +// +void P_ArchivePlayers(void) { + int i; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + + saveg_write_pad(); + + saveg_write_player_t(&players[i]); + } +} + +// +// P_UnArchivePlayers +// +void P_UnArchivePlayers(void) { + int i; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + + saveg_read_pad(); + + saveg_read_player_t(&players[i]); + + // will be set when unarc thinker + players[i].mo = NULL; + players[i].message = NULL; + players[i].attacker = NULL; + } +} + +// +// P_ArchiveWorld +// +void P_ArchiveWorld(void) { + int i; + int j; + sector_t *sec; + line_t *li; + side_t *si; + + // do sectors + for (i = 0, sec = sectors; i < numsectors; i++, sec++) { + saveg_write16(sec->floorheight >> FRACBITS); + saveg_write16(sec->ceilingheight >> FRACBITS); + saveg_write16(sec->floorpic); + saveg_write16(sec->ceilingpic); + saveg_write16(sec->lightlevel); + saveg_write16(sec->special); // needed? + saveg_write16(sec->tag); // needed? + } + + // do lines + for (i = 0, li = lines; i < numlines; i++, li++) { + saveg_write16(li->flags); + saveg_write16(li->special); + saveg_write16(li->tag); + for (j = 0; j < 2; j++) { + if (li->sidenum[j] == -1) + continue; + + si = &sides[li->sidenum[j]]; + + saveg_write16(si->textureoffset >> FRACBITS); + saveg_write16(si->rowoffset >> FRACBITS); + saveg_write16(si->toptexture); + saveg_write16(si->bottomtexture); + saveg_write16(si->midtexture); + } + } +} + +// +// P_UnArchiveWorld +// +void P_UnArchiveWorld(void) { + int i; + int j; + sector_t *sec; + line_t *li; + side_t *si; + + // do sectors + for (i = 0, sec = sectors; i < numsectors; i++, sec++) { + sec->floorheight = saveg_read16() << FRACBITS; + sec->ceilingheight = saveg_read16() << FRACBITS; + sec->floorpic = saveg_read16(); + sec->ceilingpic = saveg_read16(); + sec->lightlevel = saveg_read16(); + sec->special = saveg_read16(); // needed? + sec->tag = saveg_read16(); // needed? + sec->specialdata = 0; + sec->soundtarget = 0; + } + + // do lines + for (i = 0, li = lines; i < numlines; i++, li++) { + li->flags = saveg_read16(); + li->special = saveg_read16(); + li->tag = saveg_read16(); + for (j = 0; j < 2; j++) { + if (li->sidenum[j] == -1) + continue; + si = &sides[li->sidenum[j]]; + si->textureoffset = saveg_read16() << FRACBITS; + si->rowoffset = saveg_read16() << FRACBITS; + si->toptexture = saveg_read16(); + si->bottomtexture = saveg_read16(); + si->midtexture = saveg_read16(); + } + } +} + +// +// Thinkers +// +typedef enum { + tc_end, + tc_mobj + +} thinkerclass_t; + +// +// P_ArchiveThinkers +// +void P_ArchiveThinkers(void) { + thinker_t *th; + + // save off the current thinkers + for (th = thinkercap.next; th != &thinkercap; th = th->next) { + if (th->function.acp1 == (actionf_p1)P_MobjThinker) { + saveg_write8(tc_mobj); + saveg_write_pad(); + saveg_write_mobj_t((mobj_t *)th); + + continue; + } + + // I_Error ("P_ArchiveThinkers: Unknown thinker function"); + } + + // add a terminating marker + saveg_write8(tc_end); +} + +// +// P_UnArchiveThinkers +// +void P_UnArchiveThinkers(void) { + byte tclass; + thinker_t *currentthinker; + thinker_t *next; + mobj_t *mobj; + + // remove all the current thinkers + currentthinker = thinkercap.next; + while (currentthinker != &thinkercap) { + next = currentthinker->next; + + if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker) + P_RemoveMobj((mobj_t *)currentthinker); + else + Z_Free(currentthinker); + + currentthinker = next; + } + P_InitThinkers(); + + // read in saved thinkers + while (1) { + tclass = saveg_read8(); + switch (tclass) { + case tc_end: + return; // end of list + + case tc_mobj: + saveg_read_pad(); + mobj = Z_Malloc(sizeof(*mobj), PU_LEVEL, NULL); + saveg_read_mobj_t(mobj); + + mobj->target = NULL; + mobj->tracer = NULL; + P_SetThingPosition(mobj); + mobj->info = &mobjinfo[mobj->type]; + mobj->floorz = mobj->subsector->sector->floorheight; + mobj->ceilingz = mobj->subsector->sector->ceilingheight; + mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; + P_AddThinker(&mobj->thinker); + break; + + default: + I_Error("Unknown tclass %i in savegame", tclass); + } + } +} + +// +// P_ArchiveSpecials +// +enum { + tc_ceiling, + tc_door, + tc_floor, + tc_plat, + tc_flash, + tc_strobe, + tc_glow, + tc_endspecials + +} specials_e; + +// +// Things to handle: +// +// T_MoveCeiling, (ceiling_t: sector_t * swizzle), - active list +// T_VerticalDoor, (vldoor_t: sector_t * swizzle), +// T_MoveFloor, (floormove_t: sector_t * swizzle), +// T_LightFlash, (lightflash_t: sector_t * swizzle), +// T_StrobeFlash, (strobe_t: sector_t *), +// T_Glow, (glow_t: sector_t *), +// T_PlatRaise, (plat_t: sector_t *), - active list +// +void P_ArchiveSpecials(void) { + thinker_t *th; + int i; + + // save off the current thinkers + for (th = thinkercap.next; th != &thinkercap; th = th->next) { + if (th->function.acv == (actionf_v)NULL) { + for (i = 0; i < MAXCEILINGS; i++) + if (activeceilings[i] == (ceiling_t *)th) + break; + + if (i < MAXCEILINGS) { + saveg_write8(tc_ceiling); + saveg_write_pad(); + saveg_write_ceiling_t((ceiling_t *)th); + } + continue; + } + + if (th->function.acp1 == (actionf_p1)T_MoveCeiling) { + saveg_write8(tc_ceiling); + saveg_write_pad(); + saveg_write_ceiling_t((ceiling_t *)th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_VerticalDoor) { + saveg_write8(tc_door); + saveg_write_pad(); + saveg_write_vldoor_t((vldoor_t *)th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_MoveFloor) { + saveg_write8(tc_floor); + saveg_write_pad(); + saveg_write_floormove_t((floormove_t *)th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_PlatRaise) { + saveg_write8(tc_plat); + saveg_write_pad(); + saveg_write_plat_t((plat_t *)th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_LightFlash) { + saveg_write8(tc_flash); + saveg_write_pad(); + saveg_write_lightflash_t((lightflash_t *)th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_StrobeFlash) { + saveg_write8(tc_strobe); + saveg_write_pad(); + saveg_write_strobe_t((strobe_t *)th); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_Glow) { + saveg_write8(tc_glow); + saveg_write_pad(); + saveg_write_glow_t((glow_t *)th); + continue; + } + } + + // add a terminating marker + saveg_write8(tc_endspecials); +} + +// +// P_UnArchiveSpecials +// +void P_UnArchiveSpecials(void) { + byte tclass; + ceiling_t *ceiling; + vldoor_t *door; + floormove_t *floor; + plat_t *plat; + lightflash_t *flash; + strobe_t *strobe; + glow_t *glow; + + // read in saved thinkers + while (1) { + tclass = saveg_read8(); + + switch (tclass) { + case tc_endspecials: + return; // end of list + + case tc_ceiling: + saveg_read_pad(); + ceiling = Z_Malloc(sizeof(*ceiling), PU_LEVEL, NULL); + saveg_read_ceiling_t(ceiling); + ceiling->sector->specialdata = ceiling; + + if (ceiling->thinker.function.acp1) + ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling; + + P_AddThinker(&ceiling->thinker); + P_AddActiveCeiling(ceiling); + break; + + case tc_door: + saveg_read_pad(); + door = Z_Malloc(sizeof(*door), PU_LEVEL, NULL); + saveg_read_vldoor_t(door); + door->sector->specialdata = door; + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + P_AddThinker(&door->thinker); + break; + + case tc_floor: + saveg_read_pad(); + floor = Z_Malloc(sizeof(*floor), PU_LEVEL, NULL); + saveg_read_floormove_t(floor); + floor->sector->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor; + P_AddThinker(&floor->thinker); + break; + + case tc_plat: + saveg_read_pad(); + plat = Z_Malloc(sizeof(*plat), PU_LEVEL, NULL); + saveg_read_plat_t(plat); + plat->sector->specialdata = plat; + + if (plat->thinker.function.acp1) + plat->thinker.function.acp1 = (actionf_p1)T_PlatRaise; + + P_AddThinker(&plat->thinker); + P_AddActivePlat(plat); + break; + + case tc_flash: + saveg_read_pad(); + flash = Z_Malloc(sizeof(*flash), PU_LEVEL, NULL); + saveg_read_lightflash_t(flash); + flash->thinker.function.acp1 = (actionf_p1)T_LightFlash; + P_AddThinker(&flash->thinker); + break; + + case tc_strobe: + saveg_read_pad(); + strobe = Z_Malloc(sizeof(*strobe), PU_LEVEL, NULL); + saveg_read_strobe_t(strobe); + strobe->thinker.function.acp1 = (actionf_p1)T_StrobeFlash; + P_AddThinker(&strobe->thinker); + break; + + case tc_glow: + saveg_read_pad(); + glow = Z_Malloc(sizeof(*glow), PU_LEVEL, NULL); + saveg_read_glow_t(glow); + glow->thinker.function.acp1 = (actionf_p1)T_Glow; + P_AddThinker(&glow->thinker); + break; + + default: + I_Error("P_UnarchiveSpecials:Unknown tclass %i " + "in savegame", + tclass); + } + } +} diff --git a/client/src/p_saveg.h b/client/src/p_saveg.h new file mode 100644 index 0000000..aa173c2 --- /dev/null +++ b/client/src/p_saveg.h @@ -0,0 +1,65 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Savegame I/O, archiving, persistence. +// + +#ifndef __P_SAVEG__ +#define __P_SAVEG__ + +#include + +#include "doomtype.h" + +#define SAVEGAME_EOF 0x1d +#define VERSIONSIZE 16 + +// maximum size of a savegame description + +#define SAVESTRINGSIZE 24 + +// temporary filename to use while saving. + +char *P_TempSaveGameFile(void); + +// filename to use for a savegame slot + +char *P_SaveGameFile(int slot); + +// Savegame file header read/write functions + +boolean P_ReadSaveGameHeader(void); +void P_WriteSaveGameHeader(char *description); + +// Savegame end-of-file read/write functions + +boolean P_ReadSaveGameEOF(void); +void P_WriteSaveGameEOF(void); + +// Persistent storage/archiving. +// These are the load / save game routines. +void P_ArchivePlayers(void); +void P_UnArchivePlayers(void); +void P_ArchiveWorld(void); +void P_UnArchiveWorld(void); +void P_ArchiveThinkers(void); +void P_UnArchiveThinkers(void); +void P_ArchiveSpecials(void); +void P_UnArchiveSpecials(void); + +extern FILE *save_stream; +extern boolean savegame_error; + +#endif diff --git a/client/src/p_setup.c b/client/src/p_setup.c new file mode 100644 index 0000000..605cdb6 --- /dev/null +++ b/client/src/p_setup.c @@ -0,0 +1,778 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Do all the WAD I/O, get map description, +// set up initial state and misc. LUTs. +// + +#include +#include + +#include "d_mode.h" +#include "d_player.h" +#include "doomdata.h" +#include "doomdef.h" +#include "doomstat.h" +#include "doomtype.h" +#include "g_game.h" +#include "i_swap.h" +#include "i_system.h" +#include "info.h" +#include "m_argv.h" +#include "m_bbox.h" +#include "m_fixed.h" +#include "m_misc.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_spec.h" +#include "r_data.h" +#include "r_defs.h" +#include "r_things.h" +#include "s_sound.h" +#include "w_wad.h" +#include "z_zone.h" + +void P_SpawnMapThing(mapthing_t *mthing); + +// +// MAP related Lookup tables. +// Store VERTEXES, LINEDEFS, SIDEDEFS, etc. +// +int numvertexes; +vertex_t *vertexes; + +int numsegs; +seg_t *segs; + +int numsectors; +sector_t *sectors; + +int numsubsectors; +subsector_t *subsectors; + +int numnodes; +node_t *nodes; + +int numlines; +line_t *lines; + +int numsides; +side_t *sides; + +static int totallines; + +// BLOCKMAP +// Created from axis aligned bounding box +// of the map, a rectangular array of +// blocks of size ... +// Used to speed up collision detection +// by spatial subdivision in 2D. +// +// Blockmap size. +int bmapwidth; +int bmapheight; // size in mapblocks +short *blockmap; // int for larger maps +// offsets in blockmap are from here +short *blockmaplump; +// origin of block map +fixed_t bmaporgx; +fixed_t bmaporgy; +// for thing chains +mobj_t **blocklinks; + +// REJECT +// For fast sight rejection. +// Speeds up enemy AI by skipping detailed +// LineOf Sight calculation. +// Without special effect, this could be +// used as a PVS lookup as well. +// +byte *rejectmatrix; + +// Maintain single and multi player starting spots. +#define MAX_DEATHMATCH_STARTS 10 + +mapthing_t deathmatchstarts[MAX_DEATHMATCH_STARTS]; +mapthing_t *deathmatch_p; +mapthing_t playerstarts[MAXPLAYERS]; + +// +// P_LoadVertexes +// +void P_LoadVertexes(int lump) { + byte *data; + int i; + mapvertex_t *ml; + vertex_t *li; + + // Determine number of lumps: + // total lump length / vertex record length. + numvertexes = W_LumpLength(lump) / sizeof(mapvertex_t); + + // Allocate zone memory for buffer. + vertexes = Z_Malloc(numvertexes * sizeof(vertex_t), PU_LEVEL, 0); + + // Load data into cache. + data = W_CacheLumpNum(lump, PU_STATIC); + + ml = (mapvertex_t *)data; + li = vertexes; + + // Copy and convert vertex coordinates, + // internal representation as fixed. + for (i = 0; i < numvertexes; i++, li++, ml++) { + li->x = SHORT(ml->x) << FRACBITS; + li->y = SHORT(ml->y) << FRACBITS; + } + + // Free buffer memory. + W_ReleaseLumpNum(lump); +} + +// +// GetSectorAtNullAddress +// +sector_t *GetSectorAtNullAddress(void) { + static boolean null_sector_is_initialized = false; + static sector_t null_sector; + + if (!null_sector_is_initialized) { + memset(&null_sector, 0, sizeof(null_sector)); + I_GetMemoryValue(0, &null_sector.floorheight, 4); + I_GetMemoryValue(4, &null_sector.ceilingheight, 4); + null_sector_is_initialized = true; + } + + return &null_sector; +} + +// +// P_LoadSegs +// +void P_LoadSegs(int lump) { + byte *data; + int i; + mapseg_t *ml; + seg_t *li; + line_t *ldef; + int linedef; + int side; + int sidenum; + + numsegs = W_LumpLength(lump) / sizeof(mapseg_t); + segs = Z_Malloc(numsegs * sizeof(seg_t), PU_LEVEL, 0); + memset(segs, 0, numsegs * sizeof(seg_t)); + data = W_CacheLumpNum(lump, PU_STATIC); + + ml = (mapseg_t *)data; + li = segs; + for (i = 0; i < numsegs; i++, li++, ml++) { + li->v1 = &vertexes[SHORT(ml->v1)]; + li->v2 = &vertexes[SHORT(ml->v2)]; + + li->angle = (SHORT(ml->angle)) << FRACBITS; + li->offset = (SHORT(ml->offset)) << FRACBITS; + linedef = SHORT(ml->linedef); + ldef = &lines[linedef]; + li->linedef = ldef; + side = SHORT(ml->side); + + // e6y: check for wrong indexes + if ((unsigned)ldef->sidenum[side] >= (unsigned)numsides) { + I_Error("P_LoadSegs: linedef %d for seg %d references a non-existent " + "sidedef %d", + linedef, i, (unsigned)ldef->sidenum[side]); + } + + li->sidedef = &sides[ldef->sidenum[side]]; + li->frontsector = sides[ldef->sidenum[side]].sector; + + if (ldef->flags & ML_TWOSIDED) { + sidenum = ldef->sidenum[side ^ 1]; + + // If the sidenum is out of range, this may be a "glass hack" + // impassible window. Point at side #0 (this may not be + // the correct Vanilla behavior; however, it seems to work for + // OTTAWAU.WAD, which is the one place I've seen this trick + // used). + + if (sidenum < 0 || sidenum >= numsides) { + li->backsector = GetSectorAtNullAddress(); + } else { + li->backsector = sides[sidenum].sector; + } + } else { + li->backsector = 0; + } + } + + W_ReleaseLumpNum(lump); +} + +// +// P_LoadSubsectors +// +void P_LoadSubsectors(int lump) { + byte *data; + int i; + mapsubsector_t *ms; + subsector_t *ss; + + numsubsectors = W_LumpLength(lump) / sizeof(mapsubsector_t); + subsectors = Z_Malloc(numsubsectors * sizeof(subsector_t), PU_LEVEL, 0); + data = W_CacheLumpNum(lump, PU_STATIC); + + ms = (mapsubsector_t *)data; + memset(subsectors, 0, numsubsectors * sizeof(subsector_t)); + ss = subsectors; + + for (i = 0; i < numsubsectors; i++, ss++, ms++) { + ss->numlines = SHORT(ms->numsegs); + ss->firstline = SHORT(ms->firstseg); + } + + W_ReleaseLumpNum(lump); +} + +// +// P_LoadSectors +// +void P_LoadSectors(int lump) { + byte *data; + int i; + mapsector_t *ms; + sector_t *ss; + + numsectors = W_LumpLength(lump) / sizeof(mapsector_t); + sectors = Z_Malloc(numsectors * sizeof(sector_t), PU_LEVEL, 0); + memset(sectors, 0, numsectors * sizeof(sector_t)); + data = W_CacheLumpNum(lump, PU_STATIC); + + ms = (mapsector_t *)data; + ss = sectors; + for (i = 0; i < numsectors; i++, ss++, ms++) { + ss->floorheight = SHORT(ms->floorheight) << FRACBITS; + ss->ceilingheight = SHORT(ms->ceilingheight) << FRACBITS; + ss->floorpic = R_FlatNumForName(ms->floorpic); + ss->ceilingpic = R_FlatNumForName(ms->ceilingpic); + ss->lightlevel = SHORT(ms->lightlevel); + ss->special = SHORT(ms->special); + ss->tag = SHORT(ms->tag); + ss->thinglist = NULL; + } + + W_ReleaseLumpNum(lump); +} + +// +// P_LoadNodes +// +void P_LoadNodes(int lump) { + byte *data; + int i; + int j; + int k; + mapnode_t *mn; + node_t *no; + + numnodes = W_LumpLength(lump) / sizeof(mapnode_t); + nodes = Z_Malloc(numnodes * sizeof(node_t), PU_LEVEL, 0); + data = W_CacheLumpNum(lump, PU_STATIC); + + mn = (mapnode_t *)data; + no = nodes; + + for (i = 0; i < numnodes; i++, no++, mn++) { + no->x = SHORT(mn->x) << FRACBITS; + no->y = SHORT(mn->y) << FRACBITS; + no->dx = SHORT(mn->dx) << FRACBITS; + no->dy = SHORT(mn->dy) << FRACBITS; + for (j = 0; j < 2; j++) { + no->children[j] = SHORT(mn->children[j]); + for (k = 0; k < 4; k++) + no->bbox[j][k] = SHORT(mn->bbox[j][k]) << FRACBITS; + } + } + + W_ReleaseLumpNum(lump); +} + +// +// P_LoadThings +// +void P_LoadThings(int lump) { + byte *data; + int i; + mapthing_t *mt; + mapthing_t spawnthing; + int numthings; + boolean spawn; + + data = W_CacheLumpNum(lump, PU_STATIC); + numthings = W_LumpLength(lump) / sizeof(mapthing_t); + + mt = (mapthing_t *)data; + for (i = 0; i < numthings; i++, mt++) { + spawn = true; + + // Do not spawn cool, new monsters if !commercial + if (gamemode != commercial) { + switch (SHORT(mt->type)) { + case 68: // Arachnotron + case 64: // Archvile + case 88: // Boss Brain + case 89: // Boss Shooter + case 69: // Hell Knight + case 67: // Mancubus + case 71: // Pain Elemental + case 65: // Former Human Commando + case 66: // Revenant + case 84: // Wolf SS + spawn = false; + break; + } + } + if (spawn == false) + break; + + // Do spawn all other stuff. + spawnthing.x = SHORT(mt->x); + spawnthing.y = SHORT(mt->y); + spawnthing.angle = SHORT(mt->angle); + spawnthing.type = SHORT(mt->type); + spawnthing.options = SHORT(mt->options); + + P_SpawnMapThing(&spawnthing); + } + + W_ReleaseLumpNum(lump); +} + +// +// P_LoadLineDefs +// Also counts secret lines for intermissions. +// +void P_LoadLineDefs(int lump) { + byte *data; + int i; + maplinedef_t *mld; + line_t *ld; + vertex_t *v1; + vertex_t *v2; + + numlines = W_LumpLength(lump) / sizeof(maplinedef_t); + lines = Z_Malloc(numlines * sizeof(line_t), PU_LEVEL, 0); + memset(lines, 0, numlines * sizeof(line_t)); + data = W_CacheLumpNum(lump, PU_STATIC); + + mld = (maplinedef_t *)data; + ld = lines; + for (i = 0; i < numlines; i++, mld++, ld++) { + ld->flags = (mld->flags); + ld->special = (mld->special); + ld->tag = (mld->tag); + v1 = ld->v1 = &vertexes[(mld->v1)]; + v2 = ld->v2 = &vertexes[(mld->v2)]; + ld->dx = v2->x - v1->x; + ld->dy = v2->y - v1->y; + + if (!ld->dx) + ld->slopetype = ST_VERTICAL; + else if (!ld->dy) + ld->slopetype = ST_HORIZONTAL; + else { + if (FixedDiv(ld->dy, ld->dx) > 0) + ld->slopetype = ST_POSITIVE; + else + ld->slopetype = ST_NEGATIVE; + } + + if (v1->x < v2->x) { + ld->bbox[BOXLEFT] = v1->x; + ld->bbox[BOXRIGHT] = v2->x; + } else { + ld->bbox[BOXLEFT] = v2->x; + ld->bbox[BOXRIGHT] = v1->x; + } + + if (v1->y < v2->y) { + ld->bbox[BOXBOTTOM] = v1->y; + ld->bbox[BOXTOP] = v2->y; + } else { + ld->bbox[BOXBOTTOM] = v2->y; + ld->bbox[BOXTOP] = v1->y; + } + + ld->sidenum[0] = SHORT(mld->sidenum[0]); + ld->sidenum[1] = SHORT(mld->sidenum[1]); + + if (ld->sidenum[0] != -1) + ld->frontsector = sides[ld->sidenum[0]].sector; + else + ld->frontsector = 0; + + if (ld->sidenum[1] != -1) + ld->backsector = sides[ld->sidenum[1]].sector; + else + ld->backsector = 0; + } + + printf("[CPU] numlines: %d, sidenum0: %d, sidenum-1: %d, dx-1: %d\n", + numlines, lines[0].sidenum[1], lines[numlines-1].sidenum[1], lines[numlines-1].dx); + + W_ReleaseLumpNum(lump); +} + +// +// P_LoadSideDefs +// +void P_LoadSideDefs(int lump) { + byte *data; + int i; + mapsidedef_t *msd; + side_t *sd; + + numsides = W_LumpLength(lump) / sizeof(mapsidedef_t); + sides = Z_Malloc(numsides * sizeof(side_t), PU_LEVEL, 0); + memset(sides, 0, numsides * sizeof(side_t)); + data = W_CacheLumpNum(lump, PU_STATIC); + + msd = (mapsidedef_t *)data; + sd = sides; + for (i = 0; i < numsides; i++, msd++, sd++) { + sd->textureoffset = SHORT(msd->textureoffset) << FRACBITS; + sd->rowoffset = SHORT(msd->rowoffset) << FRACBITS; + sd->toptexture = R_TextureNumForName(msd->toptexture); + sd->bottomtexture = R_TextureNumForName(msd->bottomtexture); + sd->midtexture = R_TextureNumForName(msd->midtexture); + sd->sector = §ors[SHORT(msd->sector)]; + } + + W_ReleaseLumpNum(lump); +} + +// +// P_LoadBlockMap +// +void P_LoadBlockMap(int lump) { + int i; + int count; + int lumplen; + + lumplen = W_LumpLength(lump); + count = lumplen / 2; + + blockmaplump = Z_Malloc(lumplen, PU_LEVEL, NULL); + W_ReadLump(lump, blockmaplump); + blockmap = blockmaplump + 4; + + // Swap all short integers to native byte ordering. + + for (i = 0; i < count; i++) { + blockmaplump[i] = SHORT(blockmaplump[i]); + } + + // Read the header + + bmaporgx = blockmaplump[0] << FRACBITS; + bmaporgy = blockmaplump[1] << FRACBITS; + bmapwidth = blockmaplump[2]; + bmapheight = blockmaplump[3]; + + // Clear out mobj chains + + count = sizeof(*blocklinks) * bmapwidth * bmapheight; + blocklinks = Z_Malloc(count, PU_LEVEL, 0); + memset(blocklinks, 0, count); +} + +// +// P_GroupLines +// Builds sector line lists and subsector sector numbers. +// Finds block bounding boxes for sectors. +// +void P_GroupLines(void) { + line_t **linebuffer; + int i; + int j; + line_t *li; + sector_t *sector; + subsector_t *ss; + seg_t *seg; + fixed_t bbox[4]; + int block; + + // look up sector number for each subsector + ss = subsectors; + for (i = 0; i < numsubsectors; i++, ss++) { + seg = &segs[ss->firstline]; + ss->sector = seg->sidedef->sector; + } + + // count number of lines in each sector + li = lines; + totallines = 0; + for (i = 0; i < numlines; i++, li++) { + totallines++; + li->frontsector->linecount++; + + if (li->backsector && li->backsector != li->frontsector) { + li->backsector->linecount++; + totallines++; + } + } + + // build line tables for each sector + linebuffer = Z_Malloc(totallines * sizeof(line_t *), PU_LEVEL, 0); + + for (i = 0; i < numsectors; ++i) { + // Assign the line buffer for this sector + + sectors[i].lines = linebuffer; + linebuffer += sectors[i].linecount; + + // Reset linecount to zero so in the next stage we can count + // lines into the list. + + sectors[i].linecount = 0; + } + + // Assign lines to sectors + + for (i = 0; i < numlines; ++i) { + li = &lines[i]; + + if (li->frontsector != NULL) { + sector = li->frontsector; + + sector->lines[sector->linecount] = li; + ++sector->linecount; + } + + if (li->backsector != NULL && li->frontsector != li->backsector) { + sector = li->backsector; + + sector->lines[sector->linecount] = li; + ++sector->linecount; + } + } + + // Generate bounding boxes for sectors + + sector = sectors; + for (i = 0; i < numsectors; i++, sector++) { + M_ClearBox(bbox); + + for (j = 0; j < sector->linecount; j++) { + li = sector->lines[j]; + + M_AddToBox(bbox, li->v1->x, li->v1->y); + M_AddToBox(bbox, li->v2->x, li->v2->y); + } + + // set the degenmobj_t to the middle of the bounding box + sector->soundorg.x = (bbox[BOXRIGHT] + bbox[BOXLEFT]) / 2; + sector->soundorg.y = (bbox[BOXTOP] + bbox[BOXBOTTOM]) / 2; + + // adjust bounding box to map blocks + block = (bbox[BOXTOP] - bmaporgy + MAXRADIUS) >> MAPBLOCKSHIFT; + block = block >= bmapheight ? bmapheight - 1 : block; + sector->blockbox[BOXTOP] = block; + + block = (bbox[BOXBOTTOM] - bmaporgy - MAXRADIUS) >> MAPBLOCKSHIFT; + block = block < 0 ? 0 : block; + sector->blockbox[BOXBOTTOM] = block; + + block = (bbox[BOXRIGHT] - bmaporgx + MAXRADIUS) >> MAPBLOCKSHIFT; + block = block >= bmapwidth ? bmapwidth - 1 : block; + sector->blockbox[BOXRIGHT] = block; + + block = (bbox[BOXLEFT] - bmaporgx - MAXRADIUS) >> MAPBLOCKSHIFT; + block = block < 0 ? 0 : block; + sector->blockbox[BOXLEFT] = block; + } +} + +// Pad the REJECT lump with extra data when the lump is too small, +// to simulate a REJECT buffer overflow in Vanilla Doom. + +static void PadRejectArray(byte *array, unsigned int len) { + unsigned int i; + unsigned int byte_num; + byte *dest; + unsigned int padvalue; + + // Values to pad the REJECT array with: + + unsigned int rejectpad[4] = { + 0, // Size + 0, // Part of z_zone block header + 50, // PU_LEVEL + 0x1d4a11 // DOOM_CONST_ZONEID + }; + + rejectpad[0] = ((totallines * 4 + 3) & ~3) + 24; + + // Copy values from rejectpad into the destination array. + + dest = array; + + for (i = 0; i < len && i < sizeof(rejectpad); ++i) { + byte_num = i % 4; + *dest = (rejectpad[i / 4] >> (byte_num * 8)) & 0xff; + ++dest; + } + + // We only have a limited pad size. Print a warning if the + // REJECT lump is too small. + + if (len > sizeof(rejectpad)) { + fprintf(stderr, "PadRejectArray: REJECT lump too short to pad! (%i > %i)\n", + len, (int)sizeof(rejectpad)); + + // Pad remaining space with 0 (or 0xff, if specified on command line). + + if (M_CheckParm("-reject_pad_with_ff")) { + padvalue = 0xff; + } else { + padvalue = 0xf00; + } + + memset(array + sizeof(rejectpad), padvalue, len - sizeof(rejectpad)); + } +} + +static void P_LoadReject(int lumpnum) { + int minlength; + int lumplen; + + // Calculate the size that the REJECT lump *should* be. + + minlength = (numsectors * numsectors + 7) / 8; + + // If the lump meets the minimum length, it can be loaded directly. + // Otherwise, we need to allocate a buffer of the correct size + // and pad it with appropriate data. + + lumplen = W_LumpLength(lumpnum); + + if (lumplen >= minlength) { + rejectmatrix = W_CacheLumpNum(lumpnum, PU_LEVEL); + } else { + rejectmatrix = Z_Malloc(minlength, PU_LEVEL, &rejectmatrix); + W_ReadLump(lumpnum, rejectmatrix); + + PadRejectArray(rejectmatrix + lumplen, minlength - lumplen); + } +} + +// +// P_SetupLevel +// +void P_SetupLevel(int episode, int map, int playermask, skill_t skill) { + int i; + char lumpname[9]; + int lumpnum; + + totalkills = totalitems = totalsecret = wminfo.maxfrags = 0; + wminfo.partime = 180; + for (i = 0; i < MAXPLAYERS; i++) { + players[i].killcount = players[i].secretcount = players[i].itemcount = 0; + } + + // Initial height of PointOfView + // will be set by player think. + players[consoleplayer].viewz = 1; + + // Make sure all sounds are stopped before Z_FreeTags. + S_Start(); + + Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1); + + // UNUSED W_Profile (); + P_InitThinkers(); + + // if working with a devlopment map, reload it + W_Reload(); + + // find map name + if (gamemode == commercial) { + if (map < 10) + M_snprintf(lumpname, 9, "map0%i", map); + else + M_snprintf(lumpname, 9, "map%i", map); + } else { + lumpname[0] = 'E'; + lumpname[1] = '0' + gameepisode; + lumpname[2] = 'M'; + lumpname[3] = '0' + map; + lumpname[4] = 0; + } + + lumpnum = W_GetNumForName(lumpname); + + leveltime = 0; + + // note: most of this ordering is important + P_LoadBlockMap(lumpnum + ML_BLOCKMAP); + P_LoadVertexes(lumpnum + ML_VERTEXES); + P_LoadSectors(lumpnum + ML_SECTORS); + P_LoadSideDefs(lumpnum + ML_SIDEDEFS); + + P_LoadLineDefs(lumpnum + ML_LINEDEFS); + P_LoadSubsectors(lumpnum + ML_SSECTORS); + P_LoadNodes(lumpnum + ML_NODES); + P_LoadSegs(lumpnum + ML_SEGS); + + P_GroupLines(); + P_LoadReject(lumpnum + ML_REJECT); + + bodyqueslot = 0; + deathmatch_p = deathmatchstarts; + P_LoadThings(lumpnum + ML_THINGS); + + // if deathmatch, randomly spawn the active players + if (deathmatch) { + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) { + players[i].mo = NULL; + G_DeathMatchSpawnPlayer(i); + } + } + + // clear special respawning que + iquehead = iquetail = 0; + + // set up world state + P_SpawnSpecials(); + + // build subsector connect matrix + // UNUSED P_ConnectSubsectors (); + + // preload graphics + if (precache) + R_PrecacheLevel(); + + // printf ("free memory: 0x%x\n", Z_FreeMemory()); +} + +// +// P_Init +// +void P_Init(void) { + P_InitSwitchList(); + P_InitPicAnims(); + R_InitSprites(sprnames); +} diff --git a/client/src/p_setup.h b/client/src/p_setup.h new file mode 100644 index 0000000..c248c4a --- /dev/null +++ b/client/src/p_setup.h @@ -0,0 +1,28 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Setup a game, startup stuff. +// + +#ifndef __P_SETUP__ +#define __P_SETUP__ + +// NOT called by W_Ticker. Fixme. +void P_SetupLevel(int episode, int map, int playermask, skill_t skill); + +// Called by startup code. +void P_Init(void); + +#endif diff --git a/client/src/p_sight.c b/client/src/p_sight.c new file mode 100644 index 0000000..84fbb8a --- /dev/null +++ b/client/src/p_sight.c @@ -0,0 +1,318 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// LineOfSight/Visibility checks, uses REJECT Lookup Table. +// + +#include + +#include "doomdata.h" +#include "doomtype.h" +#include "i_system.h" +#include "m_fixed.h" +#include "p_local.h" +#include "p_mobj.h" +#include "r_defs.h" +#include "r_main.h" +// State. +#include "r_state.h" + +// +// P_CheckSight +// +fixed_t sightzstart; // eye z of looker +fixed_t topslope; +fixed_t bottomslope; // slopes to top and bottom of target + +divline_t strace; // from t1 to t2 +fixed_t t2x; +fixed_t t2y; + +int sightcounts[2]; + +// +// P_DivlineSide +// Returns side 0 (front), 1 (back), or 2 (on). +// +int P_DivlineSide(fixed_t x, fixed_t y, divline_t *node) { + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!node->dx) { + if (x == node->x) + return 2; + + if (x <= node->x) + return node->dy > 0; + + return node->dy < 0; + } + + if (!node->dy) { + if (x == node->y) + return 2; + + if (y <= node->y) + return node->dx < 0; + + return node->dx > 0; + } + + dx = (x - node->x); + dy = (y - node->y); + + left = (node->dy >> FRACBITS) * (dx >> FRACBITS); + right = (dy >> FRACBITS) * (node->dx >> FRACBITS); + + if (right < left) + return 0; // front side + + if (left == right) + return 2; + return 1; // back side +} + +// +// P_InterceptVector2 +// Returns the fractional intercept point +// along the first divline. +// This is only called by the addthings and addlines traversers. +// +fixed_t P_InterceptVector2(divline_t *v2, divline_t *v1) { + fixed_t frac; + fixed_t num; + fixed_t den; + + den = FixedMul(v1->dy >> 8, v2->dx) - FixedMul(v1->dx >> 8, v2->dy); + + if (den == 0) + return 0; + // I_Error ("P_InterceptVector: parallel"); + + num = FixedMul((v1->x - v2->x) >> 8, v1->dy) + + FixedMul((v2->y - v1->y) >> 8, v1->dx); + frac = FixedDiv(num, den); + + return frac; +} + +// +// P_CrossSubsector +// Returns true +// if strace crosses the given subsector successfully. +// +boolean P_CrossSubsector(int num) { + seg_t *seg; + line_t *line; + int s1; + int s2; + int count; + subsector_t *sub; + sector_t *front; + sector_t *back; + fixed_t opentop; + fixed_t openbottom; + divline_t divl; + vertex_t *v1; + vertex_t *v2; + fixed_t frac; + fixed_t slope; + + if (num >= numsubsectors) + I_Error("P_CrossSubsector: ss %i with numss = %i", num, numsubsectors); + + sub = &subsectors[num]; + + // check lines + count = sub->numlines; + seg = &segs[sub->firstline]; + + for (; count; seg++, count--) { + line = seg->linedef; + + // allready checked other side? + if (line->validcount == validcount) + continue; + + line->validcount = validcount; + + v1 = line->v1; + v2 = line->v2; + s1 = P_DivlineSide(v1->x, v1->y, &strace); + s2 = P_DivlineSide(v2->x, v2->y, &strace); + + // line isn't crossed? + if (s1 == s2) + continue; + + divl.x = v1->x; + divl.y = v1->y; + divl.dx = v2->x - v1->x; + divl.dy = v2->y - v1->y; + s1 = P_DivlineSide(strace.x, strace.y, &divl); + s2 = P_DivlineSide(t2x, t2y, &divl); + + // line isn't crossed? + if (s1 == s2) + continue; + + // Backsector may be NULL if this is an "impassible + // glass" hack line. + + if (line->backsector == NULL) { + return false; + } + + // stop because it is not two sided anyway + // might do this after updating validcount? + if (!(line->flags & ML_TWOSIDED)) + return false; + + // crosses a two sided line + front = seg->frontsector; + back = seg->backsector; + + // no wall to block sight with? + if (front->floorheight == back->floorheight && + front->ceilingheight == back->ceilingheight) + continue; + + // possible occluder + // because of ceiling height differences + if (front->ceilingheight < back->ceilingheight) + opentop = front->ceilingheight; + else + opentop = back->ceilingheight; + + // because of ceiling height differences + if (front->floorheight > back->floorheight) + openbottom = front->floorheight; + else + openbottom = back->floorheight; + + // quick test for totally closed doors + if (openbottom >= opentop) + return false; // stop + + frac = P_InterceptVector2(&strace, &divl); + + if (front->floorheight != back->floorheight) { + slope = FixedDiv(openbottom - sightzstart, frac); + if (slope > bottomslope) + bottomslope = slope; + } + + if (front->ceilingheight != back->ceilingheight) { + slope = FixedDiv(opentop - sightzstart, frac); + if (slope < topslope) + topslope = slope; + } + + if (topslope <= bottomslope) + return false; // stop + } + // passed the subsector ok + return true; +} + +// +// P_CrossBSPNode +// Returns true +// if strace crosses the given node successfully. +// +boolean P_CrossBSPNode(int bspnum) { + node_t *bsp; + int side; + + if (bspnum & NF_SUBSECTOR) { + if (bspnum == -1) + return P_CrossSubsector(0); + else + return P_CrossSubsector(bspnum & (~NF_SUBSECTOR)); + } + + bsp = &nodes[bspnum]; + + // decide which side the start point is on + side = P_DivlineSide(strace.x, strace.y, (divline_t *)bsp); + if (side == 2) + side = 0; // an "on" should cross both sides + + // cross the starting side + if (!P_CrossBSPNode(bsp->children[side])) + return false; + + // the partition plane is crossed here + if (side == P_DivlineSide(t2x, t2y, (divline_t *)bsp)) { + // the line doesn't touch the other side + return true; + } + + // cross the ending side + return P_CrossBSPNode(bsp->children[side ^ 1]); +} + +// +// P_CheckSight +// Returns true +// if a straight line between t1 and t2 is unobstructed. +// Uses REJECT. +// +boolean P_CheckSight(mobj_t *t1, mobj_t *t2) { + int s1; + int s2; + int pnum; + int bytenum; + int bitnum; + + // First check for trivial rejection. + + // Determine subsector entries in REJECT table. + s1 = (t1->subsector->sector - sectors); + s2 = (t2->subsector->sector - sectors); + pnum = s1 * numsectors + s2; + bytenum = pnum >> 3; + bitnum = 1 << (pnum & 7); + + // Check in REJECT table. + if (rejectmatrix[bytenum] & bitnum) { + sightcounts[0]++; + + // can't possibly be connected + return false; + } + + // An unobstructed LOS is possible. + // Now look from eyes of t1 to any part of t2. + sightcounts[1]++; + + validcount++; + + sightzstart = t1->z + t1->height - (t1->height >> 2); + topslope = (t2->z + t2->height) - sightzstart; + bottomslope = (t2->z) - sightzstart; + + strace.x = t1->x; + strace.y = t1->y; + t2x = t2->x; + t2y = t2->y; + strace.dx = t2->x - t1->x; + strace.dy = t2->y - t1->y; + + // the head node is the last node output + return P_CrossBSPNode(numnodes - 1); +} diff --git a/client/src/p_spec.c b/client/src/p_spec.c new file mode 100644 index 0000000..8153e94 --- /dev/null +++ b/client/src/p_spec.c @@ -0,0 +1,1335 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Implements special effects: +// Texture animation, height or lighting changes +// according to adjacent sectors, respective +// utility functions, etc. +// Line Tag handling. Line and Sector triggers. +// + +#include +#include +#include + +#include "d_player.h" +#include "d_think.h" +#include "doomdata.h" +#include "doomdef.h" +#include "doomstat.h" +#include "doomtype.h" +#include "g_game.h" +#include "i_system.h" +#include "i_timer.h" +#include "info.h" +#include "m_argv.h" +#include "m_fixed.h" +#include "m_misc.h" +#include "m_random.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_spec.h" +#include "r_data.h" +#include "r_defs.h" +// State. +#include "r_state.h" +#include "s_sound.h" +// Data. +#include "sounds.h" +#include "w_wad.h" +#include "z_zone.h" + +// +// Animating textures and planes +// There is another anim_t used in wi_stuff, unrelated. +// +typedef struct { + boolean istexture; + int picnum; + int basepic; + int numpics; + int speed; + +} anim_t; + +// +// source animation definition +// +typedef struct { + int istexture; // if false, it is a flat + char endname[9]; + char startname[9]; + int speed; +} animdef_t; + +#define MAXANIMS 32 + +extern anim_t anims[MAXANIMS]; +extern anim_t *lastanim; + +// +// P_InitPicAnims +// + +// Floor/ceiling animation sequences, +// defined by first and last frame, +// i.e. the flat (64x64 tile) name to +// be used. +// The full animation sequence is given +// using all the flats between the start +// and end entry, in the order found in +// the WAD file. +// +animdef_t animdefs[] = { + {false, "NUKAGE3", "NUKAGE1", 8}, + {false, "FWATER4", "FWATER1", 8}, + {false, "SWATER4", "SWATER1", 8}, + {false, "LAVA4", "LAVA1", 8}, + {false, "BLOOD3", "BLOOD1", 8}, + + // DOOM II flat animations. + {false, "RROCK08", "RROCK05", 8}, + {false, "SLIME04", "SLIME01", 8}, + {false, "SLIME08", "SLIME05", 8}, + {false, "SLIME12", "SLIME09", 8}, + + {true, "BLODGR4", "BLODGR1", 8}, + {true, "SLADRIP3", "SLADRIP1", 8}, + + {true, "BLODRIP4", "BLODRIP1", 8}, + {true, "FIREWALL", "FIREWALA", 8}, + {true, "GSTFONT3", "GSTFONT1", 8}, + {true, "FIRELAVA", "FIRELAV3", 8}, + {true, "FIREMAG3", "FIREMAG1", 8}, + {true, "FIREBLU2", "FIREBLU1", 8}, + {true, "ROCKRED3", "ROCKRED1", 8}, + + {true, "BFALL4", "BFALL1", 8}, + {true, "SFALL4", "SFALL1", 8}, + {true, "WFALL4", "WFALL1", 8}, + {true, "DBRAIN4", "DBRAIN1", 8}, + + {-1, "", "", 0}, +}; + +anim_t anims[MAXANIMS]; +anim_t *lastanim; + +// +// Animating line specials +// +#define MAXLINEANIMS 64 + +extern short numlinespecials; +extern line_t *linespeciallist[MAXLINEANIMS]; + +void P_InitPicAnims(void) { + int i; + + // Init animation + lastanim = anims; + for (i = 0; animdefs[i].istexture != -1; i++) { + char *startname, *endname; + + startname = (animdefs[i].startname); + endname = (animdefs[i].endname); + + if (animdefs[i].istexture) { + // different episode ? + if (R_CheckTextureNumForName(startname) == -1) + continue; + + lastanim->picnum = R_TextureNumForName(endname); + lastanim->basepic = R_TextureNumForName(startname); + } else { + if (W_CheckNumForName(startname) == -1) + continue; + + lastanim->picnum = R_FlatNumForName(endname); + lastanim->basepic = R_FlatNumForName(startname); + } + + lastanim->istexture = animdefs[i].istexture; + lastanim->numpics = lastanim->picnum - lastanim->basepic + 1; + + if (lastanim->numpics < 2) + I_Error("P_InitPicAnims: bad cycle from %s to %s", startname, endname); + + lastanim->speed = animdefs[i].speed; + lastanim++; + } +} + +// +// UTILITIES +// + +// +// getSide() +// Will return a side_t* +// given the number of the current sector, +// the line number, and the side (0/1) that you want. +// +side_t *getSide(int currentSector, int line, int side) { + return &sides[(sectors[currentSector].lines[line])->sidenum[side]]; +} + +// +// getSector() +// Will return a sector_t* +// given the number of the current sector, +// the line number and the side (0/1) that you want. +// +sector_t *getSector(int currentSector, int line, int side) { + return sides[(sectors[currentSector].lines[line])->sidenum[side]].sector; +} + +// +// twoSided() +// Given the sector number and the line number, +// it will tell you whether the line is two-sided or not. +// +int twoSided(int sector, int line) { + return (sectors[sector].lines[line])->flags & ML_TWOSIDED; +} + +// +// getNextSector() +// Return sector_t * of sector next to current. +// NULL if not two-sided line +// +sector_t *getNextSector(line_t *line, sector_t *sec) { + if (!(line->flags & ML_TWOSIDED)) + return NULL; + + if (line->frontsector == sec) + return line->backsector; + + return line->frontsector; +} + +// +// P_FindLowestFloorSurrounding() +// FIND LOWEST FLOOR HEIGHT IN SURROUNDING SECTORS +// +fixed_t P_FindLowestFloorSurrounding(sector_t *sec) { + int i; + line_t *check; + sector_t *other; + fixed_t floor = sec->floorheight; + + for (i = 0; i < sec->linecount; i++) { + check = sec->lines[i]; + other = getNextSector(check, sec); + + if (!other) + continue; + + if (other->floorheight < floor) + floor = other->floorheight; + } + return floor; +} + +// +// P_FindHighestFloorSurrounding() +// FIND HIGHEST FLOOR HEIGHT IN SURROUNDING SECTORS +// +fixed_t P_FindHighestFloorSurrounding(sector_t *sec) { + int i; + line_t *check; + sector_t *other; + fixed_t floor = -500 * FRACUNIT; + + for (i = 0; i < sec->linecount; i++) { + check = sec->lines[i]; + other = getNextSector(check, sec); + + if (!other) + continue; + + if (other->floorheight > floor) + floor = other->floorheight; + } + return floor; +} + +// +// P_FindNextHighestFloor +// FIND NEXT HIGHEST FLOOR IN SURROUNDING SECTORS +// Note: this should be doable w/o a fixed array. + +// Thanks to entryway for the Vanilla overflow emulation. + +// 20 adjoining sectors max! +#define MAX_ADJOINING_SECTORS 20 + +fixed_t P_FindNextHighestFloor(sector_t *sec, int currentheight) { + int i; + int h; + int min; + line_t *check; + sector_t *other; + fixed_t height = currentheight; + fixed_t heightlist[MAX_ADJOINING_SECTORS + 2]; + + for (i = 0, h = 0; i < sec->linecount; i++) { + check = sec->lines[i]; + other = getNextSector(check, sec); + + if (!other) + continue; + + if (other->floorheight > height) { + // Emulation of memory (stack) overflow + if (h == MAX_ADJOINING_SECTORS + 1) { + height = other->floorheight; + } else if (h == MAX_ADJOINING_SECTORS + 2) { + // Fatal overflow: game crashes at 22 sectors + I_Error("Sector with more than 22 adjoining sectors. " + "Vanilla will crash here"); + } + + heightlist[h++] = other->floorheight; + } + } + + // Find lowest height in list + if (!h) { + return currentheight; + } + + min = heightlist[0]; + + // Range checking? + for (i = 1; i < h; i++) { + if (heightlist[i] < min) { + min = heightlist[i]; + } + } + + return min; +} + +// +// FIND LOWEST CEILING IN THE SURROUNDING SECTORS +// +fixed_t P_FindLowestCeilingSurrounding(sector_t *sec) { + int i; + line_t *check; + sector_t *other; + fixed_t height = INT_MAX; + + for (i = 0; i < sec->linecount; i++) { + check = sec->lines[i]; + other = getNextSector(check, sec); + + if (!other) + continue; + + if (other->ceilingheight < height) + height = other->ceilingheight; + } + return height; +} + +// +// FIND HIGHEST CEILING IN THE SURROUNDING SECTORS +// +fixed_t P_FindHighestCeilingSurrounding(sector_t *sec) { + int i; + line_t *check; + sector_t *other; + fixed_t height = 0; + + for (i = 0; i < sec->linecount; i++) { + check = sec->lines[i]; + other = getNextSector(check, sec); + + if (!other) + continue; + + if (other->ceilingheight > height) + height = other->ceilingheight; + } + return height; +} + +// +// RETURN NEXT SECTOR # THAT LINE TAG REFERS TO +// +int P_FindSectorFromLineTag(line_t *line, int start) { + int i; + + for (i = start + 1; i < numsectors; i++) + if (sectors[i].tag == line->tag) + return i; + + return -1; +} + +// +// Find minimum light from an adjacent sector +// +int P_FindMinSurroundingLight(sector_t *sector, int max) { + int i; + int min; + line_t *line; + sector_t *check; + + min = max; + for (i = 0; i < sector->linecount; i++) { + line = sector->lines[i]; + check = getNextSector(line, sector); + + if (!check) + continue; + + if (check->lightlevel < min) + min = check->lightlevel; + } + return min; +} + +// +// EVENTS +// Events are operations triggered by using, crossing, +// or shooting special lines, or by timed thinkers. +// + +// +// P_CrossSpecialLine - TRIGGER +// Called every time a thing origin is about +// to cross a line with a non 0 special. +// +void P_CrossSpecialLine(int linenum, int side, mobj_t *thing) { + line_t *line; + int ok; + + line = &lines[linenum]; + + // Triggers that other things can activate + if (!thing->player) { + // Things that should NOT trigger specials... + switch (thing->type) { + case MT_ROCKET: + case MT_PLASMA: + case MT_BFG: + case MT_TROOPSHOT: + case MT_HEADSHOT: + case MT_BRUISERSHOT: + return; + break; + + default: + break; + } + + ok = 0; + switch (line->special) { + case 39: // TELEPORT TRIGGER + case 97: // TELEPORT RETRIGGER + case 125: // TELEPORT MONSTERONLY TRIGGER + case 126: // TELEPORT MONSTERONLY RETRIGGER + case 4: // RAISE DOOR + case 10: // PLAT DOWN-WAIT-UP-STAY TRIGGER + case 88: // PLAT DOWN-WAIT-UP-STAY RETRIGGER + ok = 1; + break; + } + if (!ok) + return; + } + + // Note: could use some const's here. + switch (line->special) { + // TRIGGERS. + // All from here to RETRIGGERS. + case 2: + // Open Door + EV_DoDoor(line, vld_open); + line->special = 0; + break; + + case 3: + // Close Door + EV_DoDoor(line, vld_close); + line->special = 0; + break; + + case 4: + // Raise Door + EV_DoDoor(line, vld_normal); + line->special = 0; + break; + + case 5: + // Raise Floor + EV_DoFloor(line, raiseFloor); + line->special = 0; + break; + + case 6: + // Fast Ceiling Crush & Raise + EV_DoCeiling(line, fastCrushAndRaise); + line->special = 0; + break; + + case 8: + // Build Stairs + EV_BuildStairs(line, build8); + line->special = 0; + break; + + case 10: + // PlatDownWaitUp + EV_DoPlat(line, downWaitUpStay, 0); + line->special = 0; + break; + + case 12: + // Light Turn On - brightest near + EV_LightTurnOn(line, 0); + line->special = 0; + break; + + case 13: + // Light Turn On 255 + EV_LightTurnOn(line, 255); + line->special = 0; + break; + + case 16: + // Close Door 30 + EV_DoDoor(line, vld_close30ThenOpen); + line->special = 0; + break; + + case 17: + // Start Light Strobing + EV_StartLightStrobing(line); + line->special = 0; + break; + + case 19: + // Lower Floor + EV_DoFloor(line, lowerFloor); + line->special = 0; + break; + + case 22: + // Raise floor to nearest height and change texture + EV_DoPlat(line, raiseToNearestAndChange, 0); + line->special = 0; + break; + + case 25: + // Ceiling Crush and Raise + EV_DoCeiling(line, crushAndRaise); + line->special = 0; + break; + + case 30: + // Raise floor to shortest texture height + // on either side of lines. + EV_DoFloor(line, raiseToTexture); + line->special = 0; + break; + + case 35: + // Lights Very Dark + EV_LightTurnOn(line, 35); + line->special = 0; + break; + + case 36: + // Lower Floor (TURBO) + EV_DoFloor(line, turboLower); + line->special = 0; + break; + + case 37: + // LowerAndChange + EV_DoFloor(line, lowerAndChange); + line->special = 0; + break; + + case 38: + // Lower Floor To Lowest + EV_DoFloor(line, lowerFloorToLowest); + line->special = 0; + break; + + case 39: + // TELEPORT! + EV_Teleport(line, side, thing); + line->special = 0; + break; + + case 40: + // RaiseCeilingLowerFloor + EV_DoCeiling(line, raiseToHighest); + EV_DoFloor(line, lowerFloorToLowest); + line->special = 0; + break; + + case 44: + // Ceiling Crush + EV_DoCeiling(line, lowerAndCrush); + line->special = 0; + break; + + case 52: + // EXIT! + G_ExitLevel(); + break; + + case 53: + // Perpetual Platform Raise + EV_DoPlat(line, perpetualRaise, 0); + line->special = 0; + break; + + case 54: + // Platform Stop + EV_StopPlat(line); + line->special = 0; + break; + + case 56: + // Raise Floor Crush + EV_DoFloor(line, raiseFloorCrush); + line->special = 0; + break; + + case 57: + // Ceiling Crush Stop + EV_CeilingCrushStop(line); + line->special = 0; + break; + + case 58: + // Raise Floor 24 + EV_DoFloor(line, raiseFloor24); + line->special = 0; + break; + + case 59: + // Raise Floor 24 And Change + EV_DoFloor(line, raiseFloor24AndChange); + line->special = 0; + break; + + case 104: + // Turn lights off in sector(tag) + EV_TurnTagLightsOff(line); + line->special = 0; + break; + + case 108: + // Blazing Door Raise (faster than TURBO!) + EV_DoDoor(line, vld_blazeRaise); + line->special = 0; + break; + + case 109: + // Blazing Door Open (faster than TURBO!) + EV_DoDoor(line, vld_blazeOpen); + line->special = 0; + break; + + case 100: + // Build Stairs Turbo 16 + EV_BuildStairs(line, turbo16); + line->special = 0; + break; + + case 110: + // Blazing Door Close (faster than TURBO!) + EV_DoDoor(line, vld_blazeClose); + line->special = 0; + break; + + case 119: + // Raise floor to nearest surr. floor + EV_DoFloor(line, raiseFloorToNearest); + line->special = 0; + break; + + case 121: + // Blazing PlatDownWaitUpStay + EV_DoPlat(line, blazeDWUS, 0); + line->special = 0; + break; + + case 124: + // Secret EXIT + G_SecretExitLevel(); + break; + + case 125: + // TELEPORT MonsterONLY + if (!thing->player) { + EV_Teleport(line, side, thing); + line->special = 0; + } + break; + + case 130: + // Raise Floor Turbo + EV_DoFloor(line, raiseFloorTurbo); + line->special = 0; + break; + + case 141: + // Silent Ceiling Crush & Raise + EV_DoCeiling(line, silentCrushAndRaise); + line->special = 0; + break; + + // RETRIGGERS. All from here till end. + case 72: + // Ceiling Crush + EV_DoCeiling(line, lowerAndCrush); + break; + + case 73: + // Ceiling Crush and Raise + EV_DoCeiling(line, crushAndRaise); + break; + + case 74: + // Ceiling Crush Stop + EV_CeilingCrushStop(line); + break; + + case 75: + // Close Door + EV_DoDoor(line, vld_close); + break; + + case 76: + // Close Door 30 + EV_DoDoor(line, vld_close30ThenOpen); + break; + + case 77: + // Fast Ceiling Crush & Raise + EV_DoCeiling(line, fastCrushAndRaise); + break; + + case 79: + // Lights Very Dark + EV_LightTurnOn(line, 35); + break; + + case 80: + // Light Turn On - brightest near + EV_LightTurnOn(line, 0); + break; + + case 81: + // Light Turn On 255 + EV_LightTurnOn(line, 255); + break; + + case 82: + // Lower Floor To Lowest + EV_DoFloor(line, lowerFloorToLowest); + break; + + case 83: + // Lower Floor + EV_DoFloor(line, lowerFloor); + break; + + case 84: + // LowerAndChange + EV_DoFloor(line, lowerAndChange); + break; + + case 86: + // Open Door + EV_DoDoor(line, vld_open); + break; + + case 87: + // Perpetual Platform Raise + EV_DoPlat(line, perpetualRaise, 0); + break; + + case 88: + // PlatDownWaitUp + EV_DoPlat(line, downWaitUpStay, 0); + break; + + case 89: + // Platform Stop + EV_StopPlat(line); + break; + + case 90: + // Raise Door + EV_DoDoor(line, vld_normal); + break; + + case 91: + // Raise Floor + EV_DoFloor(line, raiseFloor); + break; + + case 92: + // Raise Floor 24 + EV_DoFloor(line, raiseFloor24); + break; + + case 93: + // Raise Floor 24 And Change + EV_DoFloor(line, raiseFloor24AndChange); + break; + + case 94: + // Raise Floor Crush + EV_DoFloor(line, raiseFloorCrush); + break; + + case 95: + // Raise floor to nearest height + // and change texture. + EV_DoPlat(line, raiseToNearestAndChange, 0); + break; + + case 96: + // Raise floor to shortest texture height + // on either side of lines. + EV_DoFloor(line, raiseToTexture); + break; + + case 97: + // TELEPORT! + EV_Teleport(line, side, thing); + break; + + case 98: + // Lower Floor (TURBO) + EV_DoFloor(line, turboLower); + break; + + case 105: + // Blazing Door Raise (faster than TURBO!) + EV_DoDoor(line, vld_blazeRaise); + break; + + case 106: + // Blazing Door Open (faster than TURBO!) + EV_DoDoor(line, vld_blazeOpen); + break; + + case 107: + // Blazing Door Close (faster than TURBO!) + EV_DoDoor(line, vld_blazeClose); + break; + + case 120: + // Blazing PlatDownWaitUpStay. + EV_DoPlat(line, blazeDWUS, 0); + break; + + case 126: + // TELEPORT MonsterONLY. + if (!thing->player) + EV_Teleport(line, side, thing); + break; + + case 128: + // Raise To Nearest Floor + EV_DoFloor(line, raiseFloorToNearest); + break; + + case 129: + // Raise Floor Turbo + EV_DoFloor(line, raiseFloorTurbo); + break; + } +} + +// +// P_ShootSpecialLine - IMPACT SPECIALS +// Called when a thing shoots a special line. +// +void P_ShootSpecialLine(mobj_t *thing, line_t *line) { + int ok; + + // Impacts that other things can activate. + if (!thing->player) { + ok = 0; + switch (line->special) { + case 46: + // OPEN DOOR IMPACT + ok = 1; + break; + } + if (!ok) + return; + } + + switch (line->special) { + case 24: + // RAISE FLOOR + EV_DoFloor(line, raiseFloor); + P_ChangeSwitchTexture(line, 0); + break; + + case 46: + // OPEN DOOR + EV_DoDoor(line, vld_open); + P_ChangeSwitchTexture(line, 1); + break; + + case 47: + // RAISE FLOOR NEAR AND CHANGE + EV_DoPlat(line, raiseToNearestAndChange, 0); + P_ChangeSwitchTexture(line, 0); + break; + } +} + +// +// P_PlayerInSpecialSector +// Called every tic frame +// that the player origin is in a special sector +// +void P_PlayerInSpecialSector(player_t *player) { + sector_t *sector; + + sector = player->mo->subsector->sector; + + // Falling, not all the way down yet? + if (player->mo->z != sector->floorheight) + return; + + // Has hitten ground. + switch (sector->special) { + case 5: + // HELLSLIME DAMAGE + if (!player->powers[pw_ironfeet]) + if (!(leveltime & 0x1f)) + P_DamageMobj(player->mo, NULL, NULL, 10); + break; + + case 7: + // NUKAGE DAMAGE + if (!player->powers[pw_ironfeet]) + if (!(leveltime & 0x1f)) + P_DamageMobj(player->mo, NULL, NULL, 5); + break; + + case 16: + // SUPER HELLSLIME DAMAGE + case 4: + // STROBE HURT + if (!player->powers[pw_ironfeet] || (P_Random() < 5)) { + if (!(leveltime & 0x1f)) + P_DamageMobj(player->mo, NULL, NULL, 20); + } + break; + + case 9: + // SECRET SECTOR + player->secretcount++; + sector->special = 0; + break; + + case 11: + // EXIT SUPER DAMAGE! (for E1M8 finale) + player->cheats &= ~CF_GODMODE; + + if (!(leveltime & 0x1f)) + P_DamageMobj(player->mo, NULL, NULL, 20); + + if (player->health <= 10) + G_ExitLevel(); + break; + + default: + I_Error("P_PlayerInSpecialSector: " + "unknown special %i", + sector->special); + break; + }; +} + +// +// P_UpdateSpecials +// Animate planes, scroll walls, etc. +// +boolean levelTimer; +int levelTimeCount; + +void P_UpdateSpecials(void) { + anim_t *anim; + int pic; + int i; + line_t *line; + + // LEVEL TIMER + if (levelTimer == true) { + levelTimeCount--; + if (!levelTimeCount) + G_ExitLevel(); + } + + // ANIMATE FLATS AND TEXTURES GLOBALLY + for (anim = anims; anim < lastanim; anim++) { + for (i = anim->basepic; i < anim->basepic + anim->numpics; i++) { + pic = anim->basepic + ((leveltime / anim->speed + i) % anim->numpics); + if (anim->istexture) + texturetranslation[i] = pic; + else + flattranslation[i] = pic; + } + } + + // ANIMATE LINE SPECIALS + for (i = 0; i < numlinespecials; i++) { + line = linespeciallist[i]; + switch (line->special) { + case 48: + // EFFECT FIRSTCOL SCROLL + + sides[line->sidenum[0]].textureoffset += FRACUNIT; + break; + } + } + + // DO BUTTONS + for (i = 0; i < MAXBUTTONS; i++) + if (buttonlist[i].btimer) { + buttonlist[i].btimer--; + if (!buttonlist[i].btimer) { + switch (buttonlist[i].where) { + case top: + sides[buttonlist[i].line->sidenum[0]].toptexture = + buttonlist[i].btexture; + break; + + case middle: + sides[buttonlist[i].line->sidenum[0]].midtexture = + buttonlist[i].btexture; + break; + + case bottom: + sides[buttonlist[i].line->sidenum[0]].bottomtexture = + buttonlist[i].btexture; + break; + } + S_StartSound(&buttonlist[i].soundorg, sfx_swtchn); + memset(&buttonlist[i], 0, sizeof(button_t)); + } + } +} + +// +// Donut overrun emulation +// +// Derived from the code from PrBoom+. Thanks go to Andrey Budko (entryway) +// as usual :-) +// + +#define DONUT_FLOORHEIGHT_DEFAULT 0x00000000 +#define DONUT_FLOORPIC_DEFAULT 0x16 + +static void DonutOverrun(fixed_t *s3_floorheight, short *s3_floorpic, + line_t *line, sector_t *pillar_sector) { + static int first = 1; + static int tmp_s3_floorheight; + static int tmp_s3_floorpic; + + extern int numflats; + + if (first) { + int p; + + // This is the first time we have had an overrun. + first = 0; + + // Default values + tmp_s3_floorheight = DONUT_FLOORHEIGHT_DEFAULT; + tmp_s3_floorpic = DONUT_FLOORPIC_DEFAULT; + + //! + // @category compat + // @arg + // + // Use the specified magic values when emulating behavior caused + // by memory overruns from improperly constructed donuts. + // In Vanilla Doom this can differ depending on the operating + // system. The default (if this option is not specified) is to + // emulate the behavior when running under Windows 98. + + p = M_CheckParmWithArgs("-donut", 2); + + if (p > 0) { + // Dump of needed memory: (fixed_t)0000:0000 and (short)0000:0008 + // + // C:\>debug + // -d 0:0 + // + // DOS 6.22: + // 0000:0000 (57 92 19 00) F4 06 70 00-(16 00) + // DOS 7.1: + // 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00) + // Win98: + // 0000:0000 (00 00 00 00) 65 04 70 00-(16 00) + // DOSBox under XP: + // 0000:0000 (00 00 00 F1) ?? ?? ?? 00-(07 00) + + M_StrToInt(myargv[p + 1], &tmp_s3_floorheight); + M_StrToInt(myargv[p + 2], &tmp_s3_floorpic); + + if (tmp_s3_floorpic >= numflats) { + fprintf(stderr, "DonutOverrun: The second parameter for \"-donut\" " + "switch should be greater than 0 and less than number " + "of flats (%d). Using default value (%d) instead. \n", + numflats, DONUT_FLOORPIC_DEFAULT); + tmp_s3_floorpic = DONUT_FLOORPIC_DEFAULT; + } + } + } + + /* + fprintf(stderr, + "Linedef: %d; Sector: %d; " + "New floor height: %d; New floor pic: %d\n", + line->iLineID, pillar_sector->iSectorID, + tmp_s3_floorheight >> 16, tmp_s3_floorpic); + */ + + *s3_floorheight = (fixed_t)tmp_s3_floorheight; + *s3_floorpic = (short)tmp_s3_floorpic; +} + +// +// Special Stuff that can not be categorized +// +int EV_DoDonut(line_t *line) { + sector_t *s1; + sector_t *s2; + sector_t *s3; + int secnum; + int rtn; + int i; + floormove_t *floor; + fixed_t s3_floorheight; + short s3_floorpic; + + secnum = -1; + rtn = 0; + while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0) { + s1 = §ors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (s1->specialdata) + continue; + + rtn = 1; + s2 = getNextSector(s1->lines[0], s1); + + // Vanilla Doom does not check if the linedef is one sided. The + // game does not crash, but reads invalid memory and causes the + // sector floor to move "down" to some unknown height. + // DOSbox prints a warning about an invalid memory access. + // + // I'm not sure exactly what invalid memory is being read. This + // isn't something that should be done, anyway. + // Just print a warning and return. + + if (s2 == NULL) { + fprintf(stderr, "EV_DoDonut: linedef had no second sidedef! " + "Unexpected behavior may occur in Vanilla Doom. \n"); + break; + } + + for (i = 0; i < s2->linecount; i++) { + s3 = s2->lines[i]->backsector; + + if (s3 == s1) + continue; + + if (s3 == NULL) { + // e6y + // s3 is NULL, so + // s3->floorheight is an int at 0000:0000 + // s3->floorpic is a short at 0000:0008 + // Trying to emulate + + fprintf(stderr, "EV_DoDonut: WARNING: emulating buffer overrun due to " + "NULL back sector. " + "Unexpected behavior may occur in Vanilla Doom.\n"); + + DonutOverrun(&s3_floorheight, &s3_floorpic, line, s1); + } else { + s3_floorheight = s3->floorheight; + s3_floorpic = s3->floorpic; + } + + // Spawn rising slime + floor = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0); + P_AddThinker(&floor->thinker); + s2->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor; + floor->type = donutRaise; + floor->crush = false; + floor->direction = 1; + floor->sector = s2; + floor->speed = FLOORSPEED / 2; + floor->texture = s3_floorpic; + floor->newspecial = 0; + floor->floordestheight = s3_floorheight; + + // Spawn lowering donut-hole + floor = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0); + P_AddThinker(&floor->thinker); + s1->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor; + floor->type = lowerFloor; + floor->crush = false; + floor->direction = -1; + floor->sector = s1; + floor->speed = FLOORSPEED / 2; + floor->floordestheight = s3_floorheight; + break; + } + } + return rtn; +} + +// +// SPECIAL SPAWNING +// + +// +// P_SpawnSpecials +// After the map has been loaded, scan for specials +// that spawn thinkers +// +short numlinespecials; +line_t *linespeciallist[MAXLINEANIMS]; + +// Parses command line parameters. +void P_SpawnSpecials(void) { + sector_t *sector; + int i; + + // See if -TIMER was specified. + + if (timelimit > 0 && deathmatch) { + levelTimer = true; + levelTimeCount = timelimit * 60 * TICRATE; + } else { + levelTimer = false; + } + + // Init special SECTORs. + sector = sectors; + for (i = 0; i < numsectors; i++, sector++) { + if (!sector->special) + continue; + + switch (sector->special) { + case 1: + // FLICKERING LIGHTS + P_SpawnLightFlash(sector); + break; + + case 2: + // STROBE FAST + P_SpawnStrobeFlash(sector, FASTDARK, 0); + break; + + case 3: + // STROBE SLOW + P_SpawnStrobeFlash(sector, SLOWDARK, 0); + break; + + case 4: + // STROBE FAST/DEATH SLIME + P_SpawnStrobeFlash(sector, FASTDARK, 0); + sector->special = 4; + break; + + case 8: + // GLOWING LIGHT + P_SpawnGlowingLight(sector); + break; + case 9: + // SECRET SECTOR + totalsecret++; + break; + + case 10: + // DOOR CLOSE IN 30 SECONDS + P_SpawnDoorCloseIn30(sector); + break; + + case 12: + // SYNC STROBE SLOW + P_SpawnStrobeFlash(sector, SLOWDARK, 1); + break; + + case 13: + // SYNC STROBE FAST + P_SpawnStrobeFlash(sector, FASTDARK, 1); + break; + + case 14: + // DOOR RAISE IN 5 MINUTES + P_SpawnDoorRaiseIn5Mins(sector, i); + break; + + case 17: + P_SpawnFireFlicker(sector); + break; + } + } + + // Init line EFFECTs + numlinespecials = 0; + for (i = 0; i < numlines; i++) { + switch (lines[i].special) { + case 48: + if (numlinespecials >= MAXLINEANIMS) { + I_Error("Too many scrolling wall linedefs! " + "(Vanilla limit is 64)"); + } + // EFFECT FIRSTCOL SCROLL+ + linespeciallist[numlinespecials] = &lines[i]; + numlinespecials++; + break; + } + } + + // Init other misc stuff + for (i = 0; i < MAXCEILINGS; i++) + activeceilings[i] = NULL; + + for (i = 0; i < MAXPLATS; i++) + activeplats[i] = NULL; + + for (i = 0; i < MAXBUTTONS; i++) + memset(&buttonlist[i], 0, sizeof(button_t)); + + // UNUSED: no horizonal sliders. + // P_InitSlidingDoorFrames(); +} diff --git a/client/src/p_spec.h b/client/src/p_spec.h new file mode 100644 index 0000000..57c4f2d --- /dev/null +++ b/client/src/p_spec.h @@ -0,0 +1,492 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: none +// Implements special effects: +// Texture animation, height or lighting changes +// according to adjacent sectors, respective +// utility functions, etc. +// + +#ifndef __P_SPEC__ +#define __P_SPEC__ + +#include "d_player.h" +#include "d_think.h" +#include "doomtype.h" +#include "m_fixed.h" +#include "p_mobj.h" +#include "r_defs.h" + +// +// End-level timer (-TIMER option) +// +extern boolean levelTimer; +extern int levelTimeCount; + +// Define values for map objects +#define MO_TELEPORTMAN 14 + +// at game start +void P_InitPicAnims(void); + +// at map load +void P_SpawnSpecials(void); + +// every tic +void P_UpdateSpecials(void); + +// when needed +boolean P_UseSpecialLine(mobj_t *thing, line_t *line, int side); + +void P_ShootSpecialLine(mobj_t *thing, line_t *line); + +void P_CrossSpecialLine(int linenum, int side, mobj_t *thing); + +void P_PlayerInSpecialSector(player_t *player); + +int twoSided(int sector, int line); + +sector_t *getSector(int currentSector, int line, int side); + +side_t *getSide(int currentSector, int line, int side); + +fixed_t P_FindLowestFloorSurrounding(sector_t *sec); +fixed_t P_FindHighestFloorSurrounding(sector_t *sec); + +fixed_t P_FindNextHighestFloor(sector_t *sec, int currentheight); + +fixed_t P_FindLowestCeilingSurrounding(sector_t *sec); +fixed_t P_FindHighestCeilingSurrounding(sector_t *sec); + +int P_FindSectorFromLineTag(line_t *line, int start); + +int P_FindMinSurroundingLight(sector_t *sector, int max); + +sector_t *getNextSector(line_t *line, sector_t *sec); + +// +// SPECIAL +// +int EV_DoDonut(line_t *line); + +// +// P_LIGHTS +// +typedef struct { + thinker_t thinker; + sector_t *sector; + int count; + int maxlight; + int minlight; + +} fireflicker_t; + +typedef struct { + thinker_t thinker; + sector_t *sector; + int count; + int maxlight; + int minlight; + int maxtime; + int mintime; + +} lightflash_t; + +typedef struct { + thinker_t thinker; + sector_t *sector; + int count; + int minlight; + int maxlight; + int darktime; + int brighttime; + +} strobe_t; + +typedef struct { + thinker_t thinker; + sector_t *sector; + int minlight; + int maxlight; + int direction; + +} glow_t; + +#define GLOWSPEED 8 +#define STROBEBRIGHT 5 +#define FASTDARK 15 +#define SLOWDARK 35 + +void P_SpawnFireFlicker(sector_t *sector); +void T_LightFlash(lightflash_t *flash); +void P_SpawnLightFlash(sector_t *sector); +void T_StrobeFlash(strobe_t *flash); + +void P_SpawnStrobeFlash(sector_t *sector, int fastOrSlow, int inSync); + +void EV_StartLightStrobing(line_t *line); +void EV_TurnTagLightsOff(line_t *line); + +void EV_LightTurnOn(line_t *line, int bright); + +void T_Glow(glow_t *g); +void P_SpawnGlowingLight(sector_t *sector); + +// +// P_SWITCH +// +typedef struct { + char name1[9]; + char name2[9]; + short episode; + +} switchlist_t; + +typedef enum { + top, + middle, + bottom + +} bwhere_e; + +typedef struct { + line_t *line; + bwhere_e where; + int btexture; + int btimer; + degenmobj_t *soundorg; + +} button_t; + +// max # of wall switches in a level +#define MAXSWITCHES 50 + +// 4 players, 4 buttons each at once, max. +#define MAXBUTTONS 16 + +// 1 second, in ticks. +#define BUTTONTIME 35 + +extern button_t buttonlist[MAXBUTTONS]; + +void P_ChangeSwitchTexture(line_t *line, int useAgain); + +void P_InitSwitchList(void); + +// +// P_PLATS +// +typedef enum { + up, + down, + waiting, + in_stasis + +} plat_e; + +typedef enum { + perpetualRaise, + downWaitUpStay, + raiseAndChange, + raiseToNearestAndChange, + blazeDWUS + +} plattype_e; + +typedef struct { + thinker_t thinker; + sector_t *sector; + fixed_t speed; + fixed_t low; + fixed_t high; + int wait; + int count; + plat_e status; + plat_e oldstatus; + boolean crush; + int tag; + plattype_e type; + +} plat_t; + +#define PLATWAIT 3 +#define PLATSPEED FRACUNIT +#define MAXPLATS 30 + +extern plat_t *activeplats[MAXPLATS]; + +void T_PlatRaise(plat_t *plat); + +int EV_DoPlat(line_t *line, plattype_e type, int amount); + +void P_AddActivePlat(plat_t *plat); +void P_RemoveActivePlat(plat_t *plat); +void EV_StopPlat(line_t *line); +void P_ActivateInStasis(int tag); + +// +// P_DOORS +// +typedef enum { + vld_normal, + vld_close30ThenOpen, + vld_close, + vld_open, + vld_raiseIn5Mins, + vld_blazeRaise, + vld_blazeOpen, + vld_blazeClose + +} vldoor_e; + +typedef struct { + thinker_t thinker; + vldoor_e type; + sector_t *sector; + fixed_t topheight; + fixed_t speed; + + // 1 = up, 0 = waiting at top, -1 = down + int direction; + + // tics to wait at the top + int topwait; + // (keep in case a door going down is reset) + // when it reaches 0, start going down + int topcountdown; + +} vldoor_t; + +#define VDOORSPEED FRACUNIT * 2 +#define VDOORWAIT 150 + +void EV_VerticalDoor(line_t *line, mobj_t *thing); + +int EV_DoDoor(line_t *line, vldoor_e type); + +int EV_DoLockedDoor(line_t *line, vldoor_e type, mobj_t *thing); + +void T_VerticalDoor(vldoor_t *door); +void P_SpawnDoorCloseIn30(sector_t *sec); + +void P_SpawnDoorRaiseIn5Mins(sector_t *sec, int secnum); + +#if 0 // UNUSED +// +// Sliding doors... +// +typedef enum +{ + sd_opening, + sd_waiting, + sd_closing + +} sd_e; + + + +typedef enum +{ + sdt_openOnly, + sdt_closeOnly, + sdt_openAndClose + +} sdt_e; + + + + +typedef struct +{ + thinker_t thinker; + sdt_e type; + line_t* line; + int frame; + int whichDoorIndex; + int timer; + sector_t* frontsector; + sector_t* backsector; + sd_e status; + +} slidedoor_t; + + + +typedef struct +{ + char frontFrame1[9]; + char frontFrame2[9]; + char frontFrame3[9]; + char frontFrame4[9]; + char backFrame1[9]; + char backFrame2[9]; + char backFrame3[9]; + char backFrame4[9]; + +} slidename_t; + + + +typedef struct +{ + int frontFrames[4]; + int backFrames[4]; + +} slideframe_t; + + + +// how many frames of animation +#define SNUMFRAMES 4 + +#define SDOORWAIT 35 * 3 +#define SWAITTICS 4 + +// how many diff. types of anims +#define MAXSLIDEDOORS 5 + +void P_InitSlidingDoorFrames(void); + +void +EV_SlidingDoor +( line_t* line, + mobj_t* thing ); +#endif + +// +// P_CEILNG +// +typedef enum { + lowerToFloor, + raiseToHighest, + lowerAndCrush, + crushAndRaise, + fastCrushAndRaise, + silentCrushAndRaise + +} ceiling_e; + +typedef struct { + thinker_t thinker; + ceiling_e type; + sector_t *sector; + fixed_t bottomheight; + fixed_t topheight; + fixed_t speed; + boolean crush; + + // 1 = up, 0 = waiting, -1 = down + int direction; + + // ID + int tag; + int olddirection; + +} ceiling_t; + +#define CEILSPEED FRACUNIT +#define CEILWAIT 150 +#define MAXCEILINGS 30 + +extern ceiling_t *activeceilings[MAXCEILINGS]; + +int EV_DoCeiling(line_t *line, ceiling_e type); + +void T_MoveCeiling(ceiling_t *ceiling); +void P_AddActiveCeiling(ceiling_t *c); +void P_RemoveActiveCeiling(ceiling_t *c); +int EV_CeilingCrushStop(line_t *line); +void P_ActivateInStasisCeiling(line_t *line); + +// +// P_FLOOR +// +typedef enum { + // lower floor to highest surrounding floor + lowerFloor, + + // lower floor to lowest surrounding floor + lowerFloorToLowest, + + // lower floor to highest surrounding floor VERY FAST + turboLower, + + // raise floor to lowest surrounding CEILING + raiseFloor, + + // raise floor to next highest surrounding floor + raiseFloorToNearest, + + // raise floor to shortest height texture around it + raiseToTexture, + + // lower floor to lowest surrounding floor + // and change floorpic + lowerAndChange, + + raiseFloor24, + raiseFloor24AndChange, + raiseFloorCrush, + + // raise to next highest floor, turbo-speed + raiseFloorTurbo, + donutRaise, + raiseFloor512 + +} floor_e; + +typedef enum { + build8, // slowly build by 8 + turbo16 // quickly build by 16 + +} stair_e; + +typedef struct { + thinker_t thinker; + floor_e type; + boolean crush; + sector_t *sector; + int direction; + int newspecial; + short texture; + fixed_t floordestheight; + fixed_t speed; + +} floormove_t; + +#define FLOORSPEED FRACUNIT + +typedef enum { + ok, + crushed, + pastdest + +} result_e; + +result_e T_MovePlane(sector_t *sector, fixed_t speed, fixed_t dest, + boolean crush, int floorOrCeiling, int direction); + +int EV_BuildStairs(line_t *line, stair_e type); + +int EV_DoFloor(line_t *line, floor_e floortype); + +void T_MoveFloor(floormove_t *floor); + +// +// P_TELEPT +// +int EV_Teleport(line_t *line, int side, mobj_t *thing); + +#endif diff --git a/client/src/p_switch.c b/client/src/p_switch.c new file mode 100644 index 0000000..b495d35 --- /dev/null +++ b/client/src/p_switch.c @@ -0,0 +1,589 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: +// Switches, buttons. Two-state animation. Exits. +// + +#include "d_mode.h" +#include "doomdata.h" +// State. +#include "doomstat.h" +#include "doomtype.h" +#include "g_game.h" +#include "i_system.h" +#include "p_mobj.h" +#include "p_spec.h" +#include "r_data.h" +#include "r_defs.h" +#include "r_state.h" +#include "s_sound.h" +// Data. +#include "sounds.h" + +// +// CHANGE THE TEXTURE OF A WALL SWITCH TO ITS OPPOSITE +// +switchlist_t alphSwitchList[] = { + // Doom shareware episode 1 switches + {"SW1BRCOM", "SW2BRCOM", 1}, + {"SW1BRN1", "SW2BRN1", 1}, + {"SW1BRN2", "SW2BRN2", 1}, + {"SW1BRNGN", "SW2BRNGN", 1}, + {"SW1BROWN", "SW2BROWN", 1}, + {"SW1COMM", "SW2COMM", 1}, + {"SW1COMP", "SW2COMP", 1}, + {"SW1DIRT", "SW2DIRT", 1}, + {"SW1EXIT", "SW2EXIT", 1}, + {"SW1GRAY", "SW2GRAY", 1}, + {"SW1GRAY1", "SW2GRAY1", 1}, + {"SW1METAL", "SW2METAL", 1}, + {"SW1PIPE", "SW2PIPE", 1}, + {"SW1SLAD", "SW2SLAD", 1}, + {"SW1STARG", "SW2STARG", 1}, + {"SW1STON1", "SW2STON1", 1}, + {"SW1STON2", "SW2STON2", 1}, + {"SW1STONE", "SW2STONE", 1}, + {"SW1STRTN", "SW2STRTN", 1}, + + // Doom registered episodes 2&3 switches + {"SW1BLUE", "SW2BLUE", 2}, + {"SW1CMT", "SW2CMT", 2}, + {"SW1GARG", "SW2GARG", 2}, + {"SW1GSTON", "SW2GSTON", 2}, + {"SW1HOT", "SW2HOT", 2}, + {"SW1LION", "SW2LION", 2}, + {"SW1SATYR", "SW2SATYR", 2}, + {"SW1SKIN", "SW2SKIN", 2}, + {"SW1VINE", "SW2VINE", 2}, + {"SW1WOOD", "SW2WOOD", 2}, + + // Doom II switches + {"SW1PANEL", "SW2PANEL", 3}, + {"SW1ROCK", "SW2ROCK", 3}, + {"SW1MET2", "SW2MET2", 3}, + {"SW1WDMET", "SW2WDMET", 3}, + {"SW1BRIK", "SW2BRIK", 3}, + {"SW1MOD1", "SW2MOD1", 3}, + {"SW1ZIM", "SW2ZIM", 3}, + {"SW1STON6", "SW2STON6", 3}, + {"SW1TEK", "SW2TEK", 3}, + {"SW1MARB", "SW2MARB", 3}, + {"SW1SKULL", "SW2SKULL", 3}, + + {"\0", "\0", 0}}; + +int switchlist[MAXSWITCHES * 2]; +int numswitches; +button_t buttonlist[MAXBUTTONS]; + +// +// P_InitSwitchList +// Only called at game initialization. +// +void P_InitSwitchList(void) { + int i; + int index; + int episode; + + episode = 1; + + if (gamemode == registered || gamemode == retail) + episode = 2; + else if (gamemode == commercial) + episode = 3; + + for (index = 0, i = 0; i < MAXSWITCHES; i++) { + if (!alphSwitchList[i].episode) { + numswitches = index / 2; + switchlist[index] = -1; + break; + } + + if (alphSwitchList[i].episode <= episode) { +#if 0 // UNUSED - debug? + int value; + + if (R_CheckTextureNumForName(alphSwitchList[i].name1) < 0) + { + I_Error("Can't find switch texture '%s'!", + alphSwitchList[i].name1); + continue; + } + + value = R_TextureNumForName(alphSwitchList[i].name1); +#endif + switchlist[index++] = R_TextureNumForName((alphSwitchList[i].name1)); + switchlist[index++] = R_TextureNumForName((alphSwitchList[i].name2)); + } + } +} + +// +// Start a button counting down till it turns off. +// +void P_StartButton(line_t *line, bwhere_e w, int texture, int time) { + int i; + + // See if button is already pressed + for (i = 0; i < MAXBUTTONS; i++) { + if (buttonlist[i].btimer && buttonlist[i].line == line) { + + return; + } + } + + for (i = 0; i < MAXBUTTONS; i++) { + if (!buttonlist[i].btimer) { + buttonlist[i].line = line; + buttonlist[i].where = w; + buttonlist[i].btexture = texture; + buttonlist[i].btimer = time; + buttonlist[i].soundorg = &line->frontsector->soundorg; + return; + } + } + + I_Error("P_StartButton: no button slots left!"); +} + +// +// Function that changes wall texture. +// Tell it if switch is ok to use again (1=yes, it's a button). +// +void P_ChangeSwitchTexture(line_t *line, int useAgain) { + int texTop; + int texMid; + int texBot; + int i; + int sound; + + if (!useAgain) + line->special = 0; + + texTop = sides[line->sidenum[0]].toptexture; + texMid = sides[line->sidenum[0]].midtexture; + texBot = sides[line->sidenum[0]].bottomtexture; + + sound = sfx_swtchn; + + // EXIT SWITCH? + if (line->special == 11) + sound = sfx_swtchx; + + for (i = 0; i < numswitches * 2; i++) { + if (switchlist[i] == texTop) { + S_StartSound(buttonlist->soundorg, sound); + sides[line->sidenum[0]].toptexture = switchlist[i ^ 1]; + + if (useAgain) + P_StartButton(line, top, switchlist[i], BUTTONTIME); + + return; + } else { + if (switchlist[i] == texMid) { + S_StartSound(buttonlist->soundorg, sound); + sides[line->sidenum[0]].midtexture = switchlist[i ^ 1]; + + if (useAgain) + P_StartButton(line, middle, switchlist[i], BUTTONTIME); + + return; + } else { + if (switchlist[i] == texBot) { + S_StartSound(buttonlist->soundorg, sound); + sides[line->sidenum[0]].bottomtexture = switchlist[i ^ 1]; + + if (useAgain) + P_StartButton(line, bottom, switchlist[i], BUTTONTIME); + + return; + } + } + } + } +} + +// +// P_UseSpecialLine +// Called when a thing uses a special line. +// Only the front sides of lines are usable. +// +boolean P_UseSpecialLine(mobj_t *thing, line_t *line, int side) { + + // Err... + // Use the back sides of VERY SPECIAL lines... + if (side) { + switch (line->special) { + case 124: + // Sliding door open&close + // UNUSED? + break; + + default: + return false; + break; + } + } + + // Switches that other things can activate. + if (!thing->player) { + // never open secret doors + if (line->flags & ML_SECRET) + return false; + + switch (line->special) { + case 1: // MANUAL DOOR RAISE + case 32: // MANUAL BLUE + case 33: // MANUAL RED + case 34: // MANUAL YELLOW + break; + + default: + return false; + break; + } + } + + // do something + switch (line->special) { + // MANUALS + case 1: // Vertical Door + case 26: // Blue Door/Locked + case 27: // Yellow Door /Locked + case 28: // Red Door /Locked + + case 31: // Manual door open + case 32: // Blue locked door open + case 33: // Red locked door open + case 34: // Yellow locked door open + + case 117: // Blazing door raise + case 118: // Blazing door open + EV_VerticalDoor(line, thing); + break; + + // UNUSED - Door Slide Open&Close + // case 124: + // EV_SlidingDoor (line, thing); + // break; + + // SWITCHES + case 7: + // Build Stairs + if (EV_BuildStairs(line, build8)) + P_ChangeSwitchTexture(line, 0); + break; + + case 9: + // Change Donut + if (EV_DoDonut(line)) + P_ChangeSwitchTexture(line, 0); + break; + + case 11: + // Exit level + P_ChangeSwitchTexture(line, 0); + G_ExitLevel(); + break; + + case 14: + // Raise Floor 32 and change texture + if (EV_DoPlat(line, raiseAndChange, 32)) + P_ChangeSwitchTexture(line, 0); + break; + + case 15: + // Raise Floor 24 and change texture + if (EV_DoPlat(line, raiseAndChange, 24)) + P_ChangeSwitchTexture(line, 0); + break; + + case 18: + // Raise Floor to next highest floor + if (EV_DoFloor(line, raiseFloorToNearest)) + P_ChangeSwitchTexture(line, 0); + break; + + case 20: + // Raise Plat next highest floor and change texture + if (EV_DoPlat(line, raiseToNearestAndChange, 0)) + P_ChangeSwitchTexture(line, 0); + break; + + case 21: + // PlatDownWaitUpStay + if (EV_DoPlat(line, downWaitUpStay, 0)) + P_ChangeSwitchTexture(line, 0); + break; + + case 23: + // Lower Floor to Lowest + if (EV_DoFloor(line, lowerFloorToLowest)) + P_ChangeSwitchTexture(line, 0); + break; + + case 29: + // Raise Door + if (EV_DoDoor(line, vld_normal)) + P_ChangeSwitchTexture(line, 0); + break; + + case 41: + // Lower Ceiling to Floor + if (EV_DoCeiling(line, lowerToFloor)) + P_ChangeSwitchTexture(line, 0); + break; + + case 71: + // Turbo Lower Floor + if (EV_DoFloor(line, turboLower)) + P_ChangeSwitchTexture(line, 0); + break; + + case 49: + // Ceiling Crush And Raise + if (EV_DoCeiling(line, crushAndRaise)) + P_ChangeSwitchTexture(line, 0); + break; + + case 50: + // Close Door + if (EV_DoDoor(line, vld_close)) + P_ChangeSwitchTexture(line, 0); + break; + + case 51: + // Secret EXIT + P_ChangeSwitchTexture(line, 0); + G_SecretExitLevel(); + break; + + case 55: + // Raise Floor Crush + if (EV_DoFloor(line, raiseFloorCrush)) + P_ChangeSwitchTexture(line, 0); + break; + + case 101: + // Raise Floor + if (EV_DoFloor(line, raiseFloor)) + P_ChangeSwitchTexture(line, 0); + break; + + case 102: + // Lower Floor to Surrounding floor height + if (EV_DoFloor(line, lowerFloor)) + P_ChangeSwitchTexture(line, 0); + break; + + case 103: + // Open Door + if (EV_DoDoor(line, vld_open)) + P_ChangeSwitchTexture(line, 0); + break; + + case 111: + // Blazing Door Raise (faster than TURBO!) + if (EV_DoDoor(line, vld_blazeRaise)) + P_ChangeSwitchTexture(line, 0); + break; + + case 112: + // Blazing Door Open (faster than TURBO!) + if (EV_DoDoor(line, vld_blazeOpen)) + P_ChangeSwitchTexture(line, 0); + break; + + case 113: + // Blazing Door Close (faster than TURBO!) + if (EV_DoDoor(line, vld_blazeClose)) + P_ChangeSwitchTexture(line, 0); + break; + + case 122: + // Blazing PlatDownWaitUpStay + if (EV_DoPlat(line, blazeDWUS, 0)) + P_ChangeSwitchTexture(line, 0); + break; + + case 127: + // Build Stairs Turbo 16 + if (EV_BuildStairs(line, turbo16)) + P_ChangeSwitchTexture(line, 0); + break; + + case 131: + // Raise Floor Turbo + if (EV_DoFloor(line, raiseFloorTurbo)) + P_ChangeSwitchTexture(line, 0); + break; + + case 133: + // BlzOpenDoor BLUE + case 135: + // BlzOpenDoor RED + case 137: + // BlzOpenDoor YELLOW + if (EV_DoLockedDoor(line, vld_blazeOpen, thing)) + P_ChangeSwitchTexture(line, 0); + break; + + case 140: + // Raise Floor 512 + if (EV_DoFloor(line, raiseFloor512)) + P_ChangeSwitchTexture(line, 0); + break; + + // BUTTONS + case 42: + // Close Door + if (EV_DoDoor(line, vld_close)) + P_ChangeSwitchTexture(line, 1); + break; + + case 43: + // Lower Ceiling to Floor + if (EV_DoCeiling(line, lowerToFloor)) + P_ChangeSwitchTexture(line, 1); + break; + + case 45: + // Lower Floor to Surrounding floor height + if (EV_DoFloor(line, lowerFloor)) + P_ChangeSwitchTexture(line, 1); + break; + + case 60: + // Lower Floor to Lowest + if (EV_DoFloor(line, lowerFloorToLowest)) + P_ChangeSwitchTexture(line, 1); + break; + + case 61: + // Open Door + if (EV_DoDoor(line, vld_open)) + P_ChangeSwitchTexture(line, 1); + break; + + case 62: + // PlatDownWaitUpStay + if (EV_DoPlat(line, downWaitUpStay, 1)) + P_ChangeSwitchTexture(line, 1); + break; + + case 63: + // Raise Door + if (EV_DoDoor(line, vld_normal)) + P_ChangeSwitchTexture(line, 1); + break; + + case 64: + // Raise Floor to ceiling + if (EV_DoFloor(line, raiseFloor)) + P_ChangeSwitchTexture(line, 1); + break; + + case 66: + // Raise Floor 24 and change texture + if (EV_DoPlat(line, raiseAndChange, 24)) + P_ChangeSwitchTexture(line, 1); + break; + + case 67: + // Raise Floor 32 and change texture + if (EV_DoPlat(line, raiseAndChange, 32)) + P_ChangeSwitchTexture(line, 1); + break; + + case 65: + // Raise Floor Crush + if (EV_DoFloor(line, raiseFloorCrush)) + P_ChangeSwitchTexture(line, 1); + break; + + case 68: + // Raise Plat to next highest floor and change texture + if (EV_DoPlat(line, raiseToNearestAndChange, 0)) + P_ChangeSwitchTexture(line, 1); + break; + + case 69: + // Raise Floor to next highest floor + if (EV_DoFloor(line, raiseFloorToNearest)) + P_ChangeSwitchTexture(line, 1); + break; + + case 70: + // Turbo Lower Floor + if (EV_DoFloor(line, turboLower)) + P_ChangeSwitchTexture(line, 1); + break; + + case 114: + // Blazing Door Raise (faster than TURBO!) + if (EV_DoDoor(line, vld_blazeRaise)) + P_ChangeSwitchTexture(line, 1); + break; + + case 115: + // Blazing Door Open (faster than TURBO!) + if (EV_DoDoor(line, vld_blazeOpen)) + P_ChangeSwitchTexture(line, 1); + break; + + case 116: + // Blazing Door Close (faster than TURBO!) + if (EV_DoDoor(line, vld_blazeClose)) + P_ChangeSwitchTexture(line, 1); + break; + + case 123: + // Blazing PlatDownWaitUpStay + if (EV_DoPlat(line, blazeDWUS, 0)) + P_ChangeSwitchTexture(line, 1); + break; + + case 132: + // Raise Floor Turbo + if (EV_DoFloor(line, raiseFloorTurbo)) + P_ChangeSwitchTexture(line, 1); + break; + + case 99: + // BlzOpenDoor BLUE + case 134: + // BlzOpenDoor RED + case 136: + // BlzOpenDoor YELLOW + if (EV_DoLockedDoor(line, vld_blazeOpen, thing)) + P_ChangeSwitchTexture(line, 1); + break; + + case 138: + // Light Turn On + EV_LightTurnOn(line, 255); + P_ChangeSwitchTexture(line, 1); + break; + + case 139: + // Light Turn Off + EV_LightTurnOn(line, 35); + P_ChangeSwitchTexture(line, 1); + break; + } + + return true; +} diff --git a/client/src/p_telept.c b/client/src/p_telept.c new file mode 100644 index 0000000..702103a --- /dev/null +++ b/client/src/p_telept.c @@ -0,0 +1,119 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Teleportation. +// + +#include "d_mode.h" +#include "d_player.h" +#include "d_think.h" +#include "doomstat.h" +#include "info.h" +#include "m_fixed.h" +#include "p_local.h" +#include "p_mobj.h" +#include "r_defs.h" +// State. +#include "r_state.h" +#include "s_sound.h" +// Data. +#include "sounds.h" +#include "tables.h" + +// +// TELEPORTATION +// +int EV_Teleport(line_t *line, int side, mobj_t *thing) { + int i; + int tag; + mobj_t *m; + mobj_t *fog; + unsigned an; + thinker_t *thinker; + sector_t *sector; + fixed_t oldx; + fixed_t oldy; + fixed_t oldz; + + // don't teleport missiles + if (thing->flags & MF_MISSILE) + return 0; + + // Don't teleport if hit back of line, + // so you can get out of teleporter. + if (side == 1) + return 0; + + tag = line->tag; + for (i = 0; i < numsectors; i++) { + if (sectors[i].tag == tag) { + thinker = thinkercap.next; + for (thinker = thinkercap.next; thinker != &thinkercap; + thinker = thinker->next) { + // not a mobj + if (thinker->function.acp1 != (actionf_p1)P_MobjThinker) + continue; + + m = (mobj_t *)thinker; + + // not a teleportman + if (m->type != MT_TELEPORTMAN) + continue; + + sector = m->subsector->sector; + // wrong sector + if (sector - sectors != i) + continue; + + oldx = thing->x; + oldy = thing->y; + oldz = thing->z; + + if (!P_TeleportMove(thing, m->x, m->y)) + return 0; + + // The first Final Doom executable does not set thing->z + // when teleporting. This quirk is unique to this + // particular version; the later version included in + // some versions of the Id Anthology fixed this. + + if (gameversion != exe_final) + thing->z = thing->floorz; + + if (thing->player) + thing->player->viewz = thing->z + thing->player->viewheight; + + // spawn teleport fog at source and destination + fog = P_SpawnMobj(oldx, oldy, oldz, MT_TFOG); + S_StartSound(fog, sfx_telept); + an = m->angle >> ANGLETOFINESHIFT; + fog = P_SpawnMobj(m->x + 20 * finecosine[an], m->y + 20 * finesine[an], + thing->z, MT_TFOG); + + // emit sound, where? + S_StartSound(fog, sfx_telept); + + // don't move for a bit + if (thing->player) + thing->reactiontime = 18; + + thing->angle = m->angle; + thing->momx = thing->momy = thing->momz = 0; + return 1; + } + } + } + return 0; +} diff --git a/client/src/p_tick.c b/client/src/p_tick.c new file mode 100644 index 0000000..6aa602e --- /dev/null +++ b/client/src/p_tick.c @@ -0,0 +1,124 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Archiving: SaveGame I/O. +// Thinker, Ticker. +// + +#include "d_player.h" +#include "d_think.h" +#include "doomdef.h" +#include "doomstat.h" +#include "doomtype.h" +#include "p_local.h" +#include "p_spec.h" +#include "z_zone.h" + +int leveltime; + +// +// THINKERS +// All thinkers should be allocated by Z_Malloc +// so they can be operated on uniformly. +// The actual structures will vary in size, +// but the first element must be thinker_t. +// + +// Both the head and tail of the thinker list. +thinker_t thinkercap; + +// +// P_InitThinkers +// +void P_InitThinkers(void) { thinkercap.prev = thinkercap.next = &thinkercap; } + +// +// P_AddThinker +// Adds a new thinker at the end of the list. +// +void P_AddThinker(thinker_t *thinker) { + thinkercap.prev->next = thinker; + thinker->next = &thinkercap; + thinker->prev = thinkercap.prev; + thinkercap.prev = thinker; +} + +// +// P_RemoveThinker +// Deallocation is lazy -- it will not actually be freed +// until its thinking turn comes up. +// +void P_RemoveThinker(thinker_t *thinker) { + // FIXME: NOP. + thinker->function.acv = (actionf_v)(-1); +} + +// +// P_AllocateThinker +// Allocates memory and adds a new thinker at the end of the list. +// +void P_AllocateThinker(thinker_t *thinker) {} + +// +// P_RunThinkers +// +void P_RunThinkers(void) { + thinker_t *currentthinker, *nextthinker; + + currentthinker = thinkercap.next; + while (currentthinker != &thinkercap) { + if (currentthinker->function.acv == (actionf_v)(-1)) { + // time to remove it + nextthinker = currentthinker->next; + currentthinker->next->prev = currentthinker->prev; + currentthinker->prev->next = currentthinker->next; + Z_Free(currentthinker); + } else { + if (currentthinker->function.acp1) + currentthinker->function.acp1(currentthinker); + nextthinker = currentthinker->next; + } + currentthinker = nextthinker; + } +} + +// +// P_Ticker +// + +void P_Ticker(void) { + int i; + + // run the tic + if (paused) + return; + + // pause if in menu and at least one tic has been run + if (!netgame && menuactive && !demoplayback && + players[consoleplayer].viewz != 1) { + return; + } + + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) + P_PlayerThink(&players[i]); + + P_RunThinkers(); + P_UpdateSpecials(); + P_RespawnSpecials(); + + // for par times + leveltime++; +} diff --git a/client/src/p_tick.h b/client/src/p_tick.h new file mode 100644 index 0000000..804d86e --- /dev/null +++ b/client/src/p_tick.h @@ -0,0 +1,27 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// ? +// + +#ifndef __P_TICK__ +#define __P_TICK__ + +// Called by C_Ticker, +// can call G_PlayerExited. +// Carries out all thinking of monsters and players. +void P_Ticker(void); + +#endif diff --git a/client/src/p_user.c b/client/src/p_user.c new file mode 100644 index 0000000..a95bf7f --- /dev/null +++ b/client/src/p_user.c @@ -0,0 +1,322 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Player related stuff. +// Bobbing POV/weapon, movement. +// Pending weapon. +// + +#include "d_event.h" +#include "d_mode.h" +#include "d_player.h" +#include "d_ticcmd.h" +#include "doomdef.h" +#include "doomstat.h" +#include "doomtype.h" +#include "info.h" +#include "m_fixed.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_spec.h" +#include "r_defs.h" +#include "r_main.h" +#include "tables.h" + +// Index of the special effects (INVUL inverse) map. +#define INVERSECOLORMAP 32 + +// +// Movement. +// + +// 16 pixels of bob +#define MAXBOB 0x100000 + +boolean onground; + +// +// P_Thrust +// Moves the given origin along a given angle. +// +void P_Thrust(player_t *player, angle_t angle, fixed_t move) { + angle >>= ANGLETOFINESHIFT; + + player->mo->momx += FixedMul(move, finecosine[angle]); + player->mo->momy += FixedMul(move, finesine[angle]); +} + +// +// P_CalcHeight +// Calculate the walking / running height adjustment +// +void P_CalcHeight(player_t *player) { + int angle; + fixed_t bob; + + // Regular movement bobbing + // (needs to be calculated for gun swing + // even if not on ground) + // OPTIMIZE: tablify angle + // Note: a LUT allows for effects + // like a ramp with low health. + player->bob = FixedMul(player->mo->momx, player->mo->momx) + + FixedMul(player->mo->momy, player->mo->momy); + + player->bob >>= 2; + + if (player->bob > MAXBOB) + player->bob = MAXBOB; + + if ((player->cheats & CF_NOMOMENTUM) || !onground) { + player->viewz = player->mo->z + VIEWHEIGHT; + + if (player->viewz > player->mo->ceilingz - 4 * FRACUNIT) + player->viewz = player->mo->ceilingz - 4 * FRACUNIT; + + player->viewz = player->mo->z + player->viewheight; + return; + } + + angle = (FINEANGLES / 20 * leveltime) & FINEMASK; + bob = FixedMul(player->bob / 2, finesine[angle]); + + // move viewheight + if (player->playerstate == PST_LIVE) { + player->viewheight += player->deltaviewheight; + + if (player->viewheight > VIEWHEIGHT) { + player->viewheight = VIEWHEIGHT; + player->deltaviewheight = 0; + } + + if (player->viewheight < VIEWHEIGHT / 2) { + player->viewheight = VIEWHEIGHT / 2; + if (player->deltaviewheight <= 0) + player->deltaviewheight = 1; + } + + if (player->deltaviewheight) { + player->deltaviewheight += FRACUNIT / 4; + if (!player->deltaviewheight) + player->deltaviewheight = 1; + } + } + player->viewz = player->mo->z + player->viewheight + bob; + + if (player->viewz > player->mo->ceilingz - 4 * FRACUNIT) + player->viewz = player->mo->ceilingz - 4 * FRACUNIT; +} + +// +// P_MovePlayer +// +void P_MovePlayer(player_t *player) { + ticcmd_t *cmd; + + cmd = &player->cmd; + + player->mo->angle += (cmd->angleturn << FRACBITS); + + // Do not let the player control movement + // if not onground. + onground = (player->mo->z <= player->mo->floorz); + + if (cmd->forwardmove && onground) + P_Thrust(player, player->mo->angle, cmd->forwardmove * 2048); + + if (cmd->sidemove && onground) + P_Thrust(player, player->mo->angle - ANG90, cmd->sidemove * 2048); + + if ((cmd->forwardmove || cmd->sidemove) && + player->mo->state == &states[S_PLAY]) { + P_SetMobjState(player->mo, S_PLAY_RUN1); + } +} + +// +// P_DeathThink +// Fall on your face when dying. +// Decrease POV height to floor height. +// +#define ANG5 (ANG90 / 18) + +void P_DeathThink(player_t *player) { + angle_t angle; + angle_t delta; + + P_MovePsprites(player); + + // fall to the ground + if (player->viewheight > 6 * FRACUNIT) + player->viewheight -= FRACUNIT; + + if (player->viewheight < 6 * FRACUNIT) + player->viewheight = 6 * FRACUNIT; + + player->deltaviewheight = 0; + onground = (player->mo->z <= player->mo->floorz); + P_CalcHeight(player); + + if (player->attacker && player->attacker != player->mo) { + angle = R_PointToAngle2(player->mo->x, player->mo->y, player->attacker->x, + player->attacker->y); + + delta = angle - player->mo->angle; + + if (delta < ANG5 || delta > (unsigned)-ANG5) { + // Looking at killer, + // so fade damage flash down. + player->mo->angle = angle; + + if (player->damagecount) + player->damagecount--; + } else if (delta < ANG180) + player->mo->angle += ANG5; + else + player->mo->angle -= ANG5; + } else if (player->damagecount) + player->damagecount--; + + if (player->cmd.buttons & BT_USE) + player->playerstate = PST_REBORN; +} + +// +// P_PlayerThink +// +void P_PlayerThink(player_t *player) { + ticcmd_t *cmd; + weapontype_t newweapon; + + // fixme: do this in the cheat code + if (player->cheats & CF_NOCLIP) + player->mo->flags |= MF_NOCLIP; + else + player->mo->flags &= ~MF_NOCLIP; + + // chain saw run forward + cmd = &player->cmd; + if (player->mo->flags & MF_JUSTATTACKED) { + cmd->angleturn = 0; + cmd->forwardmove = 0xc800 / 512; + cmd->sidemove = 0; + player->mo->flags &= ~MF_JUSTATTACKED; + } + + if (player->playerstate == PST_DEAD) { + P_DeathThink(player); + return; + } + + // Move around. + // Reactiontime is used to prevent movement + // for a bit after a teleport. + if (player->mo->reactiontime) + player->mo->reactiontime--; + else + P_MovePlayer(player); + + P_CalcHeight(player); + + if (player->mo->subsector->sector->special) + P_PlayerInSpecialSector(player); + + // Check for weapon change. + + // A special event has no other buttons. + if (cmd->buttons & BT_SPECIAL) + cmd->buttons = 0; + + if (cmd->buttons & BT_CHANGE) { + // The actual changing of the weapon is done + // when the weapon psprite can do it + // (read: not in the middle of an attack). + newweapon = (cmd->buttons & BT_WEAPONMASK) >> BT_WEAPONSHIFT; + + if (newweapon == wp_fist && player->weaponowned[wp_chainsaw] && + !(player->readyweapon == wp_chainsaw && player->powers[pw_strength])) { + newweapon = wp_chainsaw; + } + + if ((gamemode == commercial) && newweapon == wp_shotgun && + player->weaponowned[wp_supershotgun] && + player->readyweapon != wp_supershotgun) { + newweapon = wp_supershotgun; + } + + if (player->weaponowned[newweapon] && newweapon != player->readyweapon) { + // Do not go to plasma or BFG in shareware, + // even if cheated. + if ((newweapon != wp_plasma && newweapon != wp_bfg) || + (gamemode != shareware)) { + player->pendingweapon = newweapon; + } + } + } + + // check for use + if (cmd->buttons & BT_USE) { + if (!player->usedown) { + P_UseLines(player); + player->usedown = true; + } + } else + player->usedown = false; + + // cycle psprites + P_MovePsprites(player); + + // Counters, time dependend power ups. + + // Strength counts up to diminish fade. + if (player->powers[pw_strength]) + player->powers[pw_strength]++; + + if (player->powers[pw_invulnerability]) + player->powers[pw_invulnerability]--; + + if (player->powers[pw_invisibility]) + if (!--player->powers[pw_invisibility]) + player->mo->flags &= ~MF_SHADOW; + + if (player->powers[pw_infrared]) + player->powers[pw_infrared]--; + + if (player->powers[pw_ironfeet]) + player->powers[pw_ironfeet]--; + + if (player->damagecount) + player->damagecount--; + + if (player->bonuscount) + player->bonuscount--; + + // Handling colormaps. + if (player->powers[pw_invulnerability]) { + if (player->powers[pw_invulnerability] > 4 * 32 || + (player->powers[pw_invulnerability] & 8)) + player->fixedcolormap = INVERSECOLORMAP; + else + player->fixedcolormap = 0; + } else if (player->powers[pw_infrared]) { + if (player->powers[pw_infrared] > 4 * 32 || + (player->powers[pw_infrared] & 8)) { + // almost full bright + player->fixedcolormap = 1; + } else + player->fixedcolormap = 0; + } else + player->fixedcolormap = 0; +} diff --git a/client/src/r_bsp.c b/client/src/r_bsp.c new file mode 100644 index 0000000..961ed4a --- /dev/null +++ b/client/src/r_bsp.c @@ -0,0 +1,497 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// BSP traversal, handling of LineSegs for rendering. +// + +#include + +#include "doomdata.h" +// State. +#include "doomstat.h" +#include "doomtype.h" +#include "i_system.h" +#include "i_video.h" +#include "m_bbox.h" +#include "m_fixed.h" +#include "r_defs.h" +#include "r_main.h" +#include "r_plane.h" +#include "r_state.h" +#include "r_things.h" +#include "tables.h" + +//#include "r_local.h" + +seg_t *curline; +side_t *sidedef; +line_t *linedef; +sector_t *frontsector; +sector_t *backsector; + +drawseg_t drawsegs[MAXDRAWSEGS]; +drawseg_t *ds_p; + +void R_StoreWallRange(int start, int stop); + +// +// R_ClearDrawSegs +// +void R_ClearDrawSegs(void) { ds_p = drawsegs; } + +// +// ClipWallSegment +// Clips the given range of columns +// and includes it in the new clip list. +// +typedef struct { + int first; + int last; + +} cliprange_t; + +// We must expand MAXSEGS to the theoretical limit of the number of solidsegs +// that can be generated in a scene by the DOOM engine. This was determined by +// Lee Killough during BOOM development to be a function of the screensize. +// The simplest thing we can do, other than fix this bug, is to let the game +// render overage and then bomb out by detecting the overflow after the +// fact. -haleyjd +//#define MAXSEGS 32 +#define MAXSEGS (SCREENWIDTH / 2 + 1) + +// newend is one past the last valid seg +cliprange_t *newend; +cliprange_t solidsegs[MAXSEGS]; + +// +// R_ClipSolidWallSegment +// Does handle solid walls, +// e.g. single sided LineDefs (middle texture) +// that entirely block the view. +// +void R_ClipSolidWallSegment(int first, int last) { + cliprange_t *next; + cliprange_t *start; + + // Find the first range that touches the range + // (adjacent pixels are touching). + start = solidsegs; + while (start->last < first - 1) + start++; + + if (first < start->first) { + if (last < start->first - 1) { + // Post is entirely visible (above start), + // so insert a new clippost. + R_StoreWallRange(first, last); + next = newend; + newend++; + + while (next != start) { + *next = *(next - 1); + next--; + } + next->first = first; + next->last = last; + return; + } + + // There is a fragment above *start. + R_StoreWallRange(first, start->first - 1); + // Now adjust the clip size. + start->first = first; + } + + // Bottom contained in start? + if (last <= start->last) + return; + + next = start; + while (last >= (next + 1)->first - 1) { + // There is a fragment between two posts. + R_StoreWallRange(next->last + 1, (next + 1)->first - 1); + next++; + + if (last <= next->last) { + // Bottom is contained in next. + // Adjust the clip size. + start->last = next->last; + goto crunch; + } + } + + // There is a fragment after *next. + R_StoreWallRange(next->last + 1, last); + // Adjust the clip size. + start->last = last; + +// Remove start+1 to next from the clip list, +// because start now covers their area. +crunch: + if (next == start) { + // Post just extended past the bottom of one post. + return; + } + + while (next++ != newend) { + // Remove a post. + *++start = *next; + } + + newend = start + 1; +} + +// +// R_ClipPassWallSegment +// Clips the given range of columns, +// but does not includes it in the clip list. +// Does handle windows, +// e.g. LineDefs with upper and lower texture. +// +void R_ClipPassWallSegment(int first, int last) { + cliprange_t *start; + + // Find the first range that touches the range + // (adjacent pixels are touching). + start = solidsegs; + while (start->last < first - 1) + start++; + + if (first < start->first) { + if (last < start->first - 1) { + // Post is entirely visible (above start). + R_StoreWallRange(first, last); + return; + } + + // There is a fragment above *start. + R_StoreWallRange(first, start->first - 1); + } + + // Bottom contained in start? + if (last <= start->last) + return; + + while (last >= (start + 1)->first - 1) { + // There is a fragment between two posts. + R_StoreWallRange(start->last + 1, (start + 1)->first - 1); + start++; + + if (last <= start->last) + return; + } + + // There is a fragment after *next. + R_StoreWallRange(start->last + 1, last); +} + +// +// R_ClearClipSegs +// +void R_ClearClipSegs(void) { + solidsegs[0].first = -0x7fffffff; + solidsegs[0].last = -1; + solidsegs[1].first = viewwidth; + solidsegs[1].last = 0x7fffffff; + newend = solidsegs + 2; +} + +// +// R_AddLine +// Clips the given segment +// and adds any visible pieces to the line list. +// +void R_AddLine(seg_t *line) { + int x1; + int x2; + angle_t angle1; + angle_t angle2; + angle_t span; + angle_t tspan; + + curline = line; + + // OPTIMIZE: quickly reject orthogonal back sides. + angle1 = R_PointToAngle(line->v1->x, line->v1->y); + angle2 = R_PointToAngle(line->v2->x, line->v2->y); + + // Clip to view edges. + // OPTIMIZE: make constant out of 2*clipangle (FIELDOFVIEW). + span = angle1 - angle2; + + // Back side? I.e. backface culling? + if (span >= ANG180) + return; + + // Global angle needed by segcalc. + rw_angle1 = angle1; + angle1 -= viewangle; + angle2 -= viewangle; + + tspan = angle1 + clipangle; + if (tspan > 2 * clipangle) { + tspan -= 2 * clipangle; + + // Totally off the left edge? + if (tspan >= span) + return; + + angle1 = clipangle; + } + tspan = clipangle - angle2; + if (tspan > 2 * clipangle) { + tspan -= 2 * clipangle; + + // Totally off the left edge? + if (tspan >= span) + return; + angle2 = -clipangle; + } + + // The seg is in the view range, + // but not necessarily visible. + angle1 = (angle1 + ANG90) >> ANGLETOFINESHIFT; + angle2 = (angle2 + ANG90) >> ANGLETOFINESHIFT; + x1 = viewangletox[angle1]; + x2 = viewangletox[angle2]; + + // Does not cross a pixel? + if (x1 == x2) + return; + + backsector = line->backsector; + + // Single sided line? + if (!backsector) + goto clipsolid; + + // Closed door. + if (backsector->ceilingheight <= frontsector->floorheight || + backsector->floorheight >= frontsector->ceilingheight) + goto clipsolid; + + // Window. + if (backsector->ceilingheight != frontsector->ceilingheight || + backsector->floorheight != frontsector->floorheight) + goto clippass; + + // Reject empty lines used for triggers + // and special events. + // Identical floor and ceiling on both sides, + // identical light levels on both sides, + // and no middle texture. + if (backsector->ceilingpic == frontsector->ceilingpic && + backsector->floorpic == frontsector->floorpic && + backsector->lightlevel == frontsector->lightlevel && + curline->sidedef->midtexture == 0) { + return; + } + +clippass: + R_ClipPassWallSegment(x1, x2 - 1); + return; + +clipsolid: + R_ClipSolidWallSegment(x1, x2 - 1); +} + +// +// R_CheckBBox +// Checks BSP node/subtree bounding box. +// Returns true +// if some part of the bbox might be visible. +// +int checkcoord[12][4] = {{3, 0, 2, 1}, {3, 0, 2, 0}, {3, 1, 2, 0}, {0}, + {2, 0, 2, 1}, {0, 0, 0, 0}, {3, 1, 3, 0}, {0}, + {2, 0, 3, 1}, {2, 1, 3, 1}, {2, 1, 3, 0}}; + +boolean R_CheckBBox(fixed_t *bspcoord) { + int boxx; + int boxy; + int boxpos; + + fixed_t x1; + fixed_t y1; + fixed_t x2; + fixed_t y2; + + angle_t angle1; + angle_t angle2; + angle_t span; + angle_t tspan; + + cliprange_t *start; + + int sx1; + int sx2; + + // Find the corners of the box + // that define the edges from current viewpoint. + if (viewx <= bspcoord[BOXLEFT]) + boxx = 0; + else if (viewx < bspcoord[BOXRIGHT]) + boxx = 1; + else + boxx = 2; + + if (viewy >= bspcoord[BOXTOP]) + boxy = 0; + else if (viewy > bspcoord[BOXBOTTOM]) + boxy = 1; + else + boxy = 2; + + boxpos = (boxy << 2) + boxx; + if (boxpos == 5) + return true; + + x1 = bspcoord[checkcoord[boxpos][0]]; + y1 = bspcoord[checkcoord[boxpos][1]]; + x2 = bspcoord[checkcoord[boxpos][2]]; + y2 = bspcoord[checkcoord[boxpos][3]]; + + // check clip list for an open space + angle1 = R_PointToAngle(x1, y1) - viewangle; + angle2 = R_PointToAngle(x2, y2) - viewangle; + + span = angle1 - angle2; + + // Sitting on a line? + if (span >= ANG180) + return true; + + tspan = angle1 + clipangle; + + if (tspan > 2 * clipangle) { + tspan -= 2 * clipangle; + + // Totally off the left edge? + if (tspan >= span) + return false; + + angle1 = clipangle; + } + tspan = clipangle - angle2; + if (tspan > 2 * clipangle) { + tspan -= 2 * clipangle; + + // Totally off the left edge? + if (tspan >= span) + return false; + + angle2 = -clipangle; + } + + // Find the first clippost + // that touches the source post + // (adjacent pixels are touching). + angle1 = (angle1 + ANG90) >> ANGLETOFINESHIFT; + angle2 = (angle2 + ANG90) >> ANGLETOFINESHIFT; + sx1 = viewangletox[angle1]; + sx2 = viewangletox[angle2]; + + // Does not cross a pixel. + if (sx1 == sx2) + return false; + sx2--; + + start = solidsegs; + while (start->last < sx2) + start++; + + if (sx1 >= start->first && sx2 <= start->last) { + // The clippost contains the new span. + return false; + } + + return true; +} + +// +// R_Subsector +// Determine floor/ceiling planes. +// Add sprites of things in sector. +// Draw one or more line segments. +// +void R_Subsector(int num) { + int count; + seg_t *line; + subsector_t *sub; + + if (num >= numsubsectors) + I_Error("R_Subsector: ss %i with numss = %i", num, numsubsectors); + + sscount++; + sub = &subsectors[num]; + frontsector = sub->sector; + count = sub->numlines; + line = &segs[sub->firstline]; + + if (frontsector->floorheight < viewz) { + floorplane = R_FindPlane(frontsector->floorheight, frontsector->floorpic, + frontsector->lightlevel); + } else + floorplane = NULL; + + if (frontsector->ceilingheight > viewz || + frontsector->ceilingpic == skyflatnum) { + ceilingplane = + R_FindPlane(frontsector->ceilingheight, frontsector->ceilingpic, + frontsector->lightlevel); + } else + ceilingplane = NULL; + + R_AddSprites(frontsector); + + while (count--) { + R_AddLine(line); + line++; + } + + // check for solidsegs overflow - extremely unsatisfactory! + if (newend > &solidsegs[32]) + I_Error("R_Subsector: solidsegs overflow (vanilla may crash here)\n"); +} + +// +// RenderBSPNode +// Renders all subsectors below a given node, +// traversing subtree recursively. +// Just call with BSP root. +void R_RenderBSPNode(int bspnum) { + node_t *bsp; + int side; + + // Found a subsector? + if (bspnum & NF_SUBSECTOR) { + if (bspnum == -1) + R_Subsector(0); + else + R_Subsector(bspnum & (~NF_SUBSECTOR)); + return; + } + + bsp = &nodes[bspnum]; + + // Decide which side the view point is on. + side = R_PointOnSide(viewx, viewy, bsp); + + // Recursively divide front space. + R_RenderBSPNode(bsp->children[side]); + + // Possibly divide back space. + if (R_CheckBBox(bsp->bbox[side ^ 1])) + R_RenderBSPNode(bsp->children[side ^ 1]); +} diff --git a/client/src/r_bsp.h b/client/src/r_bsp.h new file mode 100644 index 0000000..0981680 --- /dev/null +++ b/client/src/r_bsp.h @@ -0,0 +1,56 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh module, BSP traversal and handling. +// + +#ifndef __R_BSP__ +#define __R_BSP__ + +#include "r_defs.h" + +extern seg_t *curline; +extern side_t *sidedef; +extern line_t *linedef; +extern sector_t *frontsector; +extern sector_t *backsector; + +extern int rw_x; +extern int rw_stopx; + +extern boolean segtextured; + +// false if the back side is the same plane +extern boolean markfloor; +extern boolean markceiling; + +extern boolean skymap; + +extern drawseg_t drawsegs[MAXDRAWSEGS]; +extern drawseg_t *ds_p; + +extern lighttable_t **hscalelight; +extern lighttable_t **vscalelight; +extern lighttable_t **dscalelight; + +typedef void (*drawfunc_t)(int start, int stop); + +// BSP? +void R_ClearClipSegs(void); +void R_ClearDrawSegs(void); + +void R_RenderBSPNode(int bspnum); + +#endif diff --git a/client/src/r_data.c b/client/src/r_data.c new file mode 100644 index 0000000..822c1d8 --- /dev/null +++ b/client/src/r_data.c @@ -0,0 +1,809 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Preparation of data for rendering, +// generation of lookups, caching, retrieval by name. +// + +#include +#include +#include + +#include "d_think.h" +#include "doomstat.h" +#include "i_swap.h" +#include "i_system.h" +#include "m_fixed.h" +#include "m_misc.h" +#include "p_local.h" +#include "p_mobj.h" +#include "r_data.h" +#include "r_defs.h" +#include "r_sky.h" +#include "r_state.h" +#include "v_patch.h" +#include "w_wad.h" +#include "z_zone.h" + +struct texture_s; + +// +// Graphics. +// DOOM graphics for walls and sprites +// is stored in vertical runs of opaque pixels (posts). +// A column is composed of zero or more posts, +// a patch or sprite is composed of zero or more columns. +// + +// +// Texture definition. +// Each texture is composed of one or more patches, +// with patches being lumps stored in the WAD. +// The lumps are referenced by number, and patched +// into the rectangular texture space using origin +// and possibly other attributes. +// +typedef PACKED_STRUCT({ + short originx; + short originy; + short patch; + short stepdir; + short colormap; +}) mappatch_t; + +// +// Texture definition. +// A DOOM wall texture is a list of patches +// which are to be combined in a predefined order. +// +typedef PACKED_STRUCT({ + char name[8]; + int masked; + short width; + short height; + int obsolete; + short patchcount; + mappatch_t patches[1]; +}) maptexture_t; + +// A single patch from a texture definition, +// basically a rectangular area within +// the texture rectangle. +typedef struct { + // Block origin (allways UL), + // which has allready accounted + // for the internal origin of the patch. + short originx; + short originy; + int patch; +} texpatch_t; + +// A maptexturedef_t describes a rectangular texture, +// which is composed of one or more mappatch_t structures +// that arrange graphic patches. + +typedef struct texture_s texture_t; + +struct texture_s { + // Keep name for switch changing, etc. + char name[8]; + short width; + short height; + + // Index in textures list + + int index; + + // Next in hash table chain + + texture_t *next; + + // All the patches[patchcount] + // are drawn back to front into the cached texture. + short patchcount; + texpatch_t patches[1]; +}; + +int firstflat; +int lastflat; +int numflats; + +int firstpatch; +int lastpatch; +int numpatches; + +int firstspritelump; +int lastspritelump; +int numspritelumps; + +int numtextures; +texture_t **textures; +texture_t **textures_hashtable; + +int *texturewidthmask; +// needed for texture pegging +fixed_t *textureheight; +int *texturecompositesize; +short **texturecolumnlump; +unsigned short **texturecolumnofs; +byte **texturecomposite; + +// for global animation +int *flattranslation; +int *texturetranslation; + +// needed for pre rendering +fixed_t *spritewidth; +fixed_t *spriteoffset; +fixed_t *spritetopoffset; + +lighttable_t *colormaps; + +// +// MAPTEXTURE_T CACHING +// When a texture is first needed, +// it counts the number of composite columns +// required in the texture and allocates space +// for a column directory and any new columns. +// The directory will simply point inside other patches +// if there is only one patch in a given column, +// but any columns with multiple patches +// will have new column_ts generated. +// + +// +// R_DrawColumnInCache +// Clip and draw a column +// from a patch into a cached post. +// +void R_DrawColumnInCache(column_t *patch, byte *cache, int originy, + int cacheheight) { + int count; + int position; + byte *source; + + while (patch->topdelta != 0xff) { + source = (byte *)patch + 3; + count = patch->length; + position = originy + patch->topdelta; + + if (position < 0) { + count += position; + position = 0; + } + + if (position + count > cacheheight) + count = cacheheight - position; + + if (count > 0) + memcpy(cache + position, source, count); + + patch = (column_t *)((byte *)patch + patch->length + 4); + } +} + +// +// R_GenerateComposite +// Using the texture definition, +// the composite texture is created from the patches, +// and each column is cached. +// +void R_GenerateComposite(int texnum) { + byte *block; + texture_t *texture; + texpatch_t *patch; + patch_t *realpatch; + int x; + int x1; + int x2; + int i; + column_t *patchcol; + short *collump; + unsigned short *colofs; + + texture = textures[texnum]; + + block = Z_Malloc(texturecompositesize[texnum], PU_STATIC, + &texturecomposite[texnum]); + + collump = texturecolumnlump[texnum]; + colofs = texturecolumnofs[texnum]; + + // Composite the columns together. + patch = texture->patches; + + for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++) { + realpatch = W_CacheLumpNum(patch->patch, PU_CACHE); + x1 = patch->originx; + x2 = x1 + SHORT(realpatch->width); + + if (x1 < 0) + x = 0; + else + x = x1; + + if (x2 > texture->width) + x2 = texture->width; + + for (; x < x2; x++) { + // Column does not have multiple patches? + if (collump[x] >= 0) + continue; + + patchcol = + (column_t *)((byte *)realpatch + LONG(realpatch->columnofs[x - x1])); + R_DrawColumnInCache(patchcol, block + colofs[x], patch->originy, + texture->height); + } + } + + // Now that the texture has been built in column cache, + // it is purgable from zone memory. + Z_ChangeTag(block, PU_CACHE); +} + +// +// R_GenerateLookup +// +void R_GenerateLookup(int texnum) { + texture_t *texture; + byte *patchcount; // patchcount[texture->width] + texpatch_t *patch; + patch_t *realpatch; + int x; + int x1; + int x2; + int i; + short *collump; + unsigned short *colofs; + + texture = textures[texnum]; + + // Composited texture not created yet. + texturecomposite[texnum] = 0; + + texturecompositesize[texnum] = 0; + collump = texturecolumnlump[texnum]; + colofs = texturecolumnofs[texnum]; + + // Now count the number of columns + // that are covered by more than one patch. + // Fill in the lump / offset, so columns + // with only a single patch are all done. + patchcount = (byte *)Z_Malloc(texture->width, PU_STATIC, &patchcount); + memset(patchcount, 0, texture->width); + patch = texture->patches; + + for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++) { + realpatch = W_CacheLumpNum(patch->patch, PU_CACHE); + x1 = patch->originx; + x2 = x1 + SHORT(realpatch->width); + + if (x1 < 0) + x = 0; + else + x = x1; + + if (x2 > texture->width) + x2 = texture->width; + for (; x < x2; x++) { + patchcount[x]++; + collump[x] = patch->patch; + colofs[x] = LONG(realpatch->columnofs[x - x1]) + 3; + } + } + + for (x = 0; x < texture->width; x++) { + if (!patchcount[x]) { + printf("R_GenerateLookup: column without a patch (%s)\n", texture->name); + return; + } + // I_Error ("R_GenerateLookup: column without a patch"); + + if (patchcount[x] > 1) { + // Use the cached block. + collump[x] = -1; + colofs[x] = texturecompositesize[texnum]; + + if (texturecompositesize[texnum] > 0x10000 - texture->height) { + I_Error("R_GenerateLookup: texture %i is >64k", texnum); + } + + texturecompositesize[texnum] += texture->height; + } + } + + Z_Free(patchcount); +} + +// +// R_GetColumn +// +byte *R_GetColumn(int tex, int col) { + int lump; + int ofs; + + col &= texturewidthmask[tex]; + lump = texturecolumnlump[tex][col]; + ofs = texturecolumnofs[tex][col]; + + if (lump > 0) + return (byte *)W_CacheLumpNum(lump, PU_CACHE) + ofs; + + if (!texturecomposite[tex]) + R_GenerateComposite(tex); + + return texturecomposite[tex] + ofs; +} + +static void GenerateTextureHashTable(void) { + texture_t **rover; + int i; + int key; + + textures_hashtable = + Z_Malloc(sizeof(texture_t *) * numtextures, PU_STATIC, 0); + + memset(textures_hashtable, 0, sizeof(texture_t *) * numtextures); + + // Add all textures to hash table + + for (i = 0; i < numtextures; ++i) { + // Store index + + textures[i]->index = i; + + // Vanilla Doom does a linear search of the texures array + // and stops at the first entry it finds. If there are two + // entries with the same name, the first one in the array + // wins. The new entry must therefore be added at the end + // of the hash chain, so that earlier entries win. + + key = W_LumpNameHash(textures[i]->name) % numtextures; + + rover = &textures_hashtable[key]; + + while (*rover != NULL) { + rover = &(*rover)->next; + } + + // Hook into hash table + + textures[i]->next = NULL; + *rover = textures[i]; + } +} + +// +// R_InitTextures +// Initializes the texture list +// with the textures from the world map. +// +void R_InitTextures(void) { + maptexture_t *mtexture; + texture_t *texture; + mappatch_t *mpatch; + texpatch_t *patch; + + int i; + int j; + + int *maptex; + int *maptex2; + int *maptex1; + + char name[9]; + char *names; + char *name_p; + + int *patchlookup; + + int totalwidth; + int nummappatches; + int offset; + int maxoff; + int maxoff2; + int numtextures1; + int numtextures2; + + int *directory; + + int temp1; + int temp2; + int temp3; + + // Load the patch names from pnames.lmp. + name[8] = 0; + names = W_CacheLumpName(("PNAMES"), PU_STATIC); + nummappatches = LONG(*((int *)names)); + name_p = names + 4; + patchlookup = Z_Malloc(nummappatches * sizeof(*patchlookup), PU_STATIC, NULL); + + for (i = 0; i < nummappatches; i++) { + M_StringCopy(name, name_p + i * 8, sizeof(name)); + patchlookup[i] = W_CheckNumForName(name); + } + W_ReleaseLumpName(("PNAMES")); + + // Load the map texture definitions from textures.lmp. + // The data is contained in one or two lumps, + // TEXTURE1 for shareware, plus TEXTURE2 for commercial. + maptex = maptex1 = W_CacheLumpName(("TEXTURE1"), PU_STATIC); + numtextures1 = LONG(*maptex); + maxoff = W_LumpLength(W_GetNumForName(("TEXTURE1"))); + directory = maptex + 1; + + if (W_CheckNumForName(("TEXTURE2")) != -1) { + maptex2 = W_CacheLumpName(("TEXTURE2"), PU_STATIC); + numtextures2 = LONG(*maptex2); + maxoff2 = W_LumpLength(W_GetNumForName(("TEXTURE2"))); + } else { + maptex2 = NULL; + numtextures2 = 0; + maxoff2 = 0; + } + numtextures = numtextures1 + numtextures2; + + textures = Z_Malloc(numtextures * sizeof(*textures), PU_STATIC, 0); + texturecolumnlump = + Z_Malloc(numtextures * sizeof(*texturecolumnlump), PU_STATIC, 0); + texturecolumnofs = + Z_Malloc(numtextures * sizeof(*texturecolumnofs), PU_STATIC, 0); + texturecomposite = + Z_Malloc(numtextures * sizeof(*texturecomposite), PU_STATIC, 0); + texturecompositesize = + Z_Malloc(numtextures * sizeof(*texturecompositesize), PU_STATIC, 0); + texturewidthmask = + Z_Malloc(numtextures * sizeof(*texturewidthmask), PU_STATIC, 0); + textureheight = Z_Malloc(numtextures * sizeof(*textureheight), PU_STATIC, 0); + + totalwidth = 0; + + // Really complex printing shit... + temp1 = W_GetNumForName(("S_START")); // P_??????? + temp2 = W_GetNumForName(("S_END")) - 1; + temp3 = ((temp2 - temp1 + 63) / 64) + ((numtextures + 63) / 64); + + // If stdout is a real console, use the classic vanilla "filling + // up the box" effect, which uses backspace to "step back" inside + // the box. If stdout is a file, don't draw the box. + + if (I_ConsoleStdout()) { + printf("["); + for (i = 0; i < temp3 + 9; i++) + printf(" "); + printf("]"); + for (i = 0; i < temp3 + 10; i++) + printf("\b"); + } + + for (i = 0; i < numtextures; i++, directory++) { + if (!(i & 63)) + printf("."); + + if (i == numtextures1) { + // Start looking in second texture file. + maptex = maptex2; + maxoff = maxoff2; + directory = maptex + 1; + } + + offset = LONG(*directory); + + if (offset > maxoff) + I_Error("R_InitTextures: bad texture directory"); + + mtexture = (maptexture_t *)((byte *)maptex + offset); + + texture = textures[i] = + Z_Malloc(sizeof(texture_t) + + sizeof(texpatch_t) * (SHORT(mtexture->patchcount) - 1), + PU_STATIC, 0); + + texture->width = SHORT(mtexture->width); + texture->height = SHORT(mtexture->height); + texture->patchcount = SHORT(mtexture->patchcount); + + memcpy(texture->name, mtexture->name, sizeof(texture->name)); + mpatch = &mtexture->patches[0]; + patch = &texture->patches[0]; + + for (j = 0; j < texture->patchcount; j++, mpatch++, patch++) { + patch->originx = SHORT(mpatch->originx); + patch->originy = SHORT(mpatch->originy); + patch->patch = patchlookup[SHORT(mpatch->patch)]; + if (patch->patch == -1) { + I_Error("R_InitTextures: Missing patch in texture %s", texture->name); + } + } + texturecolumnlump[i] = + Z_Malloc(texture->width * sizeof(**texturecolumnlump), PU_STATIC, 0); + texturecolumnofs[i] = + Z_Malloc(texture->width * sizeof(**texturecolumnofs), PU_STATIC, 0); + + j = 1; + while (j * 2 <= texture->width) + j <<= 1; + + texturewidthmask[i] = j - 1; + textureheight[i] = texture->height << FRACBITS; + + totalwidth += texture->width; + } + + Z_Free(patchlookup); + + W_ReleaseLumpName(("TEXTURE1")); + if (maptex2) + W_ReleaseLumpName(("TEXTURE2")); + + // Precalculate whatever possible. + + for (i = 0; i < numtextures; i++) + R_GenerateLookup(i); + + // Create translation table for global animation. + texturetranslation = + Z_Malloc((numtextures + 1) * sizeof(*texturetranslation), PU_STATIC, 0); + + for (i = 0; i < numtextures; i++) + texturetranslation[i] = i; + + GenerateTextureHashTable(); +} + +// +// R_InitFlats +// +void R_InitFlats(void) { + int i; + + firstflat = W_GetNumForName(("F_START")) + 1; + lastflat = W_GetNumForName(("F_END")) - 1; + numflats = lastflat - firstflat + 1; + + // Create translation table for global animation. + flattranslation = + Z_Malloc((numflats + 1) * sizeof(*flattranslation), PU_STATIC, 0); + + for (i = 0; i < numflats; i++) + flattranslation[i] = i; +} + +// +// R_InitSpriteLumps +// Finds the width and hoffset of all sprites in the wad, +// so the sprite does not need to be cached completely +// just for having the header info ready during rendering. +// +void R_InitSpriteLumps(void) { + int i; + patch_t *patch; + + firstspritelump = W_GetNumForName(("S_START")) + 1; + lastspritelump = W_GetNumForName(("S_END")) - 1; + + numspritelumps = lastspritelump - firstspritelump + 1; + spritewidth = Z_Malloc(numspritelumps * sizeof(*spritewidth), PU_STATIC, 0); + spriteoffset = Z_Malloc(numspritelumps * sizeof(*spriteoffset), PU_STATIC, 0); + spritetopoffset = + Z_Malloc(numspritelumps * sizeof(*spritetopoffset), PU_STATIC, 0); + + for (i = 0; i < numspritelumps; i++) { + if (!(i & 63)) + printf("."); + + patch = W_CacheLumpNum(firstspritelump + i, PU_CACHE); + spritewidth[i] = SHORT(patch->width) << FRACBITS; + spriteoffset[i] = SHORT(patch->leftoffset) << FRACBITS; + spritetopoffset[i] = SHORT(patch->topoffset) << FRACBITS; + } +} + +// +// R_InitColormaps +// +void R_InitColormaps(void) { + int lump; + + // Load in the light tables, + // 256 byte align tables. + lump = W_GetNumForName(("COLORMAP")); + colormaps = W_CacheLumpNum(lump, PU_STATIC); +} + +// +// R_InitData +// Locates all the lumps +// that will be used by all views +// Must be called after W_Init. +// +void R_InitData(void) { + R_InitTextures(); + printf("."); + R_InitFlats(); + printf("."); + R_InitSpriteLumps(); + printf("."); + R_InitColormaps(); +} + +// +// R_FlatNumForName +// Retrieval, get a flat number for a flat name. +// +int R_FlatNumForName(char *name) { + int i; + char namet[9]; + + i = W_CheckNumForName(name); + + if (i == -1) { + namet[8] = 0; + memcpy(namet, name, 8); + I_Error("R_FlatNumForName: %s not found", namet); + } + return i - firstflat; +} + +// +// R_CheckTextureNumForName +// Check whether texture is available. +// Filter out NoTexture indicator. +// +int R_CheckTextureNumForName(char *name) { + texture_t *texture; + int key; + + // "NoTexture" marker. + if (name[0] == '-') + return 0; + + key = W_LumpNameHash(name) % numtextures; + + texture = textures_hashtable[key]; + + while (texture != NULL) { + if (!strncasecmp(texture->name, name, 8)) + return texture->index; + + texture = texture->next; + } + + return -1; +} + +// +// R_TextureNumForName +// Calls R_CheckTextureNumForName, +// aborts with error message. +// +int R_TextureNumForName(char *name) { + int i; + + i = R_CheckTextureNumForName(name); + + if (i == -1) { + I_Error("R_TextureNumForName: %s not found", name); + } + return i; +} + +// +// R_PrecacheLevel +// Preloads all relevant graphics for the level. +// +int flatmemory; +int texturememory; +int spritememory; + +void R_PrecacheLevel(void) { + char *flatpresent; + char *texturepresent; + char *spritepresent; + + int i; + int j; + int k; + int lump; + + texture_t *texture; + thinker_t *th; + spriteframe_t *sf; + + if (demoplayback) + return; + + // Precache flats. + flatpresent = Z_Malloc(numflats, PU_STATIC, NULL); + memset(flatpresent, 0, numflats); + + for (i = 0; i < numsectors; i++) { + flatpresent[sectors[i].floorpic] = 1; + flatpresent[sectors[i].ceilingpic] = 1; + } + + flatmemory = 0; + + for (i = 0; i < numflats; i++) { + if (flatpresent[i]) { + lump = firstflat + i; + flatmemory += lumpinfo[lump]->size; + W_CacheLumpNum(lump, PU_CACHE); + } + } + + Z_Free(flatpresent); + + // Precache textures. + texturepresent = Z_Malloc(numtextures, PU_STATIC, NULL); + memset(texturepresent, 0, numtextures); + + for (i = 0; i < numsides; i++) { + texturepresent[sides[i].toptexture] = 1; + texturepresent[sides[i].midtexture] = 1; + texturepresent[sides[i].bottomtexture] = 1; + } + + // Sky texture is always present. + // Note that F_SKY1 is the name used to + // indicate a sky floor/ceiling as a flat, + // while the sky texture is stored like + // a wall texture, with an episode dependend + // name. + texturepresent[skytexture] = 1; + + texturememory = 0; + for (i = 0; i < numtextures; i++) { + if (!texturepresent[i]) + continue; + + texture = textures[i]; + + for (j = 0; j < texture->patchcount; j++) { + lump = texture->patches[j].patch; + texturememory += lumpinfo[lump]->size; + W_CacheLumpNum(lump, PU_CACHE); + } + } + + Z_Free(texturepresent); + + // Precache sprites. + spritepresent = Z_Malloc(numsprites, PU_STATIC, NULL); + memset(spritepresent, 0, numsprites); + + for (th = thinkercap.next; th != &thinkercap; th = th->next) { + if (th->function.acp1 == (actionf_p1)P_MobjThinker) + spritepresent[((mobj_t *)th)->sprite] = 1; + } + + spritememory = 0; + for (i = 0; i < numsprites; i++) { + if (!spritepresent[i]) + continue; + + for (j = 0; j < sprites[i].numframes; j++) { + sf = &sprites[i].spriteframes[j]; + for (k = 0; k < 8; k++) { + lump = firstspritelump + sf->lump[k]; + spritememory += lumpinfo[lump]->size; + W_CacheLumpNum(lump, PU_CACHE); + } + } + } + + Z_Free(spritepresent); +} diff --git a/client/src/r_data.h b/client/src/r_data.h new file mode 100644 index 0000000..cb9305d --- /dev/null +++ b/client/src/r_data.h @@ -0,0 +1,42 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh module, data I/O, caching, retrieval of graphics +// by name. +// + +#ifndef __R_DATA__ +#define __R_DATA__ + +#include "doomtype.h" + +// Retrieve column data for span blitting. +byte *R_GetColumn(int tex, int col); + +// I/O, setting up the stuff. +void R_InitData(void); +void R_PrecacheLevel(void); + +// Retrieval. +// Floor/ceiling opaque texture tiles, +// lookup by name. For animation? +int R_FlatNumForName(char *name); + +// Called by P_Ticker for switches and animations, +// returns the texture number for the texture name. +int R_TextureNumForName(char *name); +int R_CheckTextureNumForName(char *name); + +#endif diff --git a/client/src/r_defs.h b/client/src/r_defs.h new file mode 100644 index 0000000..979d1bb --- /dev/null +++ b/client/src/r_defs.h @@ -0,0 +1,390 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh/rendering module, shared data struct definitions. +// + +#ifndef __R_DEFS__ +#define __R_DEFS__ + +// Screenwidth. +#include "doomdef.h" + +// Some more or less basic data types +// we depend on. +#include "m_fixed.h" + +// We rely on the thinker data struct +// to handle sound origins in sectors. +#include "d_think.h" +// SECTORS do store MObjs anyway. +#include "p_mobj.h" + +#include "i_video.h" + +#include "v_patch.h" + +// Silhouette, needed for clipping Segs (mainly) +// and sprites representing things. +#define SIL_NONE 0 +#define SIL_BOTTOM 1 +#define SIL_TOP 2 +#define SIL_BOTH 3 + +#define MAXDRAWSEGS 256 + +// +// INTERNAL MAP TYPES +// used by play and refresh +// + +// +// Your plain vanilla vertex. +// Note: transformed values not buffered locally, +// like some DOOM-alikes ("wt", "WebView") did. +// +typedef struct { + fixed_t x; + fixed_t y; + +} vertex_t; + +// Forward of LineDefs, for Sectors. +struct line_s; + +// Each sector has a degenmobj_t in its center +// for sound origin purposes. +// I suppose this does not handle sound from +// moving objects (doppler), because +// position is prolly just buffered, not +// updated. +typedef struct { + thinker_t thinker; // not used for anything + fixed_t x; + fixed_t y; + fixed_t z; + +} degenmobj_t; + +// +// The SECTORS record, at runtime. +// Stores things/mobjs. +// +typedef struct { + fixed_t floorheight; + fixed_t ceilingheight; + short floorpic; + short ceilingpic; + short lightlevel; + short special; + short tag; + + // 0 = untraversed, 1,2 = sndlines -1 + int soundtraversed; + + // thing that made a sound (or null) + mobj_t *soundtarget; + + // mapblock bounding box for height changes + int blockbox[4]; + + // origin for any sounds played by the sector + degenmobj_t soundorg; + + // if == validcount, already checked + int validcount; + + // list of mobjs in sector + mobj_t *thinglist; + + // thinker_t for reversable actions + void *specialdata; + + int linecount; + struct line_s **lines; // [linecount] size + +} sector_t; + +// +// The SideDef. +// + +typedef struct { + // add this to the calculated texture column + fixed_t textureoffset; + + // add this to the calculated texture top + fixed_t rowoffset; + + // Texture indices. + // We do not maintain names here. + short toptexture; + short bottomtexture; + short midtexture; + + // Sector the SideDef is facing. + sector_t *sector; + +} side_t; + +// +// Move clipping aid for LineDefs. +// +typedef enum { + ST_HORIZONTAL, + ST_VERTICAL, + ST_POSITIVE, + ST_NEGATIVE + +} slopetype_t; + +typedef struct line_s { + // Vertices, from v1 to v2. + vertex_t *v1; + vertex_t *v2; + + // Precalculated v2 - v1 for side checking. + fixed_t dx; + fixed_t dy; + + // Animation related. + short flags; + short special; + short tag; + + // Visual appearance: SideDefs. + // sidenum[1] will be -1 if one sided + short sidenum[2]; + + // Neat. Another bounding box, for the extent + // of the LineDef. + fixed_t bbox[4]; + + // To aid move clipping. + slopetype_t slopetype; + + // Front and back sector. + // Note: redundant? Can be retrieved from SideDefs. + sector_t *frontsector; + sector_t *backsector; + + // if == validcount, already checked + int validcount; + + // thinker_t for reversable actions + void *specialdata; +} line_t; + +// +// A SubSector. +// References a Sector. +// Basically, this is a list of LineSegs, +// indicating the visible walls that define +// (all or some) sides of a convex BSP leaf. +// +typedef struct subsector_s { + sector_t *sector; + short numlines; + short firstline; + +} subsector_t; + +// +// The LineSeg. +// +typedef struct { + vertex_t *v1; + vertex_t *v2; + + fixed_t offset; + + angle_t angle; + + side_t *sidedef; + line_t *linedef; + + // Sector references. + // Could be retrieved from linedef, too. + // backsector is NULL for one sided lines + sector_t *frontsector; + sector_t *backsector; + +} seg_t; + +// +// BSP node. +// +typedef struct { + // Partition line. + fixed_t x; + fixed_t y; + fixed_t dx; + fixed_t dy; + + // Bounding box for each child. + fixed_t bbox[2][4]; + + // If NF_SUBSECTOR its a subsector. + unsigned short children[2]; + +} node_t; + +// PC direct to screen pointers +// B UNUSED - keep till detailshift in r_draw.c resolved +// extern byte* destview; +// extern byte* destscreen; + +// +// OTHER TYPES +// + +// This could be wider for >8 bit display. +// Indeed, true color support is posibble +// precalculating 24bpp lightmap/colormap LUT. +// from darkening PLAYPAL to all black. +// Could even us emore than 32 levels. +typedef pixel_t lighttable_t; + +// +// ? +// +typedef struct drawseg_s { + seg_t *curline; + int x1; + int x2; + + fixed_t scale1; + fixed_t scale2; + fixed_t scalestep; + + // 0=none, 1=bottom, 2=top, 3=both + int silhouette; + + // do not clip sprites above this + fixed_t bsilheight; + + // do not clip sprites below this + fixed_t tsilheight; + + // Pointers to lists for sprite clipping, + // all three adjusted so [x1] is first value. + short *sprtopclip; + short *sprbottomclip; + short *maskedtexturecol; + +} drawseg_t; + +// A vissprite_t is a thing +// that will be drawn during a refresh. +// I.e. a sprite object that is partly visible. +typedef struct vissprite_s { + // Doubly linked list. + struct vissprite_s *prev; + struct vissprite_s *next; + + int x1; + int x2; + + // for line side calculation + fixed_t gx; + fixed_t gy; + + // global bottom / top for silhouette clipping + fixed_t gz; + fixed_t gzt; + + // horizontal position of x1 + fixed_t startfrac; + + fixed_t scale; + + // negative if flipped + fixed_t xiscale; + + fixed_t texturemid; + int patch; + + // for color translation and shadow draw, + // maxbright frames as well + lighttable_t *colormap; + + int mobjflags; + +} vissprite_t; + +// +// Sprites are patches with a special naming convention +// so they can be recognized by R_InitSprites. +// The base name is NNNNFx or NNNNFxFx, with +// x indicating the rotation, x = 0, 1-7. +// The sprite and frame specified by a thing_t +// is range checked at run time. +// A sprite is a patch_t that is assumed to represent +// a three dimensional object and may have multiple +// rotations pre drawn. +// Horizontal flipping is used to save space, +// thus NNNNF2F5 defines a mirrored patch. +// Some sprites will only have one picture used +// for all views: NNNNF0 +// +typedef struct { + // If false use 0 for any position. + // Note: as eight entries are available, + // we might as well insert the same name eight times. + boolean rotate; + + // Lump to use for view angles 0-7. + short lump[8]; + + // Flip bit (1 = flip) to use for view angles 0-7. + byte flip[8]; + +} spriteframe_t; + +// +// A sprite definition: +// a number of animation frames. +// +typedef struct { + int numframes; + spriteframe_t *spriteframes; + +} spritedef_t; + +// +// Now what is a visplane, anyway? +// +typedef struct { + fixed_t height; + int picnum; + int lightlevel; + int minx; + int maxx; + + // leave pads for [minx-1]/[maxx+1] + + byte pad1; + // Here lies the rub for all + // dynamic resize/change of resolution. + byte top[SCREENWIDTH]; + byte pad2; + byte pad3; + // See above. + byte bottom[SCREENWIDTH]; + byte pad4; + +} visplane_t; + +#endif diff --git a/client/src/r_draw.c b/client/src/r_draw.c new file mode 100644 index 0000000..df75385 --- /dev/null +++ b/client/src/r_draw.c @@ -0,0 +1,852 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// The actual span/column drawing functions. +// Here find the main potential for optimization, +// e.g. inline assembly, different algorithms. +// + +#include + +#include "d_mode.h" +// State. +#include "doomstat.h" +#include "doomtype.h" +#include "i_system.h" +#include "i_video.h" +#include "m_fixed.h" +#include "r_defs.h" +#include "r_main.h" +#include "r_state.h" +#include "v_patch.h" +// Needs access to LFB (guess what). +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +// ? +#define MAXWIDTH 1120 +#define MAXHEIGHT 832 + +// status bar height at bottom of screen +#define SBARHEIGHT 32 + +// +// All drawing to the view buffer is accomplished in this file. +// The other refresh files only know about ccordinates, +// not the architecture of the frame buffer. +// Conveniently, the frame buffer is a linear one, +// and we need only the base address, +// and the total size == width*height*depth/8., +// + +byte *viewimage; +int viewwidth; +int scaledviewwidth; +int viewheight; +int viewwindowx; +int viewwindowy; +pixel_t *ylookup[MAXHEIGHT]; +int columnofs[MAXWIDTH]; + +// Color tables for different players, +// translate a limited part to another +// (color ramps used for suit colors). +// +byte translations[3][256]; + +// Backing buffer containing the bezel drawn around the screen and +// surrounding background. + +static pixel_t *background_buffer = NULL; + +// +// R_DrawColumn +// Source is the top of the column to scale. +// +lighttable_t *dc_colormap; +int dc_x; +int dc_yl; +int dc_yh; +fixed_t dc_iscale; +fixed_t dc_texturemid; + +// first pixel in a column (possibly virtual) +byte *dc_source; + +// just for profiling +int dccount; + +// +// A column is a vertical slice/span from a wall texture that, +// given the DOOM style restrictions on the view orientation, +// will always have constant z depth. +// Thus a special case loop for very fast rendering can +// be used. It has also been used with Wolfenstein 3D. +// +void R_DrawColumn(void) { + int count; + pixel_t *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_yh - dc_yl; + + // Zero length, column does not exceed a pixel. + if (count < 0) + return; + + if ((unsigned)dc_x >= SCREENWIDTH || dc_yl < 0 || dc_yh >= SCREENHEIGHT) + I_Error("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = ylookup[dc_yl] + columnofs[dc_x]; + + // Determine scaling, + // which is the only mapping to be done. + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + *dest = dc_colormap[dc_source[(frac >> FRACBITS) & 127]]; + + dest += SCREENWIDTH; + frac += fracstep; + + } while (count--); +} + +// UNUSED. +// Loop unrolled. +#if 0 +void R_DrawColumn (void) +{ + int count; + byte* source; + byte* dest; + byte* colormap; + + unsigned frac; + unsigned fracstep; + unsigned fracstep2; + unsigned fracstep3; + unsigned fracstep4; + + count = dc_yh - dc_yl + 1; + + source = dc_source; + colormap = dc_colormap; + dest = ylookup[dc_yl] + columnofs[dc_x]; + + fracstep = dc_iscale<<9; + frac = (dc_texturemid + (dc_yl-centery)*dc_iscale)<<9; + + fracstep2 = fracstep+fracstep; + fracstep3 = fracstep2+fracstep; + fracstep4 = fracstep3+fracstep; + + while (count >= 8) + { + dest[0] = colormap[source[frac>>25]]; + dest[SCREENWIDTH] = colormap[source[(frac+fracstep)>>25]]; + dest[SCREENWIDTH*2] = colormap[source[(frac+fracstep2)>>25]]; + dest[SCREENWIDTH*3] = colormap[source[(frac+fracstep3)>>25]]; + + frac += fracstep4; + + dest[SCREENWIDTH*4] = colormap[source[frac>>25]]; + dest[SCREENWIDTH*5] = colormap[source[(frac+fracstep)>>25]]; + dest[SCREENWIDTH*6] = colormap[source[(frac+fracstep2)>>25]]; + dest[SCREENWIDTH*7] = colormap[source[(frac+fracstep3)>>25]]; + + frac += fracstep4; + dest += SCREENWIDTH*8; + count -= 8; + } + + while (count > 0) + { + *dest = colormap[source[frac>>25]]; + dest += SCREENWIDTH; + frac += fracstep; + count--; + } +} +#endif + +void R_DrawColumnLow(void) { + int count; + pixel_t *dest; + pixel_t *dest2; + fixed_t frac; + fixed_t fracstep; + int x; + + count = dc_yh - dc_yl; + + // Zero length. + if (count < 0) + return; + + if ((unsigned)dc_x >= SCREENWIDTH || dc_yl < 0 || dc_yh >= SCREENHEIGHT) { + + I_Error("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); + } + // dccount++; + // Blocky mode, need to multiply by 2. + x = dc_x << 1; + + dest = ylookup[dc_yl] + columnofs[x]; + dest2 = ylookup[dc_yl] + columnofs[x + 1]; + + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + + do { + // Hack. Does not work corretly. + *dest2 = *dest = dc_colormap[dc_source[(frac >> FRACBITS) & 127]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + } while (count--); +} + +// +// Spectre/Invisibility. +// +#define FUZZTABLE 50 +#define FUZZOFF (SCREENWIDTH) + +int fuzzoffset[FUZZTABLE] = { + FUZZOFF, -FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, + FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, + -FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, -FUZZOFF, + FUZZOFF, FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, + FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF}; + +int fuzzpos = 0; + +// +// Framebuffer postprocessing. +// Creates a fuzzy image by copying pixels +// from adjacent ones to left and right. +// Used with an all black colormap, this +// could create the SHADOW effect, +// i.e. spectres and invisible players. +// +void R_DrawFuzzColumn(void) { + int count; + pixel_t *dest; + fixed_t frac; + fixed_t fracstep; + + // Adjust borders. Low... + if (!dc_yl) + dc_yl = 1; + + // .. and high. + if (dc_yh == viewheight - 1) + dc_yh = viewheight - 2; + + count = dc_yh - dc_yl; + + // Zero length. + if (count < 0) + return; + + if ((unsigned)dc_x >= SCREENWIDTH || dc_yl < 0 || dc_yh >= SCREENHEIGHT) { + I_Error("R_DrawFuzzColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); + } + + dest = ylookup[dc_yl] + columnofs[dc_x]; + + // Looks familiar. + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + + // Looks like an attempt at dithering, + // using the colormap #6 (of 0-31, a bit + // brighter than average). + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + *dest = colormaps[6 * 256 + dest[fuzzoffset[fuzzpos]]]; + + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) + fuzzpos = 0; + + dest += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} + +// low detail mode version + +void R_DrawFuzzColumnLow(void) { + int count; + pixel_t *dest; + pixel_t *dest2; + fixed_t frac; + fixed_t fracstep; + int x; + + // Adjust borders. Low... + if (!dc_yl) + dc_yl = 1; + + // .. and high. + if (dc_yh == viewheight - 1) + dc_yh = viewheight - 2; + + count = dc_yh - dc_yl; + + // Zero length. + if (count < 0) + return; + + // low detail mode, need to multiply by 2 + + x = dc_x << 1; + + if ((unsigned)x >= SCREENWIDTH || dc_yl < 0 || dc_yh >= SCREENHEIGHT) { + I_Error("R_DrawFuzzColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); + } + + dest = ylookup[dc_yl] + columnofs[x]; + dest2 = ylookup[dc_yl] + columnofs[x + 1]; + + // Looks familiar. + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + + // Looks like an attempt at dithering, + // using the colormap #6 (of 0-31, a bit + // brighter than average). + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + *dest = colormaps[6 * 256 + dest[fuzzoffset[fuzzpos]]]; + *dest2 = colormaps[6 * 256 + dest2[fuzzoffset[fuzzpos]]]; + + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) + fuzzpos = 0; + + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} + +// +// R_DrawTranslatedColumn +// Used to draw player sprites +// with the green colorramp mapped to others. +// Could be used with different translation +// tables, e.g. the lighter colored version +// of the BaronOfHell, the HellKnight, uses +// identical sprites, kinda brightened up. +// +byte *dc_translation; +byte *translationtables; + +void R_DrawTranslatedColumn(void) { + int count; + pixel_t *dest; + fixed_t frac; + fixed_t fracstep; + + count = dc_yh - dc_yl; + if (count < 0) + return; + + if ((unsigned)dc_x >= SCREENWIDTH || dc_yl < 0 || dc_yh >= SCREENHEIGHT) { + I_Error("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); + } + + dest = ylookup[dc_yl] + columnofs[dc_x]; + + // Looks familiar. + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + + // Here we do an additional index re-mapping. + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + *dest = dc_colormap[dc_translation[dc_source[frac >> FRACBITS]]]; + dest += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} + +void R_DrawTranslatedColumnLow(void) { + int count; + pixel_t *dest; + pixel_t *dest2; + fixed_t frac; + fixed_t fracstep; + int x; + + count = dc_yh - dc_yl; + if (count < 0) + return; + + // low detail, need to scale by 2 + x = dc_x << 1; + + if ((unsigned)x >= SCREENWIDTH || dc_yl < 0 || dc_yh >= SCREENHEIGHT) { + I_Error("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, x); + } + + dest = ylookup[dc_yl] + columnofs[x]; + dest2 = ylookup[dc_yl] + columnofs[x + 1]; + + // Looks familiar. + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + + // Here we do an additional index re-mapping. + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + *dest = dc_colormap[dc_translation[dc_source[frac >> FRACBITS]]]; + *dest2 = dc_colormap[dc_translation[dc_source[frac >> FRACBITS]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} + +// +// R_InitTranslationTables +// Creates the translation tables to map +// the green color ramp to gray, brown, red. +// Assumes a given structure of the PLAYPAL. +// Could be read from a lump instead. +// +void R_InitTranslationTables(void) { + int i; + + translationtables = Z_Malloc(256 * 3, PU_STATIC, 0); + + // translate just the 16 green colors + for (i = 0; i < 256; i++) { + if (i >= 0x70 && i <= 0x7f) { + // map green ramp to gray, brown, red + translationtables[i] = 0x60 + (i & 0xf); + translationtables[i + 256] = 0x40 + (i & 0xf); + translationtables[i + 512] = 0x20 + (i & 0xf); + } else { + // Keep all other colors as is. + translationtables[i] = translationtables[i + 256] = + translationtables[i + 512] = i; + } + } +} + +// +// R_DrawSpan +// With DOOM style restrictions on view orientation, +// the floors and ceilings consist of horizontal slices +// or spans with constant z depth. +// However, rotation around the world z axis is possible, +// thus this mapping, while simpler and faster than +// perspective correct texture mapping, has to traverse +// the texture at an angle in all but a few cases. +// In consequence, flats are not stored by column (like walls), +// and the inner loop has to step in texture space u and v. +// +int ds_y; +int ds_x1; +int ds_x2; + +lighttable_t *ds_colormap; + +fixed_t ds_xfrac; +fixed_t ds_yfrac; +fixed_t ds_xstep; +fixed_t ds_ystep; + +// start of a 64*64 tile image +byte *ds_source; + +// just for profiling +int dscount; + +// +// Draws the actual span. +void R_DrawSpan(void) { + unsigned int position, step; + pixel_t *dest; + int count; + int spot; + unsigned int xtemp, ytemp; + + if (ds_x2 < ds_x1 || ds_x1 < 0 || ds_x2 >= SCREENWIDTH || + (unsigned)ds_y > SCREENHEIGHT) { + I_Error("R_DrawSpan: %i to %i at %i", ds_x1, ds_x2, ds_y); + } + // dscount++; + + // Pack position and step variables into a single 32-bit integer, + // with x in the top 16 bits and y in the bottom 16 bits. For + // each 16-bit part, the top 6 bits are the integer part and the + // bottom 10 bits are the fractional part of the pixel position. + + position = ((ds_xfrac << 10) & 0xffff0000) | ((ds_yfrac >> 6) & 0x0000ffff); + step = ((ds_xstep << 10) & 0xffff0000) | ((ds_ystep >> 6) & 0x0000ffff); + + dest = ylookup[ds_y] + columnofs[ds_x1]; + + // We do not check for zero spans here? + count = ds_x2 - ds_x1; + + do { + // Calculate current texture index in u,v. + ytemp = (position >> 4) & 0x0fc0; + xtemp = (position >> 26); + spot = xtemp | ytemp; + + // Lookup pixel from flat texture tile, + // re-index using light/colormap. + *dest++ = ds_colormap[ds_source[spot]]; + + position += step; + + } while (count--); +} + +// UNUSED. +// Loop unrolled by 4. +#if 0 +void R_DrawSpan (void) +{ + unsigned position, step; + + byte* source; + byte* colormap; + pixel_t* dest; + + unsigned count; + usingned spot; + unsigned value; + unsigned temp; + unsigned xtemp; + unsigned ytemp; + + position = ((ds_xfrac<<10)&0xffff0000) | ((ds_yfrac>>6)&0xffff); + step = ((ds_xstep<<10)&0xffff0000) | ((ds_ystep>>6)&0xffff); + + source = ds_source; + colormap = ds_colormap; + dest = ylookup[ds_y] + columnofs[ds_x1]; + count = ds_x2 - ds_x1 + 1; + + while (count >= 4) + { + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[0] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[1] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[2] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[3] = colormap[source[spot]]; + + count -= 4; + dest += 4; + } + while (count > 0) + { + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + *dest++ = colormap[source[spot]]; + count--; + } +} +#endif + +// +// Again.. +// +void R_DrawSpanLow(void) { + unsigned int position, step; + unsigned int xtemp, ytemp; + pixel_t *dest; + int count; + int spot; + + if (ds_x2 < ds_x1 || ds_x1 < 0 || ds_x2 >= SCREENWIDTH || + (unsigned)ds_y > SCREENHEIGHT) { + I_Error("R_DrawSpan: %i to %i at %i", ds_x1, ds_x2, ds_y); + } + // dscount++; + + position = ((ds_xfrac << 10) & 0xffff0000) | ((ds_yfrac >> 6) & 0x0000ffff); + step = ((ds_xstep << 10) & 0xffff0000) | ((ds_ystep >> 6) & 0x0000ffff); + + count = (ds_x2 - ds_x1); + + // Blocky mode, need to multiply by 2. + ds_x1 <<= 1; + ds_x2 <<= 1; + + dest = ylookup[ds_y] + columnofs[ds_x1]; + + do { + // Calculate current texture index in u,v. + ytemp = (position >> 4) & 0x0fc0; + xtemp = (position >> 26); + spot = xtemp | ytemp; + + // Lowres/blocky mode does it twice, + // while scale is adjusted appropriately. + *dest++ = ds_colormap[ds_source[spot]]; + *dest++ = ds_colormap[ds_source[spot]]; + + position += step; + + } while (count--); +} + +// +// R_InitBuffer +// Creats lookup tables that avoid +// multiplies and other hazzles +// for getting the framebuffer address +// of a pixel to draw. +// +void R_InitBuffer(int width, int height) { + int i; + + // Handle resize, + // e.g. smaller view windows + // with border and/or status bar. + viewwindowx = (SCREENWIDTH - width) >> 1; + + // Column offset. For windows. + for (i = 0; i < width; i++) + columnofs[i] = viewwindowx + i; + + // Samw with base row offset. + if (width == SCREENWIDTH) + viewwindowy = 0; + else + viewwindowy = (SCREENHEIGHT - SBARHEIGHT - height) >> 1; + + // Preclaculate all row offsets. + for (i = 0; i < height; i++) + ylookup[i] = I_VideoBuffer + (i + viewwindowy) * SCREENWIDTH; +} + +// +// R_FillBackScreen +// Fills the back screen with a pattern +// for variable screen sizes +// Also draws a beveled edge. +// +void R_FillBackScreen(void) { + byte *src; + pixel_t *dest; + int x; + int y; + patch_t *patch; + + // DOOM border patch. + char *name1 = ("FLOOR7_2"); + + // DOOM II border patch. + char *name2 = ("GRNROCK"); + + char *name; + + // If we are running full screen, there is no need to do any of this, + // and the background buffer can be freed if it was previously in use. + + if (scaledviewwidth == SCREENWIDTH) { + if (background_buffer != NULL) { + Z_Free(background_buffer); + background_buffer = NULL; + } + + return; + } + + // Allocate the background buffer if necessary + + if (background_buffer == NULL) { + background_buffer = Z_Malloc(SCREENWIDTH * (SCREENHEIGHT - SBARHEIGHT) * + sizeof(*background_buffer), + PU_STATIC, NULL); + } + + if (gamemode == commercial) + name = name2; + else + name = name1; + + src = W_CacheLumpName(name, PU_CACHE); + dest = background_buffer; + + for (y = 0; y < SCREENHEIGHT - SBARHEIGHT; y++) { + for (x = 0; x < SCREENWIDTH / 64; x++) { + memcpy(dest, src + ((y & 63) << 6), 64); + dest += 64; + } + + if (SCREENWIDTH & 63) { + memcpy(dest, src + ((y & 63) << 6), SCREENWIDTH & 63); + dest += (SCREENWIDTH & 63); + } + } + + // Draw screen and bezel; this is done to a separate screen buffer. + + V_UseBuffer(background_buffer); + + patch = W_CacheLumpName(("brdr_t"), PU_CACHE); + + for (x = 0; x < scaledviewwidth; x += 8) + V_DrawPatch(viewwindowx + x, viewwindowy - 8, patch); + patch = W_CacheLumpName(("brdr_b"), PU_CACHE); + + for (x = 0; x < scaledviewwidth; x += 8) + V_DrawPatch(viewwindowx + x, viewwindowy + viewheight, patch); + patch = W_CacheLumpName(("brdr_l"), PU_CACHE); + + for (y = 0; y < viewheight; y += 8) + V_DrawPatch(viewwindowx - 8, viewwindowy + y, patch); + patch = W_CacheLumpName(("brdr_r"), PU_CACHE); + + for (y = 0; y < viewheight; y += 8) + V_DrawPatch(viewwindowx + scaledviewwidth, viewwindowy + y, patch); + + // Draw beveled edge. + V_DrawPatch(viewwindowx - 8, viewwindowy - 8, + W_CacheLumpName(("brdr_tl"), PU_CACHE)); + + V_DrawPatch(viewwindowx + scaledviewwidth, viewwindowy - 8, + W_CacheLumpName(("brdr_tr"), PU_CACHE)); + + V_DrawPatch(viewwindowx - 8, viewwindowy + viewheight, + W_CacheLumpName(("brdr_bl"), PU_CACHE)); + + V_DrawPatch(viewwindowx + scaledviewwidth, viewwindowy + viewheight, + W_CacheLumpName(("brdr_br"), PU_CACHE)); + + V_RestoreBuffer(); +} + +// +// Copy a screen buffer. +// +void R_VideoErase(unsigned ofs, int count) { + // LFB copy. + // This might not be a good idea if memcpy + // is not optiomal, e.g. byte by byte on + // a 32bit CPU, as GNU GCC/Linux libc did + // at one point. + + if (background_buffer != NULL) { + memcpy(I_VideoBuffer + ofs, background_buffer + ofs, + count * sizeof(*I_VideoBuffer)); + } +} + +// +// R_DrawViewBorder +// Draws the border around the view +// for different size windows? +// +void R_DrawViewBorder(void) { + int top; + int side; + int ofs; + int i; + + if (scaledviewwidth == SCREENWIDTH) + return; + + top = ((SCREENHEIGHT - SBARHEIGHT) - viewheight) / 2; + side = (SCREENWIDTH - scaledviewwidth) / 2; + + // copy top and one line of left side + R_VideoErase(0, top * SCREENWIDTH + side); + + // copy one line of right side and bottom + ofs = (viewheight + top) * SCREENWIDTH - side; + R_VideoErase(ofs, top * SCREENWIDTH + side); + + // copy sides using wraparound + ofs = top * SCREENWIDTH + SCREENWIDTH - side; + side <<= 1; + + for (i = 1; i < viewheight; i++) { + R_VideoErase(ofs, side); + ofs += SCREENWIDTH; + } + + // ? + V_MarkRect(0, 0, SCREENWIDTH, SCREENHEIGHT - SBARHEIGHT); +} diff --git a/client/src/r_draw.h b/client/src/r_draw.h new file mode 100644 index 0000000..35358e8 --- /dev/null +++ b/client/src/r_draw.h @@ -0,0 +1,90 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + +#ifndef __R_DRAW__ +#define __R_DRAW__ + +#include "doomtype.h" +#include "m_fixed.h" +#include "r_defs.h" + +extern lighttable_t *dc_colormap; +extern int dc_x; +extern int dc_yl; +extern int dc_yh; +extern fixed_t dc_iscale; +extern fixed_t dc_texturemid; + +// first pixel in a column +extern byte *dc_source; + +// The span blitting interface. +// Hook in assembler or system specific BLT +// here. +void R_DrawColumn(void); +void R_DrawColumnLow(void); + +// The Spectre/Invisibility effect. +void R_DrawFuzzColumn(void); +void R_DrawFuzzColumnLow(void); + +// Draw with color translation tables, +// for player sprite rendering, +// Green/Red/Blue/Indigo shirts. +void R_DrawTranslatedColumn(void); +void R_DrawTranslatedColumnLow(void); + +void R_VideoErase(unsigned ofs, int count); + +extern int ds_y; +extern int ds_x1; +extern int ds_x2; + +extern lighttable_t *ds_colormap; + +extern fixed_t ds_xfrac; +extern fixed_t ds_yfrac; +extern fixed_t ds_xstep; +extern fixed_t ds_ystep; + +// start of a 64*64 tile image +extern byte *ds_source; + +extern byte *translationtables; +extern byte *dc_translation; + +// Span blitting for rows, floor/ceiling. +// No Sepctre effect needed. +void R_DrawSpan(void); + +// Low resolution mode, 160x200? +void R_DrawSpanLow(void); + +void R_InitBuffer(int width, int height); + +// Initialize color translation tables, +// for player rendering etc. +void R_InitTranslationTables(void); + +// Rendering function. +void R_FillBackScreen(void); + +// If the view size is not full screen, draws a border around it. +void R_DrawViewBorder(void); + +#endif diff --git a/client/src/r_local.h b/client/src/r_local.h new file mode 100644 index 0000000..5e43cd1 --- /dev/null +++ b/client/src/r_local.h @@ -0,0 +1,43 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh (R_*) module, global header. +// All the rendering/drawing stuff is here. +// + +#ifndef __R_LOCAL__ +#define __R_LOCAL__ + +// Binary Angles, sine/cosine/atan lookups. +#include "tables.h" + +// Screen size related parameters. +#include "doomdef.h" + +// Include the refresh/render data structs. +#include "r_data.h" + +// +// Separate header file for each module. +// +#include "r_bsp.h" +#include "r_data.h" +#include "r_draw.h" +#include "r_main.h" +#include "r_plane.h" +#include "r_segs.h" +#include "r_things.h" + +#endif // __R_LOCAL__ diff --git a/client/src/r_main.c b/client/src/r_main.c new file mode 100644 index 0000000..22c0e0f --- /dev/null +++ b/client/src/r_main.c @@ -0,0 +1,750 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Rendering main loop and setup functions, +// utility functions (BSP, geometry, trigonometry). +// See tables.c, too. +// + +#include +#include + +#include "d_loop.h" +#include "d_player.h" +#include "doomdata.h" +#include "doomtype.h" +#include "i_video.h" +#include "m_bbox.h" +#include "m_fixed.h" +#include "m_menu.h" +#include "p_mobj.h" +#include "r_bsp.h" +#include "r_data.h" +#include "r_defs.h" +#include "r_draw.h" +#include "r_main.h" +#include "r_plane.h" +#include "r_sky.h" +#include "r_state.h" +#include "r_things.h" +#include "tables.h" + +// Fineangles in the SCREENWIDTH wide window. +#define FIELDOFVIEW 2048 + +int viewangleoffset; + +// increment every time a check is made +int validcount = 1; + +lighttable_t *fixedcolormap; +extern lighttable_t **walllights; + +int centerx; +int centery; + +fixed_t centerxfrac; +fixed_t centeryfrac; +fixed_t projection; + +// just for profiling purposes +int framecount; + +int sscount; +int linecount; +int loopcount; + +fixed_t viewx; +fixed_t viewy; +fixed_t viewz; + +angle_t viewangle; + +fixed_t viewcos; +fixed_t viewsin; + +player_t *viewplayer; + +// 0 = high, 1 = low +int detailshift; + +// +// precalculated math tables +// +angle_t clipangle; + +// The viewangletox[viewangle + FINEANGLES/4] lookup +// maps the visible view angles to screen X coordinates, +// flattening the arc to a flat projection plane. +// There will be many angles mapped to the same X. +int viewangletox[FINEANGLES / 2]; + +// The xtoviewangleangle[] table maps a screen pixel +// to the lowest viewangle that maps back to x ranges +// from clipangle to -clipangle. +angle_t xtoviewangle[SCREENWIDTH + 1]; + +lighttable_t *scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; +lighttable_t *scalelightfixed[MAXLIGHTSCALE]; +lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ]; + +// bumped light from gun blasts +int extralight; + +void (*colfunc)(void); +void (*basecolfunc)(void); +void (*fuzzcolfunc)(void); +void (*transcolfunc)(void); +void (*spanfunc)(void); + +// +// R_AddPointToBox +// Expand a given bbox +// so that it encloses a given point. +// +void R_AddPointToBox(int x, int y, fixed_t *box) { + if (x < box[BOXLEFT]) + box[BOXLEFT] = x; + if (x > box[BOXRIGHT]) + box[BOXRIGHT] = x; + if (y < box[BOXBOTTOM]) + box[BOXBOTTOM] = y; + if (y > box[BOXTOP]) + box[BOXTOP] = y; +} + +// +// R_PointOnSide +// Traverse BSP (sub) tree, +// check point against partition plane. +// Returns side 0 (front) or 1 (back). +// +int R_PointOnSide(fixed_t x, fixed_t y, node_t *node) { + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!node->dx) { + if (x <= node->x) + return node->dy > 0; + + return node->dy < 0; + } + if (!node->dy) { + if (y <= node->y) + return node->dx < 0; + + return node->dx > 0; + } + + dx = (x - node->x); + dy = (y - node->y); + + // Try to quickly decide by looking at sign bits. + if ((node->dy ^ node->dx ^ dx ^ dy) & 0x80000000) { + if ((node->dy ^ dx) & 0x80000000) { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul(node->dy >> FRACBITS, dx); + right = FixedMul(dy, node->dx >> FRACBITS); + + if (right < left) { + // front side + return 0; + } + // back side + return 1; +} + +int R_PointOnSegSide(fixed_t x, fixed_t y, seg_t *line) { + fixed_t lx; + fixed_t ly; + fixed_t ldx; + fixed_t ldy; + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + lx = line->v1->x; + ly = line->v1->y; + + ldx = line->v2->x - lx; + ldy = line->v2->y - ly; + + if (!ldx) { + if (x <= lx) + return ldy > 0; + + return ldy < 0; + } + if (!ldy) { + if (y <= ly) + return ldx < 0; + + return ldx > 0; + } + + dx = (x - lx); + dy = (y - ly); + + // Try to quickly decide by looking at sign bits. + if ((ldy ^ ldx ^ dx ^ dy) & 0x80000000) { + if ((ldy ^ dx) & 0x80000000) { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul(ldy >> FRACBITS, dx); + right = FixedMul(dy, ldx >> FRACBITS); + + if (right < left) { + // front side + return 0; + } + // back side + return 1; +} + +// +// R_PointToAngle +// To get a global angle from cartesian coordinates, +// the coordinates are flipped until they are in +// the first octant of the coordinate system, then +// the y (<=x) is scaled and divided by x to get a +// tangent (slope) value which is looked up in the +// tantoangle[] table. + +// + +angle_t R_PointToAngle(fixed_t x, fixed_t y) { + x -= viewx; + y -= viewy; + + if ((!x) && (!y)) + return 0; + + if (x >= 0) { + // x >=0 + if (y >= 0) { + // y>= 0 + + if (x > y) { + // octant 0 + return tantoangle[SlopeDiv(y, x)]; + } else { + // octant 1 + return ANG90 - 1 - tantoangle[SlopeDiv(x, y)]; + } + } else { + // y<0 + y = -y; + + if (x > y) { + // octant 8 + return -tantoangle[SlopeDiv(y, x)]; + } else { + // octant 7 + return ANG270 + tantoangle[SlopeDiv(x, y)]; + } + } + } else { + // x<0 + x = -x; + + if (y >= 0) { + // y>= 0 + if (x > y) { + // octant 3 + return ANG180 - 1 - tantoangle[SlopeDiv(y, x)]; + } else { + // octant 2 + return ANG90 + tantoangle[SlopeDiv(x, y)]; + } + } else { + // y<0 + y = -y; + + if (x > y) { + // octant 4 + return ANG180 + tantoangle[SlopeDiv(y, x)]; + } else { + // octant 5 + return ANG270 - 1 - tantoangle[SlopeDiv(x, y)]; + } + } + } + return 0; +} + +angle_t R_PointToAngle2(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2) { + viewx = x1; + viewy = y1; + + return R_PointToAngle(x2, y2); +} + +fixed_t R_PointToDist(fixed_t x, fixed_t y) { + int angle; + fixed_t dx; + fixed_t dy; + fixed_t temp; + fixed_t dist; + fixed_t frac; + + dx = abs(x - viewx); + dy = abs(y - viewy); + + if (dy > dx) { + temp = dx; + dx = dy; + dy = temp; + } + + // Fix crashes in udm1.wad + + if (dx != 0) { + frac = FixedDiv(dy, dx); + } else { + frac = 0; + } + + angle = (tantoangle[frac >> DBITS] + ANG90) >> ANGLETOFINESHIFT; + + // use as cosine + dist = FixedDiv(dx, finesine[angle]); + + return dist; +} + +// +// R_InitPointToAngle +// +void R_InitPointToAngle(void) { +// UNUSED - now getting from tables.c +#if 0 + int i; + long t; + float f; +// +// slope (tangent) to angle lookup +// + for (i=0 ; i<=SLOPERANGE ; i++) + { + f = atan( (float)i/SLOPERANGE )/(3.141592657*2); + t = 0xffffffff*f; + tantoangle[i] = t; + } +#endif +} + +// +// R_ScaleFromGlobalAngle +// Returns the texture mapping scale +// for the current line (horizontal span) +// at the given angle. +// rw_distance must be calculated first. +// +fixed_t R_ScaleFromGlobalAngle(angle_t visangle) { + fixed_t scale; + angle_t anglea; + angle_t angleb; + int sinea; + int sineb; + fixed_t num; + int den; + +// UNUSED +#if 0 +{ + fixed_t dist; + fixed_t z; + fixed_t sinv; + fixed_t cosv; + + sinv = finesine[(visangle-rw_normalangle)>>ANGLETOFINESHIFT]; + dist = FixedDiv (rw_distance, sinv); + cosv = finecosine[(viewangle-visangle)>>ANGLETOFINESHIFT]; + z = abs(FixedMul (dist, cosv)); + scale = FixedDiv(projection, z); + return scale; +} +#endif + + anglea = ANG90 + (visangle - viewangle); + angleb = ANG90 + (visangle - rw_normalangle); + + // both sines are allways positive + sinea = finesine[anglea >> ANGLETOFINESHIFT]; + sineb = finesine[angleb >> ANGLETOFINESHIFT]; + num = FixedMul(projection, sineb) << detailshift; + den = FixedMul(rw_distance, sinea); + + if (den > num >> FRACBITS) { + scale = FixedDiv(num, den); + + if (scale > 64 * FRACUNIT) + scale = 64 * FRACUNIT; + else if (scale < 256) + scale = 256; + } else + scale = 64 * FRACUNIT; + + return scale; +} + +// +// R_InitTables +// +void R_InitTables(void) { +// UNUSED: now getting from tables.c +#if 0 + int i; + float a; + float fv; + int t; + + // viewangle tangent table + for (i=0 ; i FRACUNIT * 2) + t = -1; + else if (finetangent[i] < -FRACUNIT * 2) + t = viewwidth + 1; + else { + t = FixedMul(finetangent[i], focallength); + t = (centerxfrac - t + FRACUNIT - 1) >> FRACBITS; + + if (t < -1) + t = -1; + else if (t > viewwidth + 1) + t = viewwidth + 1; + } + viewangletox[i] = t; + } + + // Scan viewangletox[] to generate xtoviewangle[]: + // xtoviewangle will give the smallest view angle + // that maps to x. + for (x = 0; x <= viewwidth; x++) { + i = 0; + while (viewangletox[i] > x) + i++; + xtoviewangle[x] = (i << ANGLETOFINESHIFT) - ANG90; + } + + // Take out the fencepost cases from viewangletox. + for (i = 0; i < FINEANGLES / 2; i++) { + t = FixedMul(finetangent[i], focallength); + t = centerx - t; + + if (viewangletox[i] == -1) + viewangletox[i] = 0; + else if (viewangletox[i] == viewwidth + 1) + viewangletox[i] = viewwidth; + } + + clipangle = xtoviewangle[0]; +} + +// +// R_InitLightTables +// Only inits the zlight table, +// because the scalelight table changes with view size. +// +#define DISTMAP 2 + +void R_InitLightTables(void) { + int i; + int j; + int level; + int startmap; + int scale; + + // Calculate the light levels to use + // for each level / distance combination. + for (i = 0; i < LIGHTLEVELS; i++) { + startmap = ((LIGHTLEVELS - 1 - i) * 2) * NUMCOLORMAPS / LIGHTLEVELS; + for (j = 0; j < MAXLIGHTZ; j++) { + scale = FixedDiv((SCREENWIDTH / 2 * FRACUNIT), (j + 1) << LIGHTZSHIFT); + scale >>= LIGHTSCALESHIFT; + level = startmap - scale / DISTMAP; + + if (level < 0) + level = 0; + + if (level >= NUMCOLORMAPS) + level = NUMCOLORMAPS - 1; + + zlight[i][j] = colormaps + level * 256; + } + } +} + +// +// R_SetViewSize +// Do not really change anything here, +// because it might be in the middle of a refresh. +// The change will take effect next refresh. +// +boolean setsizeneeded; +int setblocks; +int setdetail; + +void R_SetViewSize(int blocks, int detail) { + setsizeneeded = true; + setblocks = blocks; + setdetail = detail; +} + +// +// R_ExecuteSetViewSize +// +void R_ExecuteSetViewSize(void) { + fixed_t cosadj; + fixed_t dy; + int i; + int j; + int level; + int startmap; + + setsizeneeded = false; + + if (setblocks == 11) { + scaledviewwidth = SCREENWIDTH; + viewheight = SCREENHEIGHT; + } else { + scaledviewwidth = setblocks * 32; + viewheight = (setblocks * 168 / 10) & ~7; + } + + detailshift = setdetail; + viewwidth = scaledviewwidth >> detailshift; + + centery = viewheight / 2; + centerx = viewwidth / 2; + centerxfrac = centerx << FRACBITS; + centeryfrac = centery << FRACBITS; + projection = centerxfrac; + + if (!detailshift) { + colfunc = basecolfunc = R_DrawColumn; + fuzzcolfunc = R_DrawFuzzColumn; + transcolfunc = R_DrawTranslatedColumn; + spanfunc = R_DrawSpan; + } else { + colfunc = basecolfunc = R_DrawColumnLow; + fuzzcolfunc = R_DrawFuzzColumnLow; + transcolfunc = R_DrawTranslatedColumnLow; + spanfunc = R_DrawSpanLow; + } + + R_InitBuffer(scaledviewwidth, viewheight); + + R_InitTextureMapping(); + + // psprite scales + pspritescale = FRACUNIT * viewwidth / SCREENWIDTH; + pspriteiscale = FRACUNIT * SCREENWIDTH / viewwidth; + + // thing clipping + for (i = 0; i < viewwidth; i++) + screenheightarray[i] = viewheight; + + // planes + for (i = 0; i < viewheight; i++) { + dy = ((i - viewheight / 2) << FRACBITS) + FRACUNIT / 2; + dy = abs(dy); + yslope[i] = FixedDiv((viewwidth << detailshift) / 2 * FRACUNIT, dy); + } + + for (i = 0; i < viewwidth; i++) { + cosadj = abs(finecosine[xtoviewangle[i] >> ANGLETOFINESHIFT]); + distscale[i] = FixedDiv(FRACUNIT, cosadj); + } + + // Calculate the light levels to use + // for each level / scale combination. + for (i = 0; i < LIGHTLEVELS; i++) { + startmap = ((LIGHTLEVELS - 1 - i) * 2) * NUMCOLORMAPS / LIGHTLEVELS; + for (j = 0; j < MAXLIGHTSCALE; j++) { + level = startmap - j * SCREENWIDTH / (viewwidth << detailshift) / DISTMAP; + + if (level < 0) + level = 0; + + if (level >= NUMCOLORMAPS) + level = NUMCOLORMAPS - 1; + + scalelight[i][j] = colormaps + level * 256; + } + } +} + +// +// R_Init +// + +void R_Init(void) { + R_InitData(); + printf("."); + R_InitPointToAngle(); + printf("."); + R_InitTables(); + // viewwidth / viewheight / detailLevel are set by the defaults + printf("."); + + R_SetViewSize(screenblocks, detailLevel); + R_InitPlanes(); + printf("."); + R_InitLightTables(); + printf("."); + R_InitSkyMap(); + R_InitTranslationTables(); + printf("."); + + framecount = 0; +} + +// +// R_PointInSubsector +// +subsector_t *R_PointInSubsector(fixed_t x, fixed_t y) { + node_t *node; + int side; + int nodenum; + + // single subsector is a special case + if (!numnodes) + return subsectors; + + nodenum = numnodes - 1; + + while (!(nodenum & NF_SUBSECTOR)) { + node = &nodes[nodenum]; + side = R_PointOnSide(x, y, node); + nodenum = node->children[side]; + } + + return &subsectors[nodenum & ~NF_SUBSECTOR]; +} + +// +// R_SetupFrame +// +void R_SetupFrame(player_t *player) { + int i; + + viewplayer = player; + viewx = player->mo->x; + viewy = player->mo->y; + viewangle = player->mo->angle + viewangleoffset; + extralight = player->extralight; + + viewz = player->viewz; + + viewsin = finesine[viewangle >> ANGLETOFINESHIFT]; + viewcos = finecosine[viewangle >> ANGLETOFINESHIFT]; + + sscount = 0; + + if (player->fixedcolormap) { + fixedcolormap = colormaps + player->fixedcolormap * 256; + + walllights = scalelightfixed; + + for (i = 0; i < MAXLIGHTSCALE; i++) + scalelightfixed[i] = fixedcolormap; + } else + fixedcolormap = 0; + + framecount++; + validcount++; +} + +// +// R_RenderView +// +void R_RenderPlayerView(player_t *player) { + R_SetupFrame(player); + + // Clear buffers. + R_ClearClipSegs(); + R_ClearDrawSegs(); + R_ClearPlanes(); + R_ClearSprites(); + + // check for new console commands. + NetUpdate(); + + // The head node is the last node output. + R_RenderBSPNode(numnodes - 1); + + // Check for new console commands. + NetUpdate(); + + R_DrawPlanes(); + + // Check for new console commands. + NetUpdate(); + + R_DrawMasked(); + + // Check for new console commands. + NetUpdate(); +} diff --git a/client/src/r_main.h b/client/src/r_main.h new file mode 100644 index 0000000..a5ab94a --- /dev/null +++ b/client/src/r_main.h @@ -0,0 +1,122 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// + +#ifndef __R_MAIN__ +#define __R_MAIN__ + +#include "d_player.h" +#include "m_fixed.h" +#include "r_defs.h" +#include "tables.h" + +// +// POV related. +// +extern fixed_t viewcos; +extern fixed_t viewsin; + +extern int viewwindowx; +extern int viewwindowy; + +extern int centerx; +extern int centery; + +extern fixed_t centerxfrac; +extern fixed_t centeryfrac; +extern fixed_t projection; + +extern int validcount; + +extern int linecount; +extern int loopcount; + +// +// Lighting LUT. +// Used for z-depth cuing per column/row, +// and other lighting effects (sector ambient, flash). +// + +// Lighting constants. +// Now why not 32 levels here? +#define LIGHTLEVELS 16 +#define LIGHTSEGSHIFT 4 + +#define MAXLIGHTSCALE 48 +#define LIGHTSCALESHIFT 12 +#define MAXLIGHTZ 128 +#define LIGHTZSHIFT 20 + +extern lighttable_t *scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; +extern lighttable_t *scalelightfixed[MAXLIGHTSCALE]; +extern lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ]; + +extern int extralight; +extern lighttable_t *fixedcolormap; + +// Number of diminishing brightness levels. +// There a 0-31, i.e. 32 LUT in the COLORMAP lump. +#define NUMCOLORMAPS 32 + +// Blocky/low detail mode. +// B remove this? +// 0 = high, 1 = low +extern int detailshift; + +// +// Function pointers to switch refresh/drawing functions. +// Used to select shadow mode etc. +// +extern void (*colfunc)(void); +extern void (*transcolfunc)(void); +extern void (*basecolfunc)(void); +extern void (*fuzzcolfunc)(void); +// No shadow effects on floors. +extern void (*spanfunc)(void); + +// +// Utility functions. +int R_PointOnSide(fixed_t x, fixed_t y, node_t *node); + +int R_PointOnSegSide(fixed_t x, fixed_t y, seg_t *line); + +angle_t R_PointToAngle(fixed_t x, fixed_t y); + +angle_t R_PointToAngle2(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2); + +fixed_t R_PointToDist(fixed_t x, fixed_t y); + +fixed_t R_ScaleFromGlobalAngle(angle_t visangle); + +subsector_t *R_PointInSubsector(fixed_t x, fixed_t y); + +void R_AddPointToBox(int x, int y, fixed_t *box); + +// +// REFRESH - the actual rendering functions. +// + +// Called by G_Drawer. +void R_RenderPlayerView(player_t *player); + +// Called by startup code. +void R_Init(void); + +// Called by M_Responder. +void R_SetViewSize(int blocks, int detail); + +#endif diff --git a/client/src/r_plane.c b/client/src/r_plane.c new file mode 100644 index 0000000..5c0626a --- /dev/null +++ b/client/src/r_plane.c @@ -0,0 +1,374 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Here is a core component: drawing the floors and ceilings, +// while maintaining a per column clipping list only. +// Moreover, the sky areas have to be determined. +// + +#include +#include + +#include "doomstat.h" +#include "doomtype.h" +#include "i_system.h" +#include "i_video.h" +#include "m_fixed.h" +#include "r_bsp.h" +#include "r_data.h" +#include "r_defs.h" +#include "r_draw.h" +#include "r_main.h" +#include "r_plane.h" +#include "r_sky.h" +#include "r_state.h" +#include "r_things.h" +#include "tables.h" +#include "w_wad.h" +#include "z_zone.h" + +planefunction_t floorfunc; +planefunction_t ceilingfunc; + +// +// opening +// + +// Here comes the obnoxious "visplane". +#define MAXVISPLANES 128 +visplane_t visplanes[MAXVISPLANES]; +visplane_t *lastvisplane; +visplane_t *floorplane; +visplane_t *ceilingplane; + +// ? +#define MAXOPENINGS SCREENWIDTH * 64 +short openings[MAXOPENINGS]; +short *lastopening; + +// +// Clip values are the solid pixel bounding the range. +// floorclip starts out SCREENHEIGHT +// ceilingclip starts out -1 +// +short floorclip[SCREENWIDTH]; +short ceilingclip[SCREENWIDTH]; + +// +// spanstart holds the start of a plane span +// initialized to 0 at start +// +int spanstart[SCREENHEIGHT]; +int spanstop[SCREENHEIGHT]; + +// +// texture mapping +// +lighttable_t **planezlight; +fixed_t planeheight; + +fixed_t yslope[SCREENHEIGHT]; +fixed_t distscale[SCREENWIDTH]; +fixed_t basexscale; +fixed_t baseyscale; + +fixed_t cachedheight[SCREENHEIGHT]; +fixed_t cacheddistance[SCREENHEIGHT]; +fixed_t cachedxstep[SCREENHEIGHT]; +fixed_t cachedystep[SCREENHEIGHT]; + +// +// R_InitPlanes +// Only at game startup. +// +void R_InitPlanes(void) { + // Doh! +} + +// +// R_MapPlane +// +// Uses global vars: +// planeheight +// ds_source +// basexscale +// baseyscale +// viewx +// viewy +// +// BASIC PRIMITIVE +// +void R_MapPlane(int y, int x1, int x2) { + angle_t angle; + fixed_t distance; + fixed_t length; + unsigned index; + + if (x2 < x1 || x1 < 0 || x2 >= viewwidth || y > viewheight) { + I_Error("R_MapPlane: %i, %i at %i", x1, x2, y); + } + + if (planeheight != cachedheight[y]) { + cachedheight[y] = planeheight; + distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]); + ds_xstep = cachedxstep[y] = FixedMul(distance, basexscale); + ds_ystep = cachedystep[y] = FixedMul(distance, baseyscale); + } else { + distance = cacheddistance[y]; + ds_xstep = cachedxstep[y]; + ds_ystep = cachedystep[y]; + } + + length = FixedMul(distance, distscale[x1]); + angle = (viewangle + xtoviewangle[x1]) >> ANGLETOFINESHIFT; + ds_xfrac = viewx + FixedMul(finecosine[angle], length); + ds_yfrac = -viewy - FixedMul(finesine[angle], length); + + if (fixedcolormap) + ds_colormap = fixedcolormap; + else { + index = distance >> LIGHTZSHIFT; + + if (index >= MAXLIGHTZ) + index = MAXLIGHTZ - 1; + + ds_colormap = planezlight[index]; + } + + ds_y = y; + ds_x1 = x1; + ds_x2 = x2; + + // high or low detail + spanfunc(); +} + +// +// R_ClearPlanes +// At begining of frame. +// +void R_ClearPlanes(void) { + int i; + angle_t angle; + + // opening / clipping determination + for (i = 0; i < viewwidth; i++) { + floorclip[i] = viewheight; + ceilingclip[i] = -1; + } + + lastvisplane = visplanes; + lastopening = openings; + + // texture calculation + memset(cachedheight, 0, sizeof(cachedheight)); + + // left to right mapping + angle = (viewangle - ANG90) >> ANGLETOFINESHIFT; + + // scale will be unit scale at SCREENWIDTH/2 distance + basexscale = FixedDiv(finecosine[angle], centerxfrac); + baseyscale = -FixedDiv(finesine[angle], centerxfrac); +} + +// +// R_FindPlane +// +visplane_t *R_FindPlane(fixed_t height, int picnum, int lightlevel) { + visplane_t *check; + + if (picnum == skyflatnum) { + height = 0; // all skys map together + lightlevel = 0; + } + + for (check = visplanes; check < lastvisplane; check++) { + if (height == check->height && picnum == check->picnum && + lightlevel == check->lightlevel) { + break; + } + } + + if (check < lastvisplane) + return check; + + if (lastvisplane - visplanes == MAXVISPLANES) + I_Error("R_FindPlane: no more visplanes"); + + lastvisplane++; + + check->height = height; + check->picnum = picnum; + check->lightlevel = lightlevel; + check->minx = SCREENWIDTH; + check->maxx = -1; + + memset(check->top, 0xff, sizeof(check->top)); + + return check; +} + +// +// R_CheckPlane +// +visplane_t *R_CheckPlane(visplane_t *pl, int start, int stop) { + int intrl; + int intrh; + int unionl; + int unionh; + int x; + + if (start < pl->minx) { + intrl = pl->minx; + unionl = start; + } else { + unionl = pl->minx; + intrl = start; + } + + if (stop > pl->maxx) { + intrh = pl->maxx; + unionh = stop; + } else { + unionh = pl->maxx; + intrh = stop; + } + + for (x = intrl; x <= intrh; x++) + if (pl->top[x] != 0xff) + break; + + if (x > intrh) { + pl->minx = unionl; + pl->maxx = unionh; + + // use the same one + return pl; + } + + // make a new visplane + lastvisplane->height = pl->height; + lastvisplane->picnum = pl->picnum; + lastvisplane->lightlevel = pl->lightlevel; + + pl = lastvisplane++; + pl->minx = start; + pl->maxx = stop; + + memset(pl->top, 0xff, sizeof(pl->top)); + + return pl; +} + +// +// R_MakeSpans +// +void R_MakeSpans(int x, int t1, int b1, int t2, int b2) { + while (t1 < t2 && t1 <= b1) { + R_MapPlane(t1, spanstart[t1], x - 1); + t1++; + } + while (b1 > b2 && b1 >= t1) { + R_MapPlane(b1, spanstart[b1], x - 1); + b1--; + } + + while (t2 < t1 && t2 <= b2) { + spanstart[t2] = x; + t2++; + } + while (b2 > b1 && b2 >= t2) { + spanstart[b2] = x; + b2--; + } +} + +// +// R_DrawPlanes +// At the end of each frame. +// +void R_DrawPlanes(void) { + visplane_t *pl; + int light; + int x; + int stop; + int angle; + int lumpnum; + + if (ds_p - drawsegs > MAXDRAWSEGS) + I_Error("R_DrawPlanes: drawsegs overflow (%i)", ds_p - drawsegs); + + if (lastvisplane - visplanes > MAXVISPLANES) + I_Error("R_DrawPlanes: visplane overflow (%i)", lastvisplane - visplanes); + + if (lastopening - openings > MAXOPENINGS) + I_Error("R_DrawPlanes: opening overflow (%i)", lastopening - openings); + + for (pl = visplanes; pl < lastvisplane; pl++) { + if (pl->minx > pl->maxx) + continue; + + // sky flat + if (pl->picnum == skyflatnum) { + dc_iscale = pspriteiscale >> detailshift; + + // Sky is allways drawn full bright, + // i.e. colormaps[0] is used. + // Because of this hack, sky is not affected + // by INVUL inverse mapping. + dc_colormap = colormaps; + dc_texturemid = skytexturemid; + for (x = pl->minx; x <= pl->maxx; x++) { + dc_yl = pl->top[x]; + dc_yh = pl->bottom[x]; + + if (dc_yl <= dc_yh) { + angle = (viewangle + xtoviewangle[x]) >> ANGLETOSKYSHIFT; + dc_x = x; + dc_source = R_GetColumn(skytexture, angle); + colfunc(); + } + } + continue; + } + + // regular flat + lumpnum = firstflat + flattranslation[pl->picnum]; + ds_source = W_CacheLumpNum(lumpnum, PU_STATIC); + + planeheight = abs(pl->height - viewz); + light = (pl->lightlevel >> LIGHTSEGSHIFT) + extralight; + + if (light >= LIGHTLEVELS) + light = LIGHTLEVELS - 1; + + if (light < 0) + light = 0; + + planezlight = zlight[light]; + + pl->top[pl->maxx + 1] = 0xff; + pl->top[pl->minx - 1] = 0xff; + + stop = pl->maxx + 1; + + for (x = pl->minx; x <= stop; x++) { + R_MakeSpans(x, pl->top[x - 1], pl->bottom[x - 1], pl->top[x], + pl->bottom[x]); + } + + W_ReleaseLumpNum(lumpnum); + } +} diff --git a/client/src/r_plane.h b/client/src/r_plane.h new file mode 100644 index 0000000..2f8ff90 --- /dev/null +++ b/client/src/r_plane.h @@ -0,0 +1,53 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh, visplane stuff (floor, ceilings). +// + +#ifndef __R_PLANE__ +#define __R_PLANE__ + +#include "i_video.h" +#include "m_fixed.h" +#include "r_defs.h" + +// Visplane related. +extern short *lastopening; + +typedef void (*planefunction_t)(int top, int bottom); + +extern planefunction_t floorfunc; +extern planefunction_t ceilingfunc_t; + +extern short floorclip[SCREENWIDTH]; +extern short ceilingclip[SCREENWIDTH]; + +extern fixed_t yslope[SCREENHEIGHT]; +extern fixed_t distscale[SCREENWIDTH]; + +void R_InitPlanes(void); +void R_ClearPlanes(void); + +void R_MapPlane(int y, int x1, int x2); + +void R_MakeSpans(int x, int t1, int b1, int t2, int b2); + +void R_DrawPlanes(void); + +visplane_t *R_FindPlane(fixed_t height, int picnum, int lightlevel); + +visplane_t *R_CheckPlane(visplane_t *pl, int start, int stop); + +#endif diff --git a/client/src/r_segs.c b/client/src/r_segs.c new file mode 100644 index 0000000..c276827 --- /dev/null +++ b/client/src/r_segs.c @@ -0,0 +1,647 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// All the clipping: columns, horizontal spans, sky columns. +// + +#include +#include +#include +#include + +#include "doomdata.h" +#include "doomstat.h" +#include "doomtype.h" +#include "i_system.h" +#include "m_fixed.h" +#include "r_bsp.h" +#include "r_data.h" +#include "r_defs.h" +#include "r_draw.h" +#include "r_main.h" +#include "r_plane.h" +#include "r_state.h" +#include "r_things.h" +#include "tables.h" +#include "v_patch.h" + +#include "ipu_transfer.h" + +// OPTIMIZE: closed two sided lines as single sided + +// True if any of the segs textures might be visible. +boolean segtextured; + +// False if the back side is the same plane. +boolean markfloor; +boolean markceiling; + +boolean maskedtexture; +int toptexture; +int bottomtexture; +int midtexture; + +angle_t rw_normalangle; +// angle to line origin +int rw_angle1; + +// +// regular wall +// +int rw_x; +int rw_stopx; +angle_t rw_centerangle; +fixed_t rw_offset; +fixed_t rw_distance; +fixed_t rw_scale; +fixed_t rw_scalestep; +fixed_t rw_midtexturemid; +fixed_t rw_toptexturemid; +fixed_t rw_bottomtexturemid; + +int worldtop; +int worldbottom; +int worldhigh; +int worldlow; + +fixed_t pixhigh; +fixed_t pixlow; +fixed_t pixhighstep; +fixed_t pixlowstep; + +fixed_t topfrac; +fixed_t topstep; + +fixed_t bottomfrac; +fixed_t bottomstep; + +lighttable_t **walllights; + +short *maskedtexturecol; + +// +// R_RenderMaskedSegRange +// +void R_RenderMaskedSegRange(drawseg_t *ds, int x1, int x2) { + unsigned index; + column_t *col; + int lightnum; + int texnum; + + // Calculate light table. + // Use different light tables + // for horizontal / vertical / diagonal. Diagonal? + // OPTIMIZE: get rid of LIGHTSEGSHIFT globally + curline = ds->curline; + frontsector = curline->frontsector; + backsector = curline->backsector; + texnum = texturetranslation[curline->sidedef->midtexture]; + + lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT) + extralight; + + if (curline->v1->y == curline->v2->y) + lightnum--; + else if (curline->v1->x == curline->v2->x) + lightnum++; + + if (lightnum < 0) + walllights = scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + walllights = scalelight[LIGHTLEVELS - 1]; + else + walllights = scalelight[lightnum]; + + maskedtexturecol = ds->maskedtexturecol; + + rw_scalestep = ds->scalestep; + spryscale = ds->scale1 + (x1 - ds->x1) * rw_scalestep; + mfloorclip = ds->sprbottomclip; + mceilingclip = ds->sprtopclip; + + // find positioning + if (curline->linedef->flags & ML_DONTPEGBOTTOM) { + dc_texturemid = frontsector->floorheight > backsector->floorheight + ? frontsector->floorheight + : backsector->floorheight; + dc_texturemid = dc_texturemid + textureheight[texnum] - viewz; + } else { + dc_texturemid = frontsector->ceilingheight < backsector->ceilingheight + ? frontsector->ceilingheight + : backsector->ceilingheight; + dc_texturemid = dc_texturemid - viewz; + } + dc_texturemid += curline->sidedef->rowoffset; + + if (fixedcolormap) + dc_colormap = fixedcolormap; + + // draw the columns + for (dc_x = x1; dc_x <= x2; dc_x++) { + // calculate lighting + if (maskedtexturecol[dc_x] != SHRT_MAX) { + if (!fixedcolormap) { + index = spryscale >> LIGHTSCALESHIFT; + + if (index >= MAXLIGHTSCALE) + index = MAXLIGHTSCALE - 1; + + dc_colormap = walllights[index]; + } + + sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); + dc_iscale = 0xffffffffu / (unsigned)spryscale; + + // draw the texture + col = + (column_t *)((byte *)R_GetColumn(texnum, maskedtexturecol[dc_x]) - 3); + + R_DrawMaskedColumn(col); + maskedtexturecol[dc_x] = SHRT_MAX; + } + spryscale += rw_scalestep; + } +} + +// +// R_RenderSegLoop +// Draws zero, one, or two textures (and possibly a masked +// texture) for walls. +// Can draw or mark the starting pixel of floor and ceiling +// textures. +// CALLED: CORE LOOPING ROUTINE. +// +#define HEIGHTBITS 12 +#define HEIGHTUNIT (1 << HEIGHTBITS) + +void R_RenderSegLoop(void) { + angle_t angle; + unsigned index; + int yl; + int yh; + int mid; + fixed_t texturecolumn; + int top; + int bottom; + + for (; rw_x < rw_stopx; rw_x++) { + // mark floor / ceiling areas + yl = (topfrac + HEIGHTUNIT - 1) >> HEIGHTBITS; + + // no space above wall? + if (yl < ceilingclip[rw_x] + 1) + yl = ceilingclip[rw_x] + 1; + + if (markceiling) { + top = ceilingclip[rw_x] + 1; + bottom = yl - 1; + + if (bottom >= floorclip[rw_x]) + bottom = floorclip[rw_x] - 1; + + if (top <= bottom) { + ceilingplane->top[rw_x] = top; + ceilingplane->bottom[rw_x] = bottom; + } + } + + yh = bottomfrac >> HEIGHTBITS; + + if (yh >= floorclip[rw_x]) + yh = floorclip[rw_x] - 1; + + if (markfloor) { + top = yh + 1; + bottom = floorclip[rw_x] - 1; + if (top <= ceilingclip[rw_x]) + top = ceilingclip[rw_x] + 1; + if (top <= bottom) { + floorplane->top[rw_x] = top; + floorplane->bottom[rw_x] = bottom; + } + } + + // texturecolumn and lighting are independent of wall tiers + if (segtextured) { + // calculate texture offset + angle = (rw_centerangle + xtoviewangle[rw_x]) >> ANGLETOFINESHIFT; + texturecolumn = rw_offset - FixedMul(finetangent[angle], rw_distance); + texturecolumn >>= FRACBITS; + // calculate lighting + index = rw_scale >> LIGHTSCALESHIFT; + + if (index >= MAXLIGHTSCALE) + index = MAXLIGHTSCALE - 1; + + dc_colormap = walllights[index]; + dc_x = rw_x; + dc_iscale = 0xffffffffu / (unsigned)rw_scale; + } else { + // purely to shut up the compiler + + texturecolumn = 0; + } + + // draw the wall tiers + if (midtexture) { + // single sided line + dc_yl = yl; + dc_yh = yh; + dc_texturemid = rw_midtexturemid; + dc_source = R_GetColumn(midtexture, texturecolumn); + colfunc(); + ceilingclip[rw_x] = viewheight; + floorclip[rw_x] = -1; + } else { + // two sided line + if (toptexture) { + // top wall + mid = pixhigh >> HEIGHTBITS; + pixhigh += pixhighstep; + + if (mid >= floorclip[rw_x]) + mid = floorclip[rw_x] - 1; + + if (mid >= yl) { + dc_yl = yl; + dc_yh = mid; + dc_texturemid = rw_toptexturemid; + dc_source = R_GetColumn(toptexture, texturecolumn); + colfunc(); + ceilingclip[rw_x] = mid; + } else + ceilingclip[rw_x] = yl - 1; + } else { + // no top wall + if (markceiling) + ceilingclip[rw_x] = yl - 1; + } + + if (bottomtexture) { + // bottom wall + mid = (pixlow + HEIGHTUNIT - 1) >> HEIGHTBITS; + pixlow += pixlowstep; + + // no space above wall? + if (mid <= ceilingclip[rw_x]) + mid = ceilingclip[rw_x] + 1; + + if (mid <= yh) { + dc_yl = mid; + dc_yh = yh; + dc_texturemid = rw_bottomtexturemid; + dc_source = R_GetColumn(bottomtexture, texturecolumn); + colfunc(); + floorclip[rw_x] = mid; + } else + floorclip[rw_x] = yh + 1; + } else { + // no bottom wall + if (markfloor) + floorclip[rw_x] = yh + 1; + } + + if (maskedtexture) { + // save texturecol + // for backdrawing of masked mid texture + maskedtexturecol[rw_x] = texturecolumn; + } + } + + rw_scale += rw_scalestep; + topfrac += topstep; + bottomfrac += bottomstep; + } +} + +// +// R_StoreWallRange +// A wall segment will be drawn +// between start and stop pixels (inclusive). +// +void R_StoreWallRange(int start, int stop) { + fixed_t hyp; + fixed_t sineval; + angle_t distangle, offsetangle; + fixed_t vtop; + int lightnum; + + // don't overflow and crash + if (ds_p == &drawsegs[MAXDRAWSEGS]) + return; + + if (start >= viewwidth || start > stop) + I_Error("Bad R_RenderWallRange: %i to %i", start, stop); + + sidedef = curline->sidedef; + linedef = curline->linedef; + + if (!(linedef->flags & ML_MAPPED)) { // JOSEF: send new visible lines to IPU + // mark the segment as visible for auto map + linedef->flags |= ML_MAPPED; + IPU_NotifyLineMapped(linedef); + } + + // calculate rw_distance for scale calculation + rw_normalangle = curline->angle + ANG90; + offsetangle = abs((int)(rw_normalangle - rw_angle1)); + + if (offsetangle > ANG90) + offsetangle = ANG90; + + distangle = ANG90 - offsetangle; + hyp = R_PointToDist(curline->v1->x, curline->v1->y); + sineval = finesine[distangle >> ANGLETOFINESHIFT]; + rw_distance = FixedMul(hyp, sineval); + + ds_p->x1 = rw_x = start; + ds_p->x2 = stop; + ds_p->curline = curline; + rw_stopx = stop + 1; + + // calculate scale at both ends and step + ds_p->scale1 = rw_scale = + R_ScaleFromGlobalAngle(viewangle + xtoviewangle[start]); + + if (stop > start) { + ds_p->scale2 = R_ScaleFromGlobalAngle(viewangle + xtoviewangle[stop]); + ds_p->scalestep = rw_scalestep = (ds_p->scale2 - rw_scale) / (stop - start); + } else { +// UNUSED: try to fix the stretched line bug +#if 0 + if (rw_distance < FRACUNIT/2) + { + fixed_t trx,try; + fixed_t gxt,gyt; + + trx = curline->v1->x - viewx; + try = curline->v1->y - viewy; + + gxt = FixedMul(trx,viewcos); + gyt = -FixedMul(try,viewsin); + ds_p->scale1 = FixedDiv(projection, gxt-gyt)<scale2 = ds_p->scale1; + } + + // calculate texture boundaries + // and decide if floor / ceiling marks are needed + worldtop = frontsector->ceilingheight - viewz; + worldbottom = frontsector->floorheight - viewz; + + midtexture = toptexture = bottomtexture = maskedtexture = 0; + ds_p->maskedtexturecol = NULL; + + if (!backsector) { + // single sided line + midtexture = texturetranslation[sidedef->midtexture]; + // a single sided line is terminal, so it must mark ends + markfloor = markceiling = true; + if (linedef->flags & ML_DONTPEGBOTTOM) { + vtop = frontsector->floorheight + textureheight[sidedef->midtexture]; + // bottom of texture at bottom + rw_midtexturemid = vtop - viewz; + } else { + // top of texture at top + rw_midtexturemid = worldtop; + } + rw_midtexturemid += sidedef->rowoffset; + + ds_p->silhouette = SIL_BOTH; + ds_p->sprtopclip = screenheightarray; + ds_p->sprbottomclip = negonearray; + ds_p->bsilheight = INT_MAX; + ds_p->tsilheight = INT_MIN; + } else { + // two sided line + ds_p->sprtopclip = ds_p->sprbottomclip = NULL; + ds_p->silhouette = 0; + + if (frontsector->floorheight > backsector->floorheight) { + ds_p->silhouette = SIL_BOTTOM; + ds_p->bsilheight = frontsector->floorheight; + } else if (backsector->floorheight > viewz) { + ds_p->silhouette = SIL_BOTTOM; + ds_p->bsilheight = INT_MAX; + // ds_p->sprbottomclip = negonearray; + } + + if (frontsector->ceilingheight < backsector->ceilingheight) { + ds_p->silhouette |= SIL_TOP; + ds_p->tsilheight = frontsector->ceilingheight; + } else if (backsector->ceilingheight < viewz) { + ds_p->silhouette |= SIL_TOP; + ds_p->tsilheight = INT_MIN; + // ds_p->sprtopclip = screenheightarray; + } + + if (backsector->ceilingheight <= frontsector->floorheight) { + ds_p->sprbottomclip = negonearray; + ds_p->bsilheight = INT_MAX; + ds_p->silhouette |= SIL_BOTTOM; + } + + if (backsector->floorheight >= frontsector->ceilingheight) { + ds_p->sprtopclip = screenheightarray; + ds_p->tsilheight = INT_MIN; + ds_p->silhouette |= SIL_TOP; + } + + worldhigh = backsector->ceilingheight - viewz; + worldlow = backsector->floorheight - viewz; + + // hack to allow height changes in outdoor areas + if (frontsector->ceilingpic == skyflatnum && + backsector->ceilingpic == skyflatnum) { + worldtop = worldhigh; + } + + if (worldlow != worldbottom || + backsector->floorpic != frontsector->floorpic || + backsector->lightlevel != frontsector->lightlevel) { + markfloor = true; + } else { + // same plane on both sides + markfloor = false; + } + + if (worldhigh != worldtop || + backsector->ceilingpic != frontsector->ceilingpic || + backsector->lightlevel != frontsector->lightlevel) { + markceiling = true; + } else { + // same plane on both sides + markceiling = false; + } + + if (backsector->ceilingheight <= frontsector->floorheight || + backsector->floorheight >= frontsector->ceilingheight) { + // closed door + markceiling = markfloor = true; + } + + if (worldhigh < worldtop) { + // top texture + toptexture = texturetranslation[sidedef->toptexture]; + if (linedef->flags & ML_DONTPEGTOP) { + // top of texture at top + rw_toptexturemid = worldtop; + } else { + vtop = backsector->ceilingheight + textureheight[sidedef->toptexture]; + + // bottom of texture + rw_toptexturemid = vtop - viewz; + } + } + if (worldlow > worldbottom) { + // bottom texture + bottomtexture = texturetranslation[sidedef->bottomtexture]; + + if (linedef->flags & ML_DONTPEGBOTTOM) { + // bottom of texture at bottom + // top of texture at top + rw_bottomtexturemid = worldtop; + } else // top of texture at top + rw_bottomtexturemid = worldlow; + } + rw_toptexturemid += sidedef->rowoffset; + rw_bottomtexturemid += sidedef->rowoffset; + + // allocate space for masked texture tables + if (sidedef->midtexture) { + // masked midtexture + maskedtexture = true; + ds_p->maskedtexturecol = maskedtexturecol = lastopening - rw_x; + lastopening += rw_stopx - rw_x; + } + } + + // calculate rw_offset (only needed for textured lines) + segtextured = midtexture | toptexture | bottomtexture | maskedtexture; + + if (segtextured) { + offsetangle = rw_normalangle - rw_angle1; + + if (offsetangle > ANG180) + offsetangle = -offsetangle; + + if (offsetangle > ANG90) + offsetangle = ANG90; + + sineval = finesine[offsetangle >> ANGLETOFINESHIFT]; + rw_offset = FixedMul(hyp, sineval); + + if (rw_normalangle - rw_angle1 < ANG180) + rw_offset = -rw_offset; + + rw_offset += sidedef->textureoffset + curline->offset; + rw_centerangle = ANG90 + viewangle - rw_normalangle; + + // calculate light table + // use different light tables + // for horizontal / vertical / diagonal + // OPTIMIZE: get rid of LIGHTSEGSHIFT globally + if (!fixedcolormap) { + lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT) + extralight; + + if (curline->v1->y == curline->v2->y) + lightnum--; + else if (curline->v1->x == curline->v2->x) + lightnum++; + + if (lightnum < 0) + walllights = scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + walllights = scalelight[LIGHTLEVELS - 1]; + else + walllights = scalelight[lightnum]; + } + } + + // if a floor / ceiling plane is on the wrong side + // of the view plane, it is definitely invisible + // and doesn't need to be marked. + + if (frontsector->floorheight >= viewz) { + // above view plane + markfloor = false; + } + + if (frontsector->ceilingheight <= viewz && + frontsector->ceilingpic != skyflatnum) { + // below view plane + markceiling = false; + } + + // calculate incremental stepping values for texture edges + worldtop >>= 4; + worldbottom >>= 4; + + topstep = -FixedMul(rw_scalestep, worldtop); + topfrac = (centeryfrac >> 4) - FixedMul(worldtop, rw_scale); + + bottomstep = -FixedMul(rw_scalestep, worldbottom); + bottomfrac = (centeryfrac >> 4) - FixedMul(worldbottom, rw_scale); + + if (backsector) { + worldhigh >>= 4; + worldlow >>= 4; + + if (worldhigh < worldtop) { + pixhigh = (centeryfrac >> 4) - FixedMul(worldhigh, rw_scale); + pixhighstep = -FixedMul(rw_scalestep, worldhigh); + } + + if (worldlow > worldbottom) { + pixlow = (centeryfrac >> 4) - FixedMul(worldlow, rw_scale); + pixlowstep = -FixedMul(rw_scalestep, worldlow); + } + } + + // render it + if (markceiling) + ceilingplane = R_CheckPlane(ceilingplane, rw_x, rw_stopx - 1); + + if (markfloor) + floorplane = R_CheckPlane(floorplane, rw_x, rw_stopx - 1); + + R_RenderSegLoop(); + + // save sprite clipping info + if (((ds_p->silhouette & SIL_TOP) || maskedtexture) && !ds_p->sprtopclip) { + memcpy(lastopening, ceilingclip + start, + sizeof(*lastopening) * (rw_stopx - start)); + ds_p->sprtopclip = lastopening - start; + lastopening += rw_stopx - start; + } + + if (((ds_p->silhouette & SIL_BOTTOM) || maskedtexture) && + !ds_p->sprbottomclip) { + memcpy(lastopening, floorclip + start, + sizeof(*lastopening) * (rw_stopx - start)); + ds_p->sprbottomclip = lastopening - start; + lastopening += rw_stopx - start; + } + + if (maskedtexture && !(ds_p->silhouette & SIL_TOP)) { + ds_p->silhouette |= SIL_TOP; + ds_p->tsilheight = INT_MIN; + } + if (maskedtexture && !(ds_p->silhouette & SIL_BOTTOM)) { + ds_p->silhouette |= SIL_BOTTOM; + ds_p->bsilheight = INT_MAX; + } + ds_p++; +} diff --git a/client/src/r_segs.h b/client/src/r_segs.h new file mode 100644 index 0000000..b22e539 --- /dev/null +++ b/client/src/r_segs.h @@ -0,0 +1,26 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh module, drawing LineSegs from BSP. +// + +#ifndef __R_SEGS__ +#define __R_SEGS__ + +#include "r_defs.h" + +void R_RenderMaskedSegRange(drawseg_t *ds, int x1, int x2); + +#endif diff --git a/client/src/r_sky.c b/client/src/r_sky.c new file mode 100644 index 0000000..8cd326a --- /dev/null +++ b/client/src/r_sky.c @@ -0,0 +1,42 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Sky rendering. The DOOM sky is a texture map like any +// wall, wrapping around. A 1024 columns equal 360 degrees. +// The default sky map is 256 columns and repeats 4 times +// on a 320 screen? +// +// + +#include "i_video.h" +// Needed for FRACUNIT. +#include "m_fixed.h" +#include "r_sky.h" + +// +// sky mapping +// +int skyflatnum; +int skytexture; +int skytexturemid; + +// +// R_InitSkyMap +// Called whenever the view size changes. +// +void R_InitSkyMap(void) { + // skyflatnum = R_FlatNumForName ( SKYFLATNAME ); + skytexturemid = SCREENHEIGHT / 2 * FRACUNIT; +} diff --git a/client/src/r_sky.h b/client/src/r_sky.h new file mode 100644 index 0000000..0f1ca03 --- /dev/null +++ b/client/src/r_sky.h @@ -0,0 +1,34 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Sky rendering. +// + +#ifndef __R_SKY__ +#define __R_SKY__ + +// SKY, store the number for name. +#define SKYFLATNAME "F_SKY1" + +// The sky map is 256*128*4 maps. +#define ANGLETOSKYSHIFT 22 + +extern int skytexture; +extern int skytexturemid; + +// Called whenever the view size changes. +void R_InitSkyMap(void); + +#endif diff --git a/client/src/r_state.h b/client/src/r_state.h new file mode 100644 index 0000000..4c2e5be --- /dev/null +++ b/client/src/r_state.h @@ -0,0 +1,113 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh/render internal state variables (global). +// + +#ifndef __R_STATE__ +#define __R_STATE__ + +// Need data structure definitions. +#include "d_player.h" +#include "r_data.h" + +// +// Refresh internal data structures, +// for rendering. +// + +// needed for texture pegging +extern fixed_t *textureheight; + +// needed for pre rendering (fracs) +extern fixed_t *spritewidth; + +extern fixed_t *spriteoffset; +extern fixed_t *spritetopoffset; + +extern lighttable_t *colormaps; + +extern int viewwidth; +extern int scaledviewwidth; +extern int viewheight; + +extern int firstflat; + +// for global animation +extern int *flattranslation; +extern int *texturetranslation; + +// Sprite.... +extern int firstspritelump; +extern int lastspritelump; +extern int numspritelumps; + +// +// Lookup tables for map data. +// +extern int numsprites; +extern spritedef_t *sprites; + +extern int numvertexes; +extern vertex_t *vertexes; + +extern int numsegs; +extern seg_t *segs; + +extern int numsectors; +extern sector_t *sectors; + +extern int numsubsectors; +extern subsector_t *subsectors; + +extern int numnodes; +extern node_t *nodes; + +extern int numlines; +extern line_t *lines; + +extern int numsides; +extern side_t *sides; + +// +// POV data. +// +extern fixed_t viewx; +extern fixed_t viewy; +extern fixed_t viewz; + +extern angle_t viewangle; +extern player_t *viewplayer; + +// ? +extern angle_t clipangle; + +extern int viewangletox[FINEANGLES / 2]; +extern angle_t xtoviewangle[SCREENWIDTH + 1]; +// extern fixed_t finetangent[FINEANGLES/2]; + +extern fixed_t rw_distance; +extern angle_t rw_normalangle; + +// angle to line origin +extern int rw_angle1; + +// Segs count? +extern int sscount; + +extern visplane_t *floorplane; +extern visplane_t *ceilingplane; + +#endif diff --git a/client/src/r_things.c b/client/src/r_things.c new file mode 100644 index 0000000..be244e1 --- /dev/null +++ b/client/src/r_things.c @@ -0,0 +1,847 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh of things, i.e. objects represented by sprites. +// + +#include +#include +#include +#include +#include + +#include "d_player.h" +#include "doomdef.h" +#include "doomstat.h" +#include "doomtype.h" +#include "i_swap.h" +#include "i_system.h" +#include "i_video.h" +#include "info.h" +#include "m_fixed.h" +#include "p_mobj.h" +#include "p_pspr.h" +#include "r_bsp.h" +#include "r_defs.h" +#include "r_draw.h" +#include "r_main.h" +#include "r_segs.h" +#include "r_state.h" +#include "r_things.h" +#include "tables.h" +#include "v_patch.h" +#include "w_wad.h" +#include "z_zone.h" + +#define MINZ (FRACUNIT * 4) +#define BASEYCENTER (SCREENHEIGHT / 2) + +// void R_DrawColumn (void); +// void R_DrawFuzzColumn (void); + +typedef struct { + int x1; + int x2; + + int column; + int topclip; + int bottomclip; + +} maskdraw_t; + +// +// Sprite rotation 0 is facing the viewer, +// rotation 1 is one angle turn CLOCKWISE around the axis. +// This is not the same as the angle, +// which increases counter clockwise (protractor). +// There was a lot of stuff grabbed wrong, so I changed it... +// +fixed_t pspritescale; +fixed_t pspriteiscale; + +lighttable_t **spritelights; + +// constant arrays +// used for psprite clipping and initializing clipping +short negonearray[SCREENWIDTH]; +short screenheightarray[SCREENWIDTH]; + +// +// INITIALIZATION FUNCTIONS +// + +// variables used to look up +// and range check thing_t sprites patches +spritedef_t *sprites; +int numsprites; + +spriteframe_t sprtemp[29]; +int maxframe; +char *spritename; + +// +// R_InstallSpriteLump +// Local function for R_InitSprites. +// +void R_InstallSpriteLump(int lump, unsigned frame, unsigned rotation, + boolean flipped) { + int r; + + if (frame >= 29 || rotation > 8) + I_Error("R_InstallSpriteLump: " + "Bad frame characters in lump %i", + lump); + + if ((int)frame > maxframe) + maxframe = frame; + + if (rotation == 0) { + // the lump should be used for all rotations + if (sprtemp[frame].rotate == false) + I_Error("R_InitSprites: Sprite %s frame %c has " + "multip rot=0 lump", + spritename, 'A' + frame); + + if (sprtemp[frame].rotate == true) + I_Error("R_InitSprites: Sprite %s frame %c has rotations " + "and a rot=0 lump", + spritename, 'A' + frame); + + sprtemp[frame].rotate = false; + for (r = 0; r < 8; r++) { + sprtemp[frame].lump[r] = lump - firstspritelump; + sprtemp[frame].flip[r] = (byte)flipped; + } + return; + } + + // the lump is only used for one rotation + if (sprtemp[frame].rotate == false) + I_Error("R_InitSprites: Sprite %s frame %c has rotations " + "and a rot=0 lump", + spritename, 'A' + frame); + + sprtemp[frame].rotate = true; + + // make 0 based + rotation--; + if (sprtemp[frame].lump[rotation] != -1) + I_Error("R_InitSprites: Sprite %s : %c : %c " + "has two lumps mapped to it", + spritename, 'A' + frame, '1' + rotation); + + sprtemp[frame].lump[rotation] = lump - firstspritelump; + sprtemp[frame].flip[rotation] = (byte)flipped; +} + +// +// R_InitSpriteDefs +// Pass a null terminated list of sprite names +// (4 chars exactly) to be used. +// Builds the sprite rotation matrixes to account +// for horizontally flipped sprites. +// Will report an error if the lumps are inconsistant. +// Only called at startup. +// +// Sprite lump names are 4 characters for the actor, +// a letter for the frame, and a number for the rotation. +// A sprite that is flippable will have an additional +// letter/number appended. +// The rotation character can be 0 to signify no rotations. +// +void R_InitSpriteDefs(char **namelist) { + char **check; + int i; + int l; + int frame; + int rotation; + int start; + int end; + int patched; + + // count the number of sprite names + check = namelist; + while (*check != NULL) + check++; + + numsprites = check - namelist; + + if (!numsprites) + return; + + sprites = Z_Malloc(numsprites * sizeof(*sprites), PU_STATIC, NULL); + + start = firstspritelump - 1; + end = lastspritelump + 1; + + // scan all the lump names for each of the names, + // noting the highest frame letter. + // Just compare 4 characters as ints + for (i = 0; i < numsprites; i++) { + spritename = (namelist[i]); + memset(sprtemp, -1, sizeof(sprtemp)); + + maxframe = -1; + + // scan the lumps, + // filling in the frames for whatever is found + for (l = start + 1; l < end; l++) { + if (!strncasecmp(lumpinfo[l]->name, spritename, 4)) { + frame = lumpinfo[l]->name[4] - 'A'; + rotation = lumpinfo[l]->name[5] - '0'; + + if (modifiedgame) + patched = W_GetNumForName(lumpinfo[l]->name); + else + patched = l; + + R_InstallSpriteLump(patched, frame, rotation, false); + + if (lumpinfo[l]->name[6]) { + frame = lumpinfo[l]->name[6] - 'A'; + rotation = lumpinfo[l]->name[7] - '0'; + R_InstallSpriteLump(l, frame, rotation, true); + } + } + } + + // check the frames that were found for completeness + if (maxframe == -1) { + sprites[i].numframes = 0; + continue; + } + + maxframe++; + + for (frame = 0; frame < maxframe; frame++) { + switch ((int)sprtemp[frame].rotate) { + case -1: + // no rotations were found for that frame at all + I_Error("R_InitSprites: No patches found " + "for %s frame %c", + spritename, frame + 'A'); + break; + + case 0: + // only the first rotation is needed + break; + + case 1: + // must have all 8 frames + for (rotation = 0; rotation < 8; rotation++) + if (sprtemp[frame].lump[rotation] == -1) + I_Error("R_InitSprites: Sprite %s frame %c " + "is missing rotations", + spritename, frame + 'A'); + break; + } + } + + // allocate space for the frames present and copy sprtemp to it + sprites[i].numframes = maxframe; + sprites[i].spriteframes = + Z_Malloc(maxframe * sizeof(spriteframe_t), PU_STATIC, NULL); + memcpy(sprites[i].spriteframes, sprtemp, maxframe * sizeof(spriteframe_t)); + } +} + +// +// GAME FUNCTIONS +// +vissprite_t vissprites[MAXVISSPRITES]; +vissprite_t *vissprite_p; +int newvissprite; + +// +// R_InitSprites +// Called at program start. +// +void R_InitSprites(char **namelist) { + int i; + + for (i = 0; i < SCREENWIDTH; i++) { + negonearray[i] = -1; + } + + R_InitSpriteDefs(namelist); +} + +// +// R_ClearSprites +// Called at frame start. +// +void R_ClearSprites(void) { vissprite_p = vissprites; } + +// +// R_NewVisSprite +// +vissprite_t overflowsprite; + +vissprite_t *R_NewVisSprite(void) { + if (vissprite_p == &vissprites[MAXVISSPRITES]) + return &overflowsprite; + + vissprite_p++; + return vissprite_p - 1; +} + +// +// R_DrawMaskedColumn +// Used for sprites and masked mid textures. +// Masked means: partly transparent, i.e. stored +// in posts/runs of opaque pixels. +// +short *mfloorclip; +short *mceilingclip; + +fixed_t spryscale; +fixed_t sprtopscreen; + +void R_DrawMaskedColumn(column_t *column) { + int topscreen; + int bottomscreen; + fixed_t basetexturemid; + + basetexturemid = dc_texturemid; + + for (; column->topdelta != 0xff;) { + // calculate unclipped screen coordinates + // for post + topscreen = sprtopscreen + spryscale * column->topdelta; + bottomscreen = topscreen + spryscale * column->length; + + dc_yl = (topscreen + FRACUNIT - 1) >> FRACBITS; + dc_yh = (bottomscreen - 1) >> FRACBITS; + + if (dc_yh >= mfloorclip[dc_x]) + dc_yh = mfloorclip[dc_x] - 1; + if (dc_yl <= mceilingclip[dc_x]) + dc_yl = mceilingclip[dc_x] + 1; + + if (dc_yl <= dc_yh) { + dc_source = (byte *)column + 3; + dc_texturemid = basetexturemid - (column->topdelta << FRACBITS); + // dc_source = (byte *)column + 3 - column->topdelta; + + // Drawn by either R_DrawColumn + // or (SHADOW) R_DrawFuzzColumn. + colfunc(); + } + column = (column_t *)((byte *)column + column->length + 4); + } + + dc_texturemid = basetexturemid; +} + +// +// R_DrawVisSprite +// mfloorclip and mceilingclip should also be set. +// +void R_DrawVisSprite(vissprite_t *vis, int x1, int x2) { + column_t *column; + int texturecolumn; + fixed_t frac; + patch_t *patch; + + patch = W_CacheLumpNum(vis->patch + firstspritelump, PU_CACHE); + + dc_colormap = vis->colormap; + + if (!dc_colormap) { + // NULL colormap = shadow draw + colfunc = fuzzcolfunc; + } else if (vis->mobjflags & MF_TRANSLATION) { + colfunc = transcolfunc; + dc_translation = translationtables - 256 + + ((vis->mobjflags & MF_TRANSLATION) >> (MF_TRANSSHIFT - 8)); + } + + dc_iscale = abs(vis->xiscale) >> detailshift; + dc_texturemid = vis->texturemid; + frac = vis->startfrac; + spryscale = vis->scale; + sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); + + for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, frac += vis->xiscale) { + texturecolumn = frac >> FRACBITS; + if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width)) + I_Error("R_DrawSpriteRange: bad texturecolumn"); + column = + (column_t *)((byte *)patch + LONG(patch->columnofs[texturecolumn])); + R_DrawMaskedColumn(column); + } + + colfunc = basecolfunc; +} + +// +// R_ProjectSprite +// Generates a vissprite for a thing +// if it might be visible. +// +void R_ProjectSprite(mobj_t *thing) { + fixed_t tr_x; + fixed_t tr_y; + + fixed_t gxt; + fixed_t gyt; + + fixed_t tx; + fixed_t tz; + + fixed_t xscale; + + int x1; + int x2; + + spritedef_t *sprdef; + spriteframe_t *sprframe; + int lump; + + unsigned rot; + boolean flip; + + int index; + + vissprite_t *vis; + + angle_t ang; + fixed_t iscale; + + // transform the origin point + tr_x = thing->x - viewx; + tr_y = thing->y - viewy; + + gxt = FixedMul(tr_x, viewcos); + gyt = -FixedMul(tr_y, viewsin); + + tz = gxt - gyt; + + // thing is behind view plane? + if (tz < MINZ) + return; + + xscale = FixedDiv(projection, tz); + + gxt = -FixedMul(tr_x, viewsin); + gyt = FixedMul(tr_y, viewcos); + tx = -(gyt + gxt); + + // too far off the side? + if (abs(tx) > (tz << 2)) + return; + + // decide which patch to use for sprite relative to player + if ((unsigned int)thing->sprite >= (unsigned int)numsprites) + I_Error("R_ProjectSprite: invalid sprite number %i ", thing->sprite); + sprdef = &sprites[thing->sprite]; + if ((thing->frame & FF_FRAMEMASK) >= sprdef->numframes) + I_Error("R_ProjectSprite: invalid sprite frame %i : %i ", thing->sprite, + thing->frame); + sprframe = &sprdef->spriteframes[thing->frame & FF_FRAMEMASK]; + + if (sprframe->rotate) { + // choose a different rotation based on player view + ang = R_PointToAngle(thing->x, thing->y); + rot = (ang - thing->angle + (unsigned)(ANG45 / 2) * 9) >> 29; + lump = sprframe->lump[rot]; + flip = (boolean)sprframe->flip[rot]; + } else { + // use single rotation for all views + lump = sprframe->lump[0]; + flip = (boolean)sprframe->flip[0]; + } + + // calculate edges of the shape + tx -= spriteoffset[lump]; + x1 = (centerxfrac + FixedMul(tx, xscale)) >> FRACBITS; + + // off the right side? + if (x1 > viewwidth) + return; + + tx += spritewidth[lump]; + x2 = ((centerxfrac + FixedMul(tx, xscale)) >> FRACBITS) - 1; + + // off the left side + if (x2 < 0) + return; + + // store information in a vissprite + vis = R_NewVisSprite(); + vis->mobjflags = thing->flags; + vis->scale = xscale << detailshift; + vis->gx = thing->x; + vis->gy = thing->y; + vis->gz = thing->z; + vis->gzt = thing->z + spritetopoffset[lump]; + vis->texturemid = vis->gzt - viewz; + vis->x1 = x1 < 0 ? 0 : x1; + vis->x2 = x2 >= viewwidth ? viewwidth - 1 : x2; + iscale = FixedDiv(FRACUNIT, xscale); + + if (flip) { + vis->startfrac = spritewidth[lump] - 1; + vis->xiscale = -iscale; + } else { + vis->startfrac = 0; + vis->xiscale = iscale; + } + + if (vis->x1 > x1) + vis->startfrac += vis->xiscale * (vis->x1 - x1); + vis->patch = lump; + + // get light level + if (thing->flags & MF_SHADOW) { + // shadow draw + vis->colormap = NULL; + } else if (fixedcolormap) { + // fixed map + vis->colormap = fixedcolormap; + } else if (thing->frame & FF_FULLBRIGHT) { + // full bright + vis->colormap = colormaps; + } + + else { + // diminished light + index = xscale >> (LIGHTSCALESHIFT - detailshift); + + if (index >= MAXLIGHTSCALE) + index = MAXLIGHTSCALE - 1; + + vis->colormap = spritelights[index]; + } +} + +// +// R_AddSprites +// During BSP traversal, this adds sprites by sector. +// +void R_AddSprites(sector_t *sec) { + mobj_t *thing; + int lightnum; + + // BSP is traversed by subsector. + // A sector might have been split into several + // subsectors during BSP building. + // Thus we check whether its already added. + if (sec->validcount == validcount) + return; + + // Well, now it will be done. + sec->validcount = validcount; + + lightnum = (sec->lightlevel >> LIGHTSEGSHIFT) + extralight; + + if (lightnum < 0) + spritelights = scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + spritelights = scalelight[LIGHTLEVELS - 1]; + else + spritelights = scalelight[lightnum]; + + // Handle all things in sector. + for (thing = sec->thinglist; thing; thing = thing->snext) + R_ProjectSprite(thing); +} + +// +// R_DrawPSprite +// +void R_DrawPSprite(pspdef_t *psp) { + fixed_t tx; + int x1; + int x2; + spritedef_t *sprdef; + spriteframe_t *sprframe; + int lump; + boolean flip; + vissprite_t *vis; + vissprite_t avis; + + // decide which patch to use + if ((unsigned)psp->state->sprite >= (unsigned int)numsprites) + I_Error("R_ProjectSprite: invalid sprite number %i ", psp->state->sprite); + sprdef = &sprites[psp->state->sprite]; + if ((psp->state->frame & FF_FRAMEMASK) >= sprdef->numframes) + I_Error("R_ProjectSprite: invalid sprite frame %i : %i ", + psp->state->sprite, psp->state->frame); + sprframe = &sprdef->spriteframes[psp->state->frame & FF_FRAMEMASK]; + + lump = sprframe->lump[0]; + flip = (boolean)sprframe->flip[0]; + + // calculate edges of the shape + tx = psp->sx - (SCREENWIDTH / 2) * FRACUNIT; + + tx -= spriteoffset[lump]; + x1 = (centerxfrac + FixedMul(tx, pspritescale)) >> FRACBITS; + + // off the right side + if (x1 > viewwidth) + return; + + tx += spritewidth[lump]; + x2 = ((centerxfrac + FixedMul(tx, pspritescale)) >> FRACBITS) - 1; + + // off the left side + if (x2 < 0) + return; + + // store information in a vissprite + vis = &avis; + vis->mobjflags = 0; + vis->texturemid = (BASEYCENTER << FRACBITS) + FRACUNIT / 2 - + (psp->sy - spritetopoffset[lump]); + vis->x1 = x1 < 0 ? 0 : x1; + vis->x2 = x2 >= viewwidth ? viewwidth - 1 : x2; + vis->scale = pspritescale << detailshift; + + if (flip) { + vis->xiscale = -pspriteiscale; + vis->startfrac = spritewidth[lump] - 1; + } else { + vis->xiscale = pspriteiscale; + vis->startfrac = 0; + } + + if (vis->x1 > x1) + vis->startfrac += vis->xiscale * (vis->x1 - x1); + + vis->patch = lump; + + if (viewplayer->powers[pw_invisibility] > 4 * 32 || + viewplayer->powers[pw_invisibility] & 8) { + // shadow draw + vis->colormap = NULL; + } else if (fixedcolormap) { + // fixed color + vis->colormap = fixedcolormap; + } else if (psp->state->frame & FF_FULLBRIGHT) { + // full bright + vis->colormap = colormaps; + } else { + // local light + vis->colormap = spritelights[MAXLIGHTSCALE - 1]; + } + + R_DrawVisSprite(vis, vis->x1, vis->x2); +} + +// +// R_DrawPlayerSprites +// +void R_DrawPlayerSprites(void) { + int i; + int lightnum; + pspdef_t *psp; + + // get light level + lightnum = (viewplayer->mo->subsector->sector->lightlevel >> LIGHTSEGSHIFT) + + extralight; + + if (lightnum < 0) + spritelights = scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + spritelights = scalelight[LIGHTLEVELS - 1]; + else + spritelights = scalelight[lightnum]; + + // clip to screen bounds + mfloorclip = screenheightarray; + mceilingclip = negonearray; + + // add all active psprites + for (i = 0, psp = viewplayer->psprites; i < NUMPSPRITES; i++, psp++) { + if (psp->state) + R_DrawPSprite(psp); + } +} + +// +// R_SortVisSprites +// +vissprite_t vsprsortedhead; + +void R_SortVisSprites(void) { + int i; + int count; + vissprite_t *ds; + vissprite_t *best; + vissprite_t unsorted; + fixed_t bestscale; + + count = vissprite_p - vissprites; + + unsorted.next = unsorted.prev = &unsorted; + + if (!count) + return; + + for (ds = vissprites; ds < vissprite_p; ds++) { + ds->next = ds + 1; + ds->prev = ds - 1; + } + + vissprites[0].prev = &unsorted; + unsorted.next = &vissprites[0]; + (vissprite_p - 1)->next = &unsorted; + unsorted.prev = vissprite_p - 1; + + // pull the vissprites out by scale + + vsprsortedhead.next = vsprsortedhead.prev = &vsprsortedhead; + for (i = 0; i < count; i++) { + bestscale = INT_MAX; + best = unsorted.next; + for (ds = unsorted.next; ds != &unsorted; ds = ds->next) { + if (ds->scale < bestscale) { + bestscale = ds->scale; + best = ds; + } + } + best->next->prev = best->prev; + best->prev->next = best->next; + best->next = &vsprsortedhead; + best->prev = vsprsortedhead.prev; + vsprsortedhead.prev->next = best; + vsprsortedhead.prev = best; + } +} + +// +// R_DrawSprite +// +void R_DrawSprite(vissprite_t *spr) { + drawseg_t *ds; + short clipbot[SCREENWIDTH]; + short cliptop[SCREENWIDTH]; + int x; + int r1; + int r2; + fixed_t scale; + fixed_t lowscale; + int silhouette; + + for (x = spr->x1; x <= spr->x2; x++) + clipbot[x] = cliptop[x] = -2; + + // Scan drawsegs from end to start for obscuring segs. + // The first drawseg that has a greater scale + // is the clip seg. + for (ds = ds_p - 1; ds >= drawsegs; ds--) { + // determine if the drawseg obscures the sprite + if (ds->x1 > spr->x2 || ds->x2 < spr->x1 || + (!ds->silhouette && !ds->maskedtexturecol)) { + // does not cover sprite + continue; + } + + r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1; + r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2; + + if (ds->scale1 > ds->scale2) { + lowscale = ds->scale2; + scale = ds->scale1; + } else { + lowscale = ds->scale1; + scale = ds->scale2; + } + + if (scale < spr->scale || + (lowscale < spr->scale && + !R_PointOnSegSide(spr->gx, spr->gy, ds->curline))) { + // masked mid texture? + if (ds->maskedtexturecol) + R_RenderMaskedSegRange(ds, r1, r2); + // seg is behind sprite + continue; + } + + // clip this piece of the sprite + silhouette = ds->silhouette; + + if (spr->gz >= ds->bsilheight) + silhouette &= ~SIL_BOTTOM; + + if (spr->gzt <= ds->tsilheight) + silhouette &= ~SIL_TOP; + + if (silhouette == 1) { + // bottom sil + for (x = r1; x <= r2; x++) + if (clipbot[x] == -2) + clipbot[x] = ds->sprbottomclip[x]; + } else if (silhouette == 2) { + // top sil + for (x = r1; x <= r2; x++) + if (cliptop[x] == -2) + cliptop[x] = ds->sprtopclip[x]; + } else if (silhouette == 3) { + // both + for (x = r1; x <= r2; x++) { + if (clipbot[x] == -2) + clipbot[x] = ds->sprbottomclip[x]; + if (cliptop[x] == -2) + cliptop[x] = ds->sprtopclip[x]; + } + } + } + + // all clipping has been performed, so draw the sprite + + // check for unclipped columns + for (x = spr->x1; x <= spr->x2; x++) { + if (clipbot[x] == -2) + clipbot[x] = viewheight; + + if (cliptop[x] == -2) + cliptop[x] = -1; + } + + mfloorclip = clipbot; + mceilingclip = cliptop; + R_DrawVisSprite(spr, spr->x1, spr->x2); +} + +// +// R_DrawMasked +// +void R_DrawMasked(void) { + vissprite_t *spr; + drawseg_t *ds; + + R_SortVisSprites(); + + if (vissprite_p > vissprites) { + // draw all vissprites back to front + for (spr = vsprsortedhead.next; spr != &vsprsortedhead; spr = spr->next) { + + R_DrawSprite(spr); + } + } + + // render any remaining masked mid textures + for (ds = ds_p - 1; ds >= drawsegs; ds--) + if (ds->maskedtexturecol) + R_RenderMaskedSegRange(ds, ds->x1, ds->x2); + + // draw the psprites on top of everything + // but does not draw on side views + if (!viewangleoffset) + R_DrawPlayerSprites(); +} diff --git a/client/src/r_things.h b/client/src/r_things.h new file mode 100644 index 0000000..89ad81f --- /dev/null +++ b/client/src/r_things.h @@ -0,0 +1,60 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Rendering of moving objects, sprites. +// + +#ifndef __R_THINGS__ +#define __R_THINGS__ + +#include "i_video.h" +#include "m_fixed.h" +#include "r_defs.h" +#include "v_patch.h" + +#define MAXVISSPRITES 128 + +extern vissprite_t vissprites[MAXVISSPRITES]; +extern vissprite_t *vissprite_p; +extern vissprite_t vsprsortedhead; + +// Constant arrays used for psprite clipping +// and initializing clipping. +extern short negonearray[SCREENWIDTH]; +extern short screenheightarray[SCREENWIDTH]; + +// vars for R_DrawMaskedColumn +extern short *mfloorclip; +extern short *mceilingclip; +extern fixed_t spryscale; +extern fixed_t sprtopscreen; + +extern fixed_t pspritescale; +extern fixed_t pspriteiscale; + +void R_DrawMaskedColumn(column_t *column); + +void R_SortVisSprites(void); + +void R_AddSprites(sector_t *sec); +void R_AddPSprites(void); +void R_DrawSprites(void); +void R_InitSprites(char **namelist); +void R_ClearSprites(void); +void R_DrawMasked(void); + +void R_ClipVisSprite(vissprite_t *vis, int xl, int xh); + +#endif diff --git a/client/src/s_sound.c b/client/src/s_sound.c new file mode 100644 index 0000000..ad3338b --- /dev/null +++ b/client/src/s_sound.c @@ -0,0 +1,589 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: none +// + +#include +#include + +#include "d_mode.h" +#include "d_player.h" +#include "doomstat.h" +#include "doomtype.h" +#include "i_sound.h" +#include "i_system.h" +#include "m_fixed.h" +#include "m_misc.h" +#include "m_random.h" +#include "r_main.h" +#include "s_sound.h" +#include "sounds.h" +#include "tables.h" +#include "w_wad.h" +#include "z_zone.h" + +// when to clip out sounds +// Does not fit the large outdoor areas. + +#define S_CLIPPING_DIST (1200 * FRACUNIT) + +// Distance tp origin when sounds should be maxed out. +// This should relate to movement clipping resolution +// (see BLOCKMAP handling). +// In the source code release: (160*FRACUNIT). Changed back to the +// Vanilla value of 200 (why was this changed?) + +#define S_CLOSE_DIST (200 * FRACUNIT) + +// The range over which sound attenuates + +#define S_ATTENUATOR ((S_CLIPPING_DIST - S_CLOSE_DIST) >> FRACBITS) + +// Stereo separation + +#define S_STEREO_SWING (96 * FRACUNIT) + +#define NORM_PRIORITY 64 +#define NORM_SEP 128 + +typedef struct { + // sound information (if null, channel avail.) + sfxinfo_t *sfxinfo; + + // origin of sound + mobj_t *origin; + + // handle of the sound being played + int handle; + + int pitch; + +} channel_t; + +// The set of channels available + +static channel_t *channels; + +// Maximum volume of a sound effect. +// Internal default is max out of 0-15. + +int sfxVolume = 8; + +// Maximum volume of music. + +int musicVolume = 8; + +// Internal volume level, ranging from 0-127 + +static int snd_SfxVolume; + +// Whether songs are mus_paused + +static boolean mus_paused; + +// Music currently being played + +static musicinfo_t *mus_playing = NULL; + +// Number of channels to use + +int snd_channels = 8; + +// +// Initializes sound stuff, including volume +// Sets channels, SFX and music volume, +// allocates channel buffer, sets S_sfx lookup. +// + +void S_Init(int sfxVolume, int musicVolume) { + int i; + + I_PrecacheSounds(S_sfx, NUMSFX); + + S_SetSfxVolume(sfxVolume); + S_SetMusicVolume(musicVolume); + + // Allocating the internal channels for mixing + // (the maximum numer of sounds rendered + // simultaneously) within zone memory. + channels = Z_Malloc(snd_channels * sizeof(channel_t), PU_STATIC, 0); + + // Free all channels for use + for (i = 0; i < snd_channels; i++) { + channels[i].sfxinfo = 0; + } + + // no sounds are playing, and they are not mus_paused + mus_paused = 0; + + // Note that sounds have not been cached (yet). + for (i = 1; i < NUMSFX; i++) { + S_sfx[i].lumpnum = S_sfx[i].usefulness = -1; + } + + // Doom defaults to pitch-shifting off. + if (snd_pitchshift == -1) { + snd_pitchshift = 0; + } + + I_AtExit(S_Shutdown, true); +} + +void S_Shutdown(void) { + I_ShutdownSound(); + I_ShutdownMusic(); +} + +static void S_StopChannel(int cnum) { + int i; + channel_t *c; + + c = &channels[cnum]; + + if (c->sfxinfo) { + // stop the sound playing + + if (I_SoundIsPlaying(c->handle)) { + I_StopSound(c->handle); + } + + // check to see if other channels are playing the sound + + for (i = 0; i < snd_channels; i++) { + if (cnum != i && c->sfxinfo == channels[i].sfxinfo) { + break; + } + } + + // degrade usefulness of sound data + + c->sfxinfo->usefulness--; + c->sfxinfo = NULL; + c->origin = NULL; + } +} + +// +// Per level startup code. +// Kills playing sounds at start of level, +// determines music if any, changes music. +// + +void S_Start(void) { + int cnum; + int mnum; + + // kill all playing sounds at start of level + // (trust me - a good idea) + for (cnum = 0; cnum < snd_channels; cnum++) { + if (channels[cnum].sfxinfo) { + S_StopChannel(cnum); + } + } + + // start new music for the level + mus_paused = 0; + + if (gamemode == commercial) { + mnum = mus_runnin + gamemap - 1; + } else { + int spmus[] = { + // Song - Who? - Where? + + mus_e3m4, // American e4m1 + mus_e3m2, // Romero e4m2 + mus_e3m3, // Shawn e4m3 + mus_e1m5, // American e4m4 + mus_e2m7, // Tim e4m5 + mus_e2m4, // Romero e4m6 + mus_e2m6, // J.Anderson e4m7 CHIRON.WAD + mus_e2m5, // Shawn e4m8 + mus_e1m9, // Tim e4m9 + }; + + if (gameepisode < 4) { + mnum = mus_e1m1 + (gameepisode - 1) * 9 + gamemap - 1; + } else { + mnum = spmus[gamemap - 1]; + } + } + + S_ChangeMusic(mnum, true); +} + +void S_StopSound(mobj_t *origin) { + int cnum; + + for (cnum = 0; cnum < snd_channels; cnum++) { + if (channels[cnum].sfxinfo && channels[cnum].origin == origin) { + S_StopChannel(cnum); + break; + } + } +} + +// +// S_GetChannel : +// If none available, return -1. Otherwise channel #. +// + +static int S_GetChannel(mobj_t *origin, sfxinfo_t *sfxinfo) { + // channel number to use + int cnum; + + channel_t *c; + + // Find an open channel + for (cnum = 0; cnum < snd_channels; cnum++) { + if (!channels[cnum].sfxinfo) { + break; + } else if (origin && channels[cnum].origin == origin) { + S_StopChannel(cnum); + break; + } + } + + // None available + if (cnum == snd_channels) { + // Look for lower priority + for (cnum = 0; cnum < snd_channels; cnum++) { + if (channels[cnum].sfxinfo->priority >= sfxinfo->priority) { + break; + } + } + + if (cnum == snd_channels) { + // FUCK! No lower priority. Sorry, Charlie. + return -1; + } else { + // Otherwise, kick out lower priority. + S_StopChannel(cnum); + } + } + + c = &channels[cnum]; + + // channel is decided to be cnum. + c->sfxinfo = sfxinfo; + c->origin = origin; + + return cnum; +} + +// +// Changes volume and stereo-separation variables +// from the norm of a sound effect to be played. +// If the sound is not audible, returns a 0. +// Otherwise, modifies parameters and returns 1. +// + +static int S_AdjustSoundParams(mobj_t *listener, mobj_t *source, int *vol, + int *sep) { + fixed_t approx_dist; + fixed_t adx; + fixed_t ady; + angle_t angle; + + // calculate the distance to sound origin + // and clip it if necessary + adx = abs(listener->x - source->x); + ady = abs(listener->y - source->y); + + // From _GG1_ p.428. Appox. eucledian distance fast. + approx_dist = adx + ady - ((adx < ady ? adx : ady) >> 1); + + if (gamemap != 8 && approx_dist > S_CLIPPING_DIST) { + return 0; + } + + // angle of source to listener + angle = R_PointToAngle2(listener->x, listener->y, source->x, source->y); + + if (angle > listener->angle) { + angle = angle - listener->angle; + } else { + angle = angle + (0xffffffff - listener->angle); + } + + angle >>= ANGLETOFINESHIFT; + + // stereo separation + *sep = 128 - (FixedMul(S_STEREO_SWING, finesine[angle]) >> FRACBITS); + + // volume calculation + if (approx_dist < S_CLOSE_DIST) { + *vol = snd_SfxVolume; + } else if (gamemap == 8) { + if (approx_dist > S_CLIPPING_DIST) { + approx_dist = S_CLIPPING_DIST; + } + + *vol = + 15 + + ((snd_SfxVolume - 15) * ((S_CLIPPING_DIST - approx_dist) >> FRACBITS)) / + S_ATTENUATOR; + } else { + // distance effect + *vol = (snd_SfxVolume * ((S_CLIPPING_DIST - approx_dist) >> FRACBITS)) / + S_ATTENUATOR; + } + + return (*vol > 0); +} + +// clamp supplied integer to the range 0 <= x <= 255. + +static int Clamp(int x) { + if (x < 0) { + return 0; + } else if (x > 255) { + return 255; + } + return x; +} + +void S_StartSound(void *origin_p, int sfx_id) { + sfxinfo_t *sfx; + mobj_t *origin; + int rc; + int sep; + int pitch; + int cnum; + int volume; + + origin = (mobj_t *)origin_p; + volume = snd_SfxVolume; + + // check for bogus sound # + if (sfx_id < 1 || sfx_id > NUMSFX) { + I_Error("Bad sfx #: %d", sfx_id); + } + + sfx = &S_sfx[sfx_id]; + + // Initialize sound parameters + pitch = NORM_PITCH; + if (sfx->link) { + volume += sfx->volume; + pitch = sfx->pitch; + + if (volume < 1) { + return; + } + + if (volume > snd_SfxVolume) { + volume = snd_SfxVolume; + } + } + + // Check to see if it is audible, + // and if not, modify the params + if (origin && origin != players[consoleplayer].mo) { + rc = S_AdjustSoundParams(players[consoleplayer].mo, origin, &volume, &sep); + + if (origin->x == players[consoleplayer].mo->x && + origin->y == players[consoleplayer].mo->y) { + sep = NORM_SEP; + } + + if (!rc) { + return; + } + } else { + sep = NORM_SEP; + } + + // hacks to vary the sfx pitches + if (sfx_id >= sfx_sawup && sfx_id <= sfx_sawhit) { + pitch += 8 - (M_Random() & 15); + } else if (sfx_id != sfx_itemup && sfx_id != sfx_tink) { + pitch += 16 - (M_Random() & 31); + } + pitch = Clamp(pitch); + + // kill old sound + S_StopSound(origin); + + // try to find a channel + cnum = S_GetChannel(origin, sfx); + + if (cnum < 0) { + return; + } + + // increase the usefulness + if (sfx->usefulness++ < 0) { + sfx->usefulness = 1; + } + + if (sfx->lumpnum < 0) { + sfx->lumpnum = I_GetSfxLumpNum(sfx); + } + + channels[cnum].pitch = pitch; + channels[cnum].handle = + I_StartSound(sfx, cnum, volume, sep, channels[cnum].pitch); +} + +// +// Stop and resume music, during game PAUSE. +// + +void S_PauseSound(void) { + if (mus_playing && !mus_paused) { + I_PauseSong(); + mus_paused = true; + } +} + +void S_ResumeSound(void) { + if (mus_playing && mus_paused) { + I_ResumeSong(); + mus_paused = false; + } +} + +// +// Updates music & sounds +// + +void S_UpdateSounds(mobj_t *listener) { + int audible; + int cnum; + int volume; + int sep; + sfxinfo_t *sfx; + channel_t *c; + + I_UpdateSound(); + + for (cnum = 0; cnum < snd_channels; cnum++) { + c = &channels[cnum]; + sfx = c->sfxinfo; + + if (c->sfxinfo) { + if (I_SoundIsPlaying(c->handle)) { + // initialize parameters + volume = snd_SfxVolume; + sep = NORM_SEP; + + if (sfx->link) { + volume += sfx->volume; + if (volume < 1) { + S_StopChannel(cnum); + continue; + } else if (volume > snd_SfxVolume) { + volume = snd_SfxVolume; + } + } + + // check non-local sounds for distance clipping + // or modify their params + if (c->origin && listener != c->origin) { + audible = S_AdjustSoundParams(listener, c->origin, &volume, &sep); + + if (!audible) { + S_StopChannel(cnum); + } else { + I_UpdateSoundParams(c->handle, volume, sep); + } + } + } else { + // if channel is allocated but sound has stopped, + // free it + S_StopChannel(cnum); + } + } + } +} + +void S_SetMusicVolume(int volume) { + if (volume < 0 || volume > 127) { + I_Error("Attempt to set music volume at %d", volume); + } + + I_SetMusicVolume(volume); +} + +void S_SetSfxVolume(int volume) { + if (volume < 0 || volume > 127) { + I_Error("Attempt to set sfx volume at %d", volume); + } + + snd_SfxVolume = volume; +} + +// +// Starts some music with the music id found in sounds.h. +// + +void S_StartMusic(int m_id) { S_ChangeMusic(m_id, false); } + +void S_ChangeMusic(int musicnum, int looping) { + musicinfo_t *music = NULL; + char namebuf[9]; + void *handle; + + // The Doom IWAD file has two versions of the intro music: d_intro + // and d_introa. The latter is used for OPL playback. + + if (musicnum == mus_intro && + (snd_musicdevice == SNDDEVICE_ADLIB || snd_musicdevice == SNDDEVICE_SB)) { + musicnum = mus_introa; + } + + if (musicnum <= mus_None || musicnum >= NUMMUSIC) { + I_Error("Bad music number %d", musicnum); + } else { + music = &S_music[musicnum]; + } + + if (mus_playing == music) { + return; + } + + // shutdown old music + S_StopMusic(); + + // get lumpnum if neccessary + if (!music->lumpnum) { + M_snprintf(namebuf, sizeof(namebuf), "d_%s", (music->name)); + music->lumpnum = W_GetNumForName(namebuf); + } + + music->data = W_CacheLumpNum(music->lumpnum, PU_STATIC); + + handle = I_RegisterSong(music->data, W_LumpLength(music->lumpnum)); + music->handle = handle; + I_PlaySong(handle, looping); + + mus_playing = music; +} + +boolean S_MusicPlaying(void) { return I_MusicIsPlaying(); } + +void S_StopMusic(void) { + if (mus_playing) { + if (mus_paused) { + I_ResumeSong(); + } + + I_StopSong(); + I_UnRegisterSong(mus_playing->handle); + W_ReleaseLumpNum(mus_playing->lumpnum); + mus_playing->data = NULL; + mus_playing = NULL; + } +} diff --git a/client/src/s_sound.h b/client/src/s_sound.h new file mode 100644 index 0000000..cafd723 --- /dev/null +++ b/client/src/s_sound.h @@ -0,0 +1,82 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// The not so system specific sound interface. +// + +#ifndef __S_SOUND__ +#define __S_SOUND__ + +#include "doomtype.h" +#include "p_mobj.h" + +// +// Initializes sound stuff, including volume +// Sets channels, SFX and music volume, +// allocates channel buffer, sets S_sfx lookup. +// + +void S_Init(int sfxVolume, int musicVolume); + +// Shut down sound + +void S_Shutdown(void); + +// +// Per level startup code. +// Kills playing sounds at start of level, +// determines music if any, changes music. +// + +void S_Start(void); + +// +// Start sound for thing at +// using from sounds.h +// + +void S_StartSound(void *origin, int sound_id); + +// Stop sound for thing at +void S_StopSound(mobj_t *origin); + +// Start music using from sounds.h +void S_StartMusic(int music_id); + +// Start music using from sounds.h, +// and set whether looping +void S_ChangeMusic(int music_id, int looping); + +// query if music is playing +boolean S_MusicPlaying(void); + +// Stops the music fer sure. +void S_StopMusic(void); + +// Stop and resume music, during game PAUSE. +void S_PauseSound(void); +void S_ResumeSound(void); + +// +// Updates music & sounds +// +void S_UpdateSounds(mobj_t *listener); + +void S_SetMusicVolume(int volume); +void S_SetSfxVolume(int volume); + +extern int snd_channels; + +#endif diff --git a/client/src/sha1.c b/client/src/sha1.c new file mode 100644 index 0000000..4b00301 --- /dev/null +++ b/client/src/sha1.c @@ -0,0 +1,317 @@ +/* sha1.c - SHA1 hash function + * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. + * + * Please see below for more legal information! + * + * This file is part of GnuPG. + * + * GnuPG 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. + * + * GnuPG 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + + +/* Test vectors: + * + * "abc" + * A999 3E36 4706 816A BA3E 2571 7850 C26C 9CD0 D89D + * + * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + * 8498 3E44 1C3B D26E BAAE 4AA1 F951 29E5 E546 70F1 + */ + +#include + +#include "sha1.h" + +void SHA1_Init(sha1_context_t *hd) +{ + hd->h0 = 0x67452301; + hd->h1 = 0xefcdab89; + hd->h2 = 0x98badcfe; + hd->h3 = 0x10325476; + hd->h4 = 0xc3d2e1f0; + hd->nblocks = 0; + hd->count = 0; +} + + +/**************** + * Transform the message X which consists of 16 32-bit-words + */ +static void Transform(sha1_context_t *hd, byte *data) +{ + uint32_t a,b,c,d,e,tm; + uint32_t x[16]; + + /* get values from the chaining vars */ + a = hd->h0; + b = hd->h1; + c = hd->h2; + d = hd->h3; + e = hd->h4; + +#ifdef SYS_BIG_ENDIAN + memcpy(x, data, 64); +#else + { + int i; + byte *p2; + for(i=0, p2=(byte*)x; i < 16; i++, p2 += 4 ) + { + p2[3] = *data++; + p2[2] = *data++; + p2[1] = *data++; + p2[0] = *data++; + } + } +#endif + + +#define K1 0x5A827999L +#define K2 0x6ED9EBA1L +#define K3 0x8F1BBCDCL +#define K4 0xCA62C1D6L +#define F1(x,y,z) ( z ^ ( x & ( y ^ z ) ) ) +#define F2(x,y,z) ( x ^ y ^ z ) +#define F3(x,y,z) ( ( x & y ) | ( z & ( x | y ) ) ) +#define F4(x,y,z) ( x ^ y ^ z ) + +#define rol(x,n) ( ((x) << (n)) | ((x) >> (32-(n))) ) + +#define M(i) ( tm = x[i&0x0f] ^ x[(i-14)&0x0f] \ + ^ x[(i-8)&0x0f] ^ x[(i-3)&0x0f] \ + , (x[i&0x0f] = rol(tm,1)) ) + +#define R(a,b,c,d,e,f,k,m) do { e += rol( a, 5 ) \ + + f( b, c, d ) \ + + k \ + + m; \ + b = rol( b, 30 ); \ + } while(0) + R( a, b, c, d, e, F1, K1, x[ 0] ); + R( e, a, b, c, d, F1, K1, x[ 1] ); + R( d, e, a, b, c, F1, K1, x[ 2] ); + R( c, d, e, a, b, F1, K1, x[ 3] ); + R( b, c, d, e, a, F1, K1, x[ 4] ); + R( a, b, c, d, e, F1, K1, x[ 5] ); + R( e, a, b, c, d, F1, K1, x[ 6] ); + R( d, e, a, b, c, F1, K1, x[ 7] ); + R( c, d, e, a, b, F1, K1, x[ 8] ); + R( b, c, d, e, a, F1, K1, x[ 9] ); + R( a, b, c, d, e, F1, K1, x[10] ); + R( e, a, b, c, d, F1, K1, x[11] ); + R( d, e, a, b, c, F1, K1, x[12] ); + R( c, d, e, a, b, F1, K1, x[13] ); + R( b, c, d, e, a, F1, K1, x[14] ); + R( a, b, c, d, e, F1, K1, x[15] ); + R( e, a, b, c, d, F1, K1, M(16) ); + R( d, e, a, b, c, F1, K1, M(17) ); + R( c, d, e, a, b, F1, K1, M(18) ); + R( b, c, d, e, a, F1, K1, M(19) ); + R( a, b, c, d, e, F2, K2, M(20) ); + R( e, a, b, c, d, F2, K2, M(21) ); + R( d, e, a, b, c, F2, K2, M(22) ); + R( c, d, e, a, b, F2, K2, M(23) ); + R( b, c, d, e, a, F2, K2, M(24) ); + R( a, b, c, d, e, F2, K2, M(25) ); + R( e, a, b, c, d, F2, K2, M(26) ); + R( d, e, a, b, c, F2, K2, M(27) ); + R( c, d, e, a, b, F2, K2, M(28) ); + R( b, c, d, e, a, F2, K2, M(29) ); + R( a, b, c, d, e, F2, K2, M(30) ); + R( e, a, b, c, d, F2, K2, M(31) ); + R( d, e, a, b, c, F2, K2, M(32) ); + R( c, d, e, a, b, F2, K2, M(33) ); + R( b, c, d, e, a, F2, K2, M(34) ); + R( a, b, c, d, e, F2, K2, M(35) ); + R( e, a, b, c, d, F2, K2, M(36) ); + R( d, e, a, b, c, F2, K2, M(37) ); + R( c, d, e, a, b, F2, K2, M(38) ); + R( b, c, d, e, a, F2, K2, M(39) ); + R( a, b, c, d, e, F3, K3, M(40) ); + R( e, a, b, c, d, F3, K3, M(41) ); + R( d, e, a, b, c, F3, K3, M(42) ); + R( c, d, e, a, b, F3, K3, M(43) ); + R( b, c, d, e, a, F3, K3, M(44) ); + R( a, b, c, d, e, F3, K3, M(45) ); + R( e, a, b, c, d, F3, K3, M(46) ); + R( d, e, a, b, c, F3, K3, M(47) ); + R( c, d, e, a, b, F3, K3, M(48) ); + R( b, c, d, e, a, F3, K3, M(49) ); + R( a, b, c, d, e, F3, K3, M(50) ); + R( e, a, b, c, d, F3, K3, M(51) ); + R( d, e, a, b, c, F3, K3, M(52) ); + R( c, d, e, a, b, F3, K3, M(53) ); + R( b, c, d, e, a, F3, K3, M(54) ); + R( a, b, c, d, e, F3, K3, M(55) ); + R( e, a, b, c, d, F3, K3, M(56) ); + R( d, e, a, b, c, F3, K3, M(57) ); + R( c, d, e, a, b, F3, K3, M(58) ); + R( b, c, d, e, a, F3, K3, M(59) ); + R( a, b, c, d, e, F4, K4, M(60) ); + R( e, a, b, c, d, F4, K4, M(61) ); + R( d, e, a, b, c, F4, K4, M(62) ); + R( c, d, e, a, b, F4, K4, M(63) ); + R( b, c, d, e, a, F4, K4, M(64) ); + R( a, b, c, d, e, F4, K4, M(65) ); + R( e, a, b, c, d, F4, K4, M(66) ); + R( d, e, a, b, c, F4, K4, M(67) ); + R( c, d, e, a, b, F4, K4, M(68) ); + R( b, c, d, e, a, F4, K4, M(69) ); + R( a, b, c, d, e, F4, K4, M(70) ); + R( e, a, b, c, d, F4, K4, M(71) ); + R( d, e, a, b, c, F4, K4, M(72) ); + R( c, d, e, a, b, F4, K4, M(73) ); + R( b, c, d, e, a, F4, K4, M(74) ); + R( a, b, c, d, e, F4, K4, M(75) ); + R( e, a, b, c, d, F4, K4, M(76) ); + R( d, e, a, b, c, F4, K4, M(77) ); + R( c, d, e, a, b, F4, K4, M(78) ); + R( b, c, d, e, a, F4, K4, M(79) ); + + /* update chainig vars */ + hd->h0 += a; + hd->h1 += b; + hd->h2 += c; + hd->h3 += d; + hd->h4 += e; +} + + +/* Update the message digest with the contents + * of INBUF with length INLEN. + */ +void SHA1_Update(sha1_context_t *hd, byte *inbuf, size_t inlen) +{ + if (hd->count == 64) + { + /* flush the buffer */ + Transform(hd, hd->buf); + hd->count = 0; + hd->nblocks++; + } + if (!inbuf) + return; + if (hd->count) + { + for (; inlen && hd->count < 64; inlen--) + hd->buf[hd->count++] = *inbuf++; + SHA1_Update(hd, NULL, 0); + if (!inlen) + return; + } + + while (inlen >= 64) + { + Transform(hd, inbuf); + hd->count = 0; + hd->nblocks++; + inlen -= 64; + inbuf += 64; + } + for (; inlen && hd->count < 64; inlen--) + hd->buf[hd->count++] = *inbuf++; +} + + +/* The routine final terminates the computation and + * returns the digest. + * The handle is prepared for a new cycle, but adding bytes to the + * handle will the destroy the returned buffer. + * Returns: 20 bytes representing the digest. + */ + +void SHA1_Final(sha1_digest_t digest, sha1_context_t *hd) +{ + uint32_t t, msb, lsb; + byte *p; + + SHA1_Update(hd, NULL, 0); /* flush */; + + t = hd->nblocks; + /* multiply by 64 to make a byte count */ + lsb = t << 6; + msb = t >> 26; + /* add the count */ + t = lsb; + if ((lsb += hd->count) < t) + msb++; + /* multiply by 8 to make a bit count */ + t = lsb; + lsb <<= 3; + msb <<= 3; + msb |= t >> 29; + + if (hd->count < 56) + { + /* enough room */ + hd->buf[hd->count++] = 0x80; /* pad */ + while (hd->count < 56) + hd->buf[hd->count++] = 0; /* pad */ + } + else + { + /* need one extra block */ + hd->buf[hd->count++] = 0x80; /* pad character */ + while (hd->count < 64) + hd->buf[hd->count++] = 0; + SHA1_Update(hd, NULL, 0); /* flush */; + memset(hd->buf, 0, 56 ); /* fill next block with zeroes */ + } + /* append the 64 bit count */ + hd->buf[56] = msb >> 24; + hd->buf[57] = msb >> 16; + hd->buf[58] = msb >> 8; + hd->buf[59] = msb ; + hd->buf[60] = lsb >> 24; + hd->buf[61] = lsb >> 16; + hd->buf[62] = lsb >> 8; + hd->buf[63] = lsb ; + Transform(hd, hd->buf); + + p = hd->buf; +#ifdef SYS_BIG_ENDIAN +#define X(a) do { *(uint32_t*)p = hd->h##a ; p += 4; } while(0) +#else /* little endian */ +#define X(a) do { *p++ = hd->h##a >> 24; *p++ = hd->h##a >> 16; \ + *p++ = hd->h##a >> 8; *p++ = hd->h##a; } while(0) +#endif + X(0); + X(1); + X(2); + X(3); + X(4); +#undef X + + memcpy(digest, hd->buf, sizeof(sha1_digest_t)); +} + +void SHA1_UpdateInt32(sha1_context_t *context, unsigned int val) +{ + byte buf[4]; + + buf[0] = (val >> 24) & 0xff; + buf[1] = (val >> 16) & 0xff; + buf[2] = (val >> 8) & 0xff; + buf[3] = val & 0xff; + + SHA1_Update(context, buf, 4); +} + +void SHA1_UpdateString(sha1_context_t *context, char *str) +{ + SHA1_Update(context, (byte *) str, strlen(str) + 1); +} + diff --git a/client/src/sha1.h b/client/src/sha1.h new file mode 100644 index 0000000..cb7d4e9 --- /dev/null +++ b/client/src/sha1.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// SHA-1 digest. +// + +#ifndef __SHA1_H__ +#define __SHA1_H__ + +#include +#include + +#include "doomtype.h" + +struct sha1_context_s; + +typedef struct sha1_context_s sha1_context_t; +typedef byte sha1_digest_t[20]; + +struct sha1_context_s { + uint32_t h0,h1,h2,h3,h4; + uint32_t nblocks; + byte buf[64]; + int count; +}; + +void SHA1_Init(sha1_context_t *context); +void SHA1_Update(sha1_context_t *context, byte *buf, size_t len); +void SHA1_Final(sha1_digest_t digest, sha1_context_t *context); +void SHA1_UpdateInt32(sha1_context_t *context, unsigned int val); +void SHA1_UpdateString(sha1_context_t *context, char *str); + +#endif /* #ifndef __SHA1_H__ */ + diff --git a/client/src/sounds.c b/client/src/sounds.c new file mode 100644 index 0000000..0b75a8d --- /dev/null +++ b/client/src/sounds.c @@ -0,0 +1,170 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Created by a sound utility. +// Kept as a sample, DOOM2 sounds. +// + +#include + +#include "sounds.h" + +// +// Information about all the music +// + +#define MUSIC(name) \ + { name, 0, NULL, NULL } + +musicinfo_t S_music[] = { + MUSIC(NULL), MUSIC("e1m1"), MUSIC("e1m2"), MUSIC("e1m3"), + MUSIC("e1m4"), MUSIC("e1m5"), MUSIC("e1m6"), MUSIC("e1m7"), + MUSIC("e1m8"), MUSIC("e1m9"), MUSIC("e2m1"), MUSIC("e2m2"), + MUSIC("e2m3"), MUSIC("e2m4"), MUSIC("e2m5"), MUSIC("e2m6"), + MUSIC("e2m7"), MUSIC("e2m8"), MUSIC("e2m9"), MUSIC("e3m1"), + MUSIC("e3m2"), MUSIC("e3m3"), MUSIC("e3m4"), MUSIC("e3m5"), + MUSIC("e3m6"), MUSIC("e3m7"), MUSIC("e3m8"), MUSIC("e3m9"), + MUSIC("inter"), MUSIC("intro"), MUSIC("bunny"), MUSIC("victor"), + MUSIC("introa"), MUSIC("runnin"), MUSIC("stalks"), MUSIC("countd"), + MUSIC("betwee"), MUSIC("doom"), MUSIC("the_da"), MUSIC("shawn"), + MUSIC("ddtblu"), MUSIC("in_cit"), MUSIC("dead"), MUSIC("stlks2"), + MUSIC("theda2"), MUSIC("doom2"), MUSIC("ddtbl2"), MUSIC("runni2"), + MUSIC("dead2"), MUSIC("stlks3"), MUSIC("romero"), MUSIC("shawn2"), + MUSIC("messag"), MUSIC("count2"), MUSIC("ddtbl3"), MUSIC("ampie"), + MUSIC("theda3"), MUSIC("adrian"), MUSIC("messg2"), MUSIC("romer2"), + MUSIC("tense"), MUSIC("shawn3"), MUSIC("openin"), MUSIC("evil"), + MUSIC("ultima"), MUSIC("read_m"), MUSIC("dm2ttl"), MUSIC("dm2int")}; + +// +// Information about all the sfx +// + +#define SOUND(name, priority) \ + { NULL, name, priority, NULL, -1, -1, 0, 0, -1, NULL } +#define SOUND_LINK(name, priority, link_id, pitch, volume) \ + { NULL, name, priority, &S_sfx[link_id], pitch, volume, 0, 0, -1, NULL } + +sfxinfo_t S_sfx[] = { + // S_sfx[0] needs to be a dummy for odd reasons. + SOUND("none", 0), + SOUND("pistol", 64), + SOUND("shotgn", 64), + SOUND("sgcock", 64), + SOUND("dshtgn", 64), + SOUND("dbopn", 64), + SOUND("dbcls", 64), + SOUND("dbload", 64), + SOUND("plasma", 64), + SOUND("bfg", 64), + SOUND("sawup", 64), + SOUND("sawidl", 118), + SOUND("sawful", 64), + SOUND("sawhit", 64), + SOUND("rlaunc", 64), + SOUND("rxplod", 70), + SOUND("firsht", 70), + SOUND("firxpl", 70), + SOUND("pstart", 100), + SOUND("pstop", 100), + SOUND("doropn", 100), + SOUND("dorcls", 100), + SOUND("stnmov", 119), + SOUND("swtchn", 78), + SOUND("swtchx", 78), + SOUND("plpain", 96), + SOUND("dmpain", 96), + SOUND("popain", 96), + SOUND("vipain", 96), + SOUND("mnpain", 96), + SOUND("pepain", 96), + SOUND("slop", 78), + SOUND("itemup", 78), + SOUND("wpnup", 78), + SOUND("oof", 96), + SOUND("telept", 32), + SOUND("posit1", 98), + SOUND("posit2", 98), + SOUND("posit3", 98), + SOUND("bgsit1", 98), + SOUND("bgsit2", 98), + SOUND("sgtsit", 98), + SOUND("cacsit", 98), + SOUND("brssit", 94), + SOUND("cybsit", 92), + SOUND("spisit", 90), + SOUND("bspsit", 90), + SOUND("kntsit", 90), + SOUND("vilsit", 90), + SOUND("mansit", 90), + SOUND("pesit", 90), + SOUND("sklatk", 70), + SOUND("sgtatk", 70), + SOUND("skepch", 70), + SOUND("vilatk", 70), + SOUND("claw", 70), + SOUND("skeswg", 70), + SOUND("pldeth", 32), + SOUND("pdiehi", 32), + SOUND("podth1", 70), + SOUND("podth2", 70), + SOUND("podth3", 70), + SOUND("bgdth1", 70), + SOUND("bgdth2", 70), + SOUND("sgtdth", 70), + SOUND("cacdth", 70), + SOUND("skldth", 70), + SOUND("brsdth", 32), + SOUND("cybdth", 32), + SOUND("spidth", 32), + SOUND("bspdth", 32), + SOUND("vildth", 32), + SOUND("kntdth", 32), + SOUND("pedth", 32), + SOUND("skedth", 32), + SOUND("posact", 120), + SOUND("bgact", 120), + SOUND("dmact", 120), + SOUND("bspact", 100), + SOUND("bspwlk", 100), + SOUND("vilact", 100), + SOUND("noway", 78), + SOUND("barexp", 60), + SOUND("punch", 64), + SOUND("hoof", 70), + SOUND("metal", 70), + SOUND_LINK("chgun", 64, sfx_pistol, 150, 0), + SOUND("tink", 60), + SOUND("bdopn", 100), + SOUND("bdcls", 100), + SOUND("itmbk", 100), + SOUND("flame", 32), + SOUND("flamst", 32), + SOUND("getpow", 60), + SOUND("bospit", 70), + SOUND("boscub", 70), + SOUND("bossit", 70), + SOUND("bospn", 70), + SOUND("bosdth", 70), + SOUND("manatk", 70), + SOUND("mandth", 70), + SOUND("sssit", 70), + SOUND("ssdth", 70), + SOUND("keenpn", 70), + SOUND("keendt", 70), + SOUND("skeact", 70), + SOUND("skesit", 70), + SOUND("skeatk", 70), + SOUND("radio", 60), +}; diff --git a/client/src/sounds.h b/client/src/sounds.h new file mode 100644 index 0000000..9f7d741 --- /dev/null +++ b/client/src/sounds.h @@ -0,0 +1,224 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Created by the sound utility written by Dave Taylor. +// Kept as a sample, DOOM2 sounds. Frozen. +// + +#ifndef __SOUNDS__ +#define __SOUNDS__ + +#include "i_sound.h" + +// the complete set of sound effects +extern sfxinfo_t S_sfx[]; + +// the complete set of music +extern musicinfo_t S_music[]; + +// +// Identifiers for all music in game. +// + +typedef enum { + mus_None, + mus_e1m1, + mus_e1m2, + mus_e1m3, + mus_e1m4, + mus_e1m5, + mus_e1m6, + mus_e1m7, + mus_e1m8, + mus_e1m9, + mus_e2m1, + mus_e2m2, + mus_e2m3, + mus_e2m4, + mus_e2m5, + mus_e2m6, + mus_e2m7, + mus_e2m8, + mus_e2m9, + mus_e3m1, + mus_e3m2, + mus_e3m3, + mus_e3m4, + mus_e3m5, + mus_e3m6, + mus_e3m7, + mus_e3m8, + mus_e3m9, + mus_inter, + mus_intro, + mus_bunny, + mus_victor, + mus_introa, + mus_runnin, + mus_stalks, + mus_countd, + mus_betwee, + mus_doom, + mus_the_da, + mus_shawn, + mus_ddtblu, + mus_in_cit, + mus_dead, + mus_stlks2, + mus_theda2, + mus_doom2, + mus_ddtbl2, + mus_runni2, + mus_dead2, + mus_stlks3, + mus_romero, + mus_shawn2, + mus_messag, + mus_count2, + mus_ddtbl3, + mus_ampie, + mus_theda3, + mus_adrian, + mus_messg2, + mus_romer2, + mus_tense, + mus_shawn3, + mus_openin, + mus_evil, + mus_ultima, + mus_read_m, + mus_dm2ttl, + mus_dm2int, + NUMMUSIC +} musicenum_t; + +// +// Identifiers for all sfx in game. +// + +typedef enum { + sfx_None, + sfx_pistol, + sfx_shotgn, + sfx_sgcock, + sfx_dshtgn, + sfx_dbopn, + sfx_dbcls, + sfx_dbload, + sfx_plasma, + sfx_bfg, + sfx_sawup, + sfx_sawidl, + sfx_sawful, + sfx_sawhit, + sfx_rlaunc, + sfx_rxplod, + sfx_firsht, + sfx_firxpl, + sfx_pstart, + sfx_pstop, + sfx_doropn, + sfx_dorcls, + sfx_stnmov, + sfx_swtchn, + sfx_swtchx, + sfx_plpain, + sfx_dmpain, + sfx_popain, + sfx_vipain, + sfx_mnpain, + sfx_pepain, + sfx_slop, + sfx_itemup, + sfx_wpnup, + sfx_oof, + sfx_telept, + sfx_posit1, + sfx_posit2, + sfx_posit3, + sfx_bgsit1, + sfx_bgsit2, + sfx_sgtsit, + sfx_cacsit, + sfx_brssit, + sfx_cybsit, + sfx_spisit, + sfx_bspsit, + sfx_kntsit, + sfx_vilsit, + sfx_mansit, + sfx_pesit, + sfx_sklatk, + sfx_sgtatk, + sfx_skepch, + sfx_vilatk, + sfx_claw, + sfx_skeswg, + sfx_pldeth, + sfx_pdiehi, + sfx_podth1, + sfx_podth2, + sfx_podth3, + sfx_bgdth1, + sfx_bgdth2, + sfx_sgtdth, + sfx_cacdth, + sfx_skldth, + sfx_brsdth, + sfx_cybdth, + sfx_spidth, + sfx_bspdth, + sfx_vildth, + sfx_kntdth, + sfx_pedth, + sfx_skedth, + sfx_posact, + sfx_bgact, + sfx_dmact, + sfx_bspact, + sfx_bspwlk, + sfx_vilact, + sfx_noway, + sfx_barexp, + sfx_punch, + sfx_hoof, + sfx_metal, + sfx_chgun, + sfx_tink, + sfx_bdopn, + sfx_bdcls, + sfx_itmbk, + sfx_flame, + sfx_flamst, + sfx_getpow, + sfx_bospit, + sfx_boscub, + sfx_bossit, + sfx_bospn, + sfx_bosdth, + sfx_manatk, + sfx_mandth, + sfx_sssit, + sfx_ssdth, + sfx_keenpn, + sfx_keendt, + sfx_skeact, + sfx_skesit, + sfx_skeatk, + sfx_radio, + NUMSFX +} sfxenum_t; + +#endif diff --git a/client/src/st_lib.c b/client/src/st_lib.c new file mode 100644 index 0000000..d78905f --- /dev/null +++ b/client/src/st_lib.c @@ -0,0 +1,196 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// The status bar widget code. +// + +#include "st_lib.h" +#include "i_swap.h" +#include "i_system.h" +#include "st_stuff.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +// in AM_map.c +extern boolean automapactive; + +// +// Hack display negative frags. +// Loads and store the stminus lump. +// +patch_t *sttminus; + +void STlib_init(void) { + sttminus = (patch_t *)W_CacheLumpName(("STTMINUS"), PU_STATIC); +} + +// ? +void STlib_initNum(st_number_t *n, int x, int y, patch_t **pl, int *num, + boolean *on, int width) { + n->x = x; + n->y = y; + n->oldnum = 0; + n->width = width; + n->num = num; + n->on = on; + n->p = pl; +} + +// +// A fairly efficient way to draw a number +// based on differences from the old number. +// Note: worth the trouble? +// +void STlib_drawNum(st_number_t *n, boolean refresh) { + + int numdigits = n->width; + int num = *n->num; + + int w = SHORT(n->p[0]->width); + int h = SHORT(n->p[0]->height); + int x = n->x; + + int neg; + + n->oldnum = *n->num; + + neg = num < 0; + + if (neg) { + if (numdigits == 2 && num < -9) + num = -9; + else if (numdigits == 3 && num < -99) + num = -99; + + num = -num; + } + + // clear the area + x = n->x - numdigits * w; + + if (n->y - ST_Y < 0) + I_Error("drawNum: n->y - ST_Y < 0"); + + V_CopyRect(x, n->y - ST_Y, st_backing_screen, w * numdigits, h, x, n->y); + + // if non-number, do not draw it + if (num == 1994) + return; + + x = n->x; + + // in the special case of 0, you draw 0 + if (!num) + V_DrawPatch(x - w, n->y, n->p[0]); + + // draw the new number + while (num && numdigits--) { + x -= w; + V_DrawPatch(x, n->y, n->p[num % 10]); + num /= 10; + } + + // draw a minus sign if necessary + if (neg) + V_DrawPatch(x - 8, n->y, sttminus); +} + +// +void STlib_updateNum(st_number_t *n, boolean refresh) { + if (*n->on) + STlib_drawNum(n, refresh); +} + +// +void STlib_initPercent(st_percent_t *p, int x, int y, patch_t **pl, int *num, + boolean *on, patch_t *percent) { + STlib_initNum(&p->n, x, y, pl, num, on, 3); + p->p = percent; +} + +void STlib_updatePercent(st_percent_t *per, int refresh) { + if (refresh && *per->n.on) + V_DrawPatch(per->n.x, per->n.y, per->p); + + STlib_updateNum(&per->n, refresh); +} + +void STlib_initMultIcon(st_multicon_t *i, int x, int y, patch_t **il, int *inum, + boolean *on) { + i->x = x; + i->y = y; + i->oldinum = -1; + i->inum = inum; + i->on = on; + i->p = il; +} + +void STlib_updateMultIcon(st_multicon_t *mi, boolean refresh) { + int w; + int h; + int x; + int y; + + if (*mi->on && (mi->oldinum != *mi->inum || refresh) && (*mi->inum != -1)) { + if (mi->oldinum != -1) { + x = mi->x - SHORT(mi->p[mi->oldinum]->leftoffset); + y = mi->y - SHORT(mi->p[mi->oldinum]->topoffset); + w = SHORT(mi->p[mi->oldinum]->width); + h = SHORT(mi->p[mi->oldinum]->height); + + if (y - ST_Y < 0) + I_Error("updateMultIcon: y - ST_Y < 0"); + + V_CopyRect(x, y - ST_Y, st_backing_screen, w, h, x, y); + } + V_DrawPatch(mi->x, mi->y, mi->p[*mi->inum]); + mi->oldinum = *mi->inum; + } +} + +void STlib_initBinIcon(st_binicon_t *b, int x, int y, patch_t *i, boolean *val, + boolean *on) { + b->x = x; + b->y = y; + b->oldval = false; + b->val = val; + b->on = on; + b->p = i; +} + +void STlib_updateBinIcon(st_binicon_t *bi, boolean refresh) { + int x; + int y; + int w; + int h; + + if (*bi->on && (bi->oldval != *bi->val || refresh)) { + x = bi->x - SHORT(bi->p->leftoffset); + y = bi->y - SHORT(bi->p->topoffset); + w = SHORT(bi->p->width); + h = SHORT(bi->p->height); + + if (y - ST_Y < 0) + I_Error("updateBinIcon: y - ST_Y < 0"); + + if (*bi->val) + V_DrawPatch(bi->x, bi->y, bi->p); + else + V_CopyRect(x, y - ST_Y, st_backing_screen, w, h, x, y); + + bi->oldval = *bi->val; + } +} diff --git a/client/src/st_lib.h b/client/src/st_lib.h new file mode 100644 index 0000000..d5a8625 --- /dev/null +++ b/client/src/st_lib.h @@ -0,0 +1,151 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// The status bar widget code. +// + +#ifndef __STLIB__ +#define __STLIB__ + +#include "doomtype.h" +// We are referring to patches. +#include "v_patch.h" + +// +// Typedefs of widgets +// + +// Number widget + +typedef struct { + // upper right-hand corner + // of the number (right-justified) + int x; + int y; + + // max # of digits in number + int width; + + // last number value + int oldnum; + + // pointer to current value + int *num; + + // pointer to boolean stating + // whether to update number + boolean *on; + + // list of patches for 0-9 + patch_t **p; + + // user data + int data; + +} st_number_t; + +// Percent widget ("child" of number widget, +// or, more precisely, contains a number widget.) +typedef struct { + // number information + st_number_t n; + + // percent sign graphic + patch_t *p; + +} st_percent_t; + +// Multiple Icon widget +typedef struct { + // center-justified location of icons + int x; + int y; + + // last icon number + int oldinum; + + // pointer to current icon + int *inum; + + // pointer to boolean stating + // whether to update icon + boolean *on; + + // list of icons + patch_t **p; + + // user data + int data; + +} st_multicon_t; + +// Binary Icon widget + +typedef struct { + // center-justified location of icon + int x; + int y; + + // last icon value + boolean oldval; + + // pointer to current icon status + boolean *val; + + // pointer to boolean + // stating whether to update icon + boolean *on; + + patch_t *p; // icon + int data; // user data + +} st_binicon_t; + +// +// Widget creation, access, and update routines +// + +// Initializes widget library. +// More precisely, initialize STMINUS, +// everything else is done somewhere else. +// +void STlib_init(void); + +// Number widget routines +void STlib_initNum(st_number_t *n, int x, int y, patch_t **pl, int *num, + boolean *on, int width); + +void STlib_updateNum(st_number_t *n, boolean refresh); + +// Percent widget routines +void STlib_initPercent(st_percent_t *p, int x, int y, patch_t **pl, int *num, + boolean *on, patch_t *percent); + +void STlib_updatePercent(st_percent_t *per, int refresh); + +// Multiple Icon widget routines +void STlib_initMultIcon(st_multicon_t *mi, int x, int y, patch_t **il, + int *inum, boolean *on); + +void STlib_updateMultIcon(st_multicon_t *mi, boolean refresh); + +// Binary Icon widget routines + +void STlib_initBinIcon(st_binicon_t *b, int x, int y, patch_t *i, boolean *val, + boolean *on); + +void STlib_updateBinIcon(st_binicon_t *bi, boolean refresh); + +#endif diff --git a/client/src/st_stuff.c b/client/src/st_stuff.c new file mode 100644 index 0000000..629c28f --- /dev/null +++ b/client/src/st_stuff.c @@ -0,0 +1,1186 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Status bar code. +// Does the face/direction indicator animatin. +// Does palette indicators as well (red pain/berserk, bright pickup) +// + +#include + +#include "am_map.h" +#include "d_englsh.h" +#include "d_items.h" +#include "d_mode.h" +#include "d_player.h" +#include "doomdef.h" +#include "doomkeys.h" +// State. +#include "doomstat.h" +#include "g_game.h" +#include "i_timer.h" +#include "i_video.h" +#include "m_cheat.h" +#include "m_misc.h" +#include "m_random.h" +#include "p_inter.h" +#include "p_mobj.h" +#include "r_main.h" +#include "s_sound.h" +#include "sounds.h" +#include "st_lib.h" +#include "st_stuff.h" +#include "tables.h" +#include "v_patch.h" +// Needs access to LFB. +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +// +// STATUS BAR DATA +// + +// Palette indices. +// For damage/bonus red-/gold-shifts +#define STARTREDPALS 1 +#define STARTBONUSPALS 9 +#define NUMREDPALS 8 +#define NUMBONUSPALS 4 +// Radiation suit, green shift. +#define RADIATIONPAL 13 + +// N/256*100% probability +// that the normal face state will change +#define ST_FACEPROBABILITY 96 + +// For Responder +#define ST_TOGGLECHAT KEY_ENTER + +// Location of status bar +#define ST_X 0 +#define ST_X2 104 + +#define ST_FX 143 +#define ST_FY 169 + +// Should be set to patch width +// for tall numbers later on +#define ST_TALLNUMWIDTH (tallnum[0]->width) + +// Number of status faces. +#define ST_NUMPAINFACES 5 +#define ST_NUMSTRAIGHTFACES 3 +#define ST_NUMTURNFACES 2 +#define ST_NUMSPECIALFACES 3 + +#define ST_FACESTRIDE \ + (ST_NUMSTRAIGHTFACES + ST_NUMTURNFACES + ST_NUMSPECIALFACES) + +#define ST_NUMEXTRAFACES 2 + +#define ST_NUMFACES (ST_FACESTRIDE * ST_NUMPAINFACES + ST_NUMEXTRAFACES) + +#define ST_TURNOFFSET (ST_NUMSTRAIGHTFACES) +#define ST_OUCHOFFSET (ST_TURNOFFSET + ST_NUMTURNFACES) +#define ST_EVILGRINOFFSET (ST_OUCHOFFSET + 1) +#define ST_RAMPAGEOFFSET (ST_EVILGRINOFFSET + 1) +#define ST_GODFACE (ST_NUMPAINFACES * ST_FACESTRIDE) +#define ST_DEADFACE (ST_GODFACE + 1) + +#define ST_FACESX 143 +#define ST_FACESY 168 + +#define ST_EVILGRINCOUNT (2 * TICRATE) +#define ST_STRAIGHTFACECOUNT (TICRATE / 2) +#define ST_TURNCOUNT (1 * TICRATE) +#define ST_OUCHCOUNT (1 * TICRATE) +#define ST_RAMPAGEDELAY (2 * TICRATE) + +#define ST_MUCHPAIN 20 + +// Location and size of statistics, +// justified according to widget type. +// Problem is, within which space? STbar? Screen? +// Note: this could be read in by a lump. +// Problem is, is the stuff rendered +// into a buffer, +// or into the frame buffer? + +// AMMO number pos. +#define ST_AMMOWIDTH 3 +#define ST_AMMOX 44 +#define ST_AMMOY 171 + +// HEALTH number pos. +#define ST_HEALTHWIDTH 3 +#define ST_HEALTHX 90 +#define ST_HEALTHY 171 + +// Weapon pos. +#define ST_ARMSX 111 +#define ST_ARMSY 172 +#define ST_ARMSBGX 104 +#define ST_ARMSBGY 168 +#define ST_ARMSXSPACE 12 +#define ST_ARMSYSPACE 10 + +// Frags pos. +#define ST_FRAGSX 138 +#define ST_FRAGSY 171 +#define ST_FRAGSWIDTH 2 + +// ARMOR number pos. +#define ST_ARMORWIDTH 3 +#define ST_ARMORX 221 +#define ST_ARMORY 171 + +// Key icon positions. +#define ST_KEY0WIDTH 8 +#define ST_KEY0HEIGHT 5 +#define ST_KEY0X 239 +#define ST_KEY0Y 171 +#define ST_KEY1WIDTH ST_KEY0WIDTH +#define ST_KEY1X 239 +#define ST_KEY1Y 181 +#define ST_KEY2WIDTH ST_KEY0WIDTH +#define ST_KEY2X 239 +#define ST_KEY2Y 191 + +// Ammunition counter. +#define ST_AMMO0WIDTH 3 +#define ST_AMMO0HEIGHT 6 +#define ST_AMMO0X 288 +#define ST_AMMO0Y 173 +#define ST_AMMO1WIDTH ST_AMMO0WIDTH +#define ST_AMMO1X 288 +#define ST_AMMO1Y 179 +#define ST_AMMO2WIDTH ST_AMMO0WIDTH +#define ST_AMMO2X 288 +#define ST_AMMO2Y 191 +#define ST_AMMO3WIDTH ST_AMMO0WIDTH +#define ST_AMMO3X 288 +#define ST_AMMO3Y 185 + +// Indicate maximum ammunition. +// Only needed because backpack exists. +#define ST_MAXAMMO0WIDTH 3 +#define ST_MAXAMMO0HEIGHT 5 +#define ST_MAXAMMO0X 314 +#define ST_MAXAMMO0Y 173 +#define ST_MAXAMMO1WIDTH ST_MAXAMMO0WIDTH +#define ST_MAXAMMO1X 314 +#define ST_MAXAMMO1Y 179 +#define ST_MAXAMMO2WIDTH ST_MAXAMMO0WIDTH +#define ST_MAXAMMO2X 314 +#define ST_MAXAMMO2Y 191 +#define ST_MAXAMMO3WIDTH ST_MAXAMMO0WIDTH +#define ST_MAXAMMO3X 314 +#define ST_MAXAMMO3Y 185 + +// pistol +#define ST_WEAPON0X 110 +#define ST_WEAPON0Y 172 + +// shotgun +#define ST_WEAPON1X 122 +#define ST_WEAPON1Y 172 + +// chain gun +#define ST_WEAPON2X 134 +#define ST_WEAPON2Y 172 + +// missile launcher +#define ST_WEAPON3X 110 +#define ST_WEAPON3Y 181 + +// plasma gun +#define ST_WEAPON4X 122 +#define ST_WEAPON4Y 181 + +// bfg +#define ST_WEAPON5X 134 +#define ST_WEAPON5Y 181 + +// WPNS title +#define ST_WPNSX 109 +#define ST_WPNSY 191 + +// DETH title +#define ST_DETHX 109 +#define ST_DETHY 191 + +// Incoming messages window location +// UNUSED +// #define ST_MSGTEXTX (viewwindowx) +// #define ST_MSGTEXTY (viewwindowy+viewheight-18) +#define ST_MSGTEXTX 0 +#define ST_MSGTEXTY 0 +// Dimensions given in characters. +#define ST_MSGWIDTH 52 +// Or shall I say, in lines? +#define ST_MSGHEIGHT 1 + +#define ST_OUTTEXTX 0 +#define ST_OUTTEXTY 6 + +// Width, in characters again. +#define ST_OUTWIDTH 52 +// Height, in lines. +#define ST_OUTHEIGHT 1 + +#define ST_MAPTITLEX (SCREENWIDTH - ST_MAPWIDTH * ST_CHATFONTWIDTH) + +#define ST_MAPTITLEY 0 +#define ST_MAPHEIGHT 1 + +// graphics are drawn to a backing screen and blitted to the real screen +pixel_t *st_backing_screen; + +// main player in game +static player_t *plyr; + +// ST_Start() has just been called +static boolean st_firsttime; + +// lump number for PLAYPAL +static int lu_palette; + +// used for timing +static unsigned int st_clock; + +// used for making messages go away +static int st_msgcounter = 0; + +// used when in chat +static st_chatstateenum_t st_chatstate; + +// whether in automap or first-person +static st_stateenum_t st_gamestate; + +// whether left-side main status bar is active +static boolean st_statusbaron; + +// whether status bar chat is active +static boolean st_chat; + +// value of st_chat before message popped up +static boolean st_oldchat; + +// whether chat window has the cursor on +static boolean st_cursoron; + +// !deathmatch +static boolean st_notdeathmatch; + +// !deathmatch && st_statusbaron +static boolean st_armson; + +// !deathmatch +static boolean st_fragson; + +// main bar left +static patch_t *sbar; + +// 0-9, tall numbers +static patch_t *tallnum[10]; + +// tall % sign +static patch_t *tallpercent; + +// 0-9, short, yellow (,different!) numbers +static patch_t *shortnum[10]; + +// 3 key-cards, 3 skulls +static patch_t *keys[NUMCARDS]; + +// face status patches +static patch_t *faces[ST_NUMFACES]; + +// face background +static patch_t *faceback; + +// main bar right +static patch_t *armsbg; + +// weapon ownership patches +static patch_t *arms[6][2]; + +// ready-weapon widget +static st_number_t w_ready; + +// in deathmatch only, summary of frags stats +static st_number_t w_frags; + +// health widget +static st_percent_t w_health; + +// arms background +static st_binicon_t w_armsbg; + +// weapon ownership widgets +static st_multicon_t w_arms[6]; + +// face status widget +static st_multicon_t w_faces; + +// keycard widgets +static st_multicon_t w_keyboxes[3]; + +// armor widget +static st_percent_t w_armor; + +// ammo widgets +static st_number_t w_ammo[4]; + +// max ammo widgets +static st_number_t w_maxammo[4]; + +// number of frags so far in deathmatch +static int st_fragscount; + +// used to use appopriately pained face +static int st_oldhealth = -1; + +// used for evil grin +static boolean oldweaponsowned[NUMWEAPONS]; + +// count until face changes +static int st_facecount = 0; + +// current face index, used by w_faces +static int st_faceindex = 0; + +// holds key-type for each key box on bar +static int keyboxes[3]; + +// a random number per tick +static int st_randomnumber; + +cheatseq_t cheat_mus = CHEAT("idmus", 2); +cheatseq_t cheat_god = CHEAT("iddqd", 0); +cheatseq_t cheat_ammo = CHEAT("idkfa", 0); +cheatseq_t cheat_ammonokey = CHEAT("idfa", 0); +cheatseq_t cheat_noclip = CHEAT("idspispopd", 0); +cheatseq_t cheat_commercial_noclip = CHEAT("idclip", 0); + +cheatseq_t cheat_powerup[7] = { + CHEAT("idbeholdv", 0), CHEAT("idbeholds", 0), CHEAT("idbeholdi", 0), + CHEAT("idbeholdr", 0), CHEAT("idbeholda", 0), CHEAT("idbeholdl", 0), + CHEAT("idbehold", 0), +}; + +cheatseq_t cheat_choppers = CHEAT("idchoppers", 0); +cheatseq_t cheat_clev = CHEAT("idclev", 2); +cheatseq_t cheat_mypos = CHEAT("idmypos", 0); + +// +// STATUS BAR CODE +// +void ST_Stop(void); + +void ST_refreshBackground(void) { + + if (st_statusbaron) { + V_UseBuffer(st_backing_screen); + + V_DrawPatch(ST_X, 0, sbar); + + if (netgame) + V_DrawPatch(ST_FX, 0, faceback); + + V_RestoreBuffer(); + + V_CopyRect(ST_X, 0, st_backing_screen, ST_WIDTH, ST_HEIGHT, ST_X, ST_Y); + } +} + +// Respond to keyboard input events, +// intercept cheats. +boolean ST_Responder(event_t *ev) { + int i; + + // Filter automap on/off. + if (ev->type == ev_keyup && ((ev->data1 & 0xffff0000) == AM_MSGHEADER)) { + switch (ev->data1) { + case AM_MSGENTERED: + st_gamestate = AutomapState; + st_firsttime = true; + break; + + case AM_MSGEXITED: + // fprintf(stderr, "AM exited\n"); + st_gamestate = FirstPersonState; + break; + } + } + + // if a user keypress... + else if (ev->type == ev_keydown) { + if (!netgame && gameskill != sk_nightmare) { + // 'dqd' cheat for toggleable god mode + if (cht_CheckCheat(&cheat_god, ev->data2)) { + plyr->cheats ^= CF_GODMODE; + if (plyr->cheats & CF_GODMODE) { + if (plyr->mo) + plyr->mo->health = 100; + + plyr->health = 100; + plyr->message = STSTR_DQDON; + } else + plyr->message = STSTR_DQDOFF; + } + // 'fa' cheat for killer fucking arsenal + else if (cht_CheckCheat(&cheat_ammonokey, ev->data2)) { + plyr->armorpoints = 200; + plyr->armortype = 2; + + for (i = 0; i < NUMWEAPONS; i++) + plyr->weaponowned[i] = true; + + for (i = 0; i < NUMAMMO; i++) + plyr->ammo[i] = plyr->maxammo[i]; + + plyr->message = STSTR_FAADDED; + } + // 'kfa' cheat for key full ammo + else if (cht_CheckCheat(&cheat_ammo, ev->data2)) { + plyr->armorpoints = 200; + plyr->armortype = 2; + + for (i = 0; i < NUMWEAPONS; i++) + plyr->weaponowned[i] = true; + + for (i = 0; i < NUMAMMO; i++) + plyr->ammo[i] = plyr->maxammo[i]; + + for (i = 0; i < NUMCARDS; i++) + plyr->cards[i] = true; + + plyr->message = STSTR_KFAADDED; + } + // 'mus' cheat for changing music + else if (cht_CheckCheat(&cheat_mus, ev->data2)) { + + char buf[3]; + int musnum; + + plyr->message = STSTR_MUS; + cht_GetParam(&cheat_mus, buf); + + // Note: The original v1.9 had a bug that tried to play back + // the Doom II music regardless of gamemode. This was fixed + // in the Ultimate Doom executable so that it would work for + // the Doom 1 music as well. + + if (gamemode == commercial || gameversion < exe_ultimate) { + musnum = mus_runnin + (buf[0] - '0') * 10 + buf[1] - '0' - 1; + + if (((buf[0] - '0') * 10 + buf[1] - '0') > 35 && + gameversion >= exe_doom_1_8) + plyr->message = STSTR_NOMUS; + else + S_ChangeMusic(musnum, 1); + } else { + musnum = mus_e1m1 + (buf[0] - '1') * 9 + (buf[1] - '1'); + + if (((buf[0] - '1') * 9 + buf[1] - '1') > 31) + plyr->message = STSTR_NOMUS; + else + S_ChangeMusic(musnum, 1); + } + } else if ((logical_gamemission == doom && + cht_CheckCheat(&cheat_noclip, ev->data2)) || + (logical_gamemission != doom && + cht_CheckCheat(&cheat_commercial_noclip, ev->data2))) { + // Noclip cheat. + // For Doom 1, use the idspipsopd cheat; for all others, use + // idclip + + plyr->cheats ^= CF_NOCLIP; + + if (plyr->cheats & CF_NOCLIP) + plyr->message = STSTR_NCON; + else + plyr->message = STSTR_NCOFF; + } + // 'behold?' power-up cheats + for (i = 0; i < 6; i++) { + if (cht_CheckCheat(&cheat_powerup[i], ev->data2)) { + if (!plyr->powers[i]) + P_GivePower(plyr, i); + else if (i != pw_strength) + plyr->powers[i] = 1; + else + plyr->powers[i] = 0; + + plyr->message = STSTR_BEHOLDX; + } + } + + // 'behold' power-up menu + if (cht_CheckCheat(&cheat_powerup[6], ev->data2)) { + plyr->message = STSTR_BEHOLD; + } + // 'choppers' invulnerability & chainsaw + else if (cht_CheckCheat(&cheat_choppers, ev->data2)) { + plyr->weaponowned[wp_chainsaw] = true; + plyr->powers[pw_invulnerability] = true; + plyr->message = STSTR_CHOPPERS; + } + // 'mypos' for player position + else if (cht_CheckCheat(&cheat_mypos, ev->data2)) { + static char buf[ST_MSGWIDTH]; + M_snprintf(buf, sizeof(buf), "ang=0x%x;x,y=(0x%x,0x%x)", + players[consoleplayer].mo->angle, + players[consoleplayer].mo->x, players[consoleplayer].mo->y); + plyr->message = buf; + } + } + + // 'clev' change-level cheat + if (!netgame && cht_CheckCheat(&cheat_clev, ev->data2)) { + char buf[3]; + int epsd; + int map; + + cht_GetParam(&cheat_clev, buf); + + if (gamemode == commercial) { + epsd = 0; + map = (buf[0] - '0') * 10 + buf[1] - '0'; + } else { + epsd = buf[0] - '0'; + map = buf[1] - '0'; + + // Chex.exe always warps to episode 1. + + if (gameversion == exe_chex) { + if (epsd > 1) { + epsd = 1; + } + if (map > 5) { + map = 5; + } + } + } + + // Catch invalid maps. + if (gamemode != commercial) { + if (epsd < 1) { + return false; + } + if (epsd > 4) { + return false; + } + if (epsd == 4 && gameversion < exe_ultimate) { + return false; + } + if (map < 1) { + return false; + } + if (map > 9) { + return false; + } + } else { + if (map < 1) { + return false; + } + if (map > 40) { + return false; + } + } + + // So be it. + plyr->message = STSTR_CLEV; + G_DeferedInitNew(gameskill, epsd, map); + } + } + return false; +} + +int ST_calcPainOffset(void) { + int health; + static int lastcalc; + static int oldhealth = -1; + + health = plyr->health > 100 ? 100 : plyr->health; + + if (health != oldhealth) { + lastcalc = ST_FACESTRIDE * (((100 - health) * ST_NUMPAINFACES) / 101); + oldhealth = health; + } + return lastcalc; +} + +// +// This is a not-very-pretty routine which handles +// the face states and their timing. +// the precedence of expressions is: +// dead > evil grin > turned head > straight ahead +// +void ST_updateFaceWidget(void) { + int i; + angle_t badguyangle; + angle_t diffang; + static int lastattackdown = -1; + static int priority = 0; + boolean doevilgrin; + + if (priority < 10) { + // dead + if (!plyr->health) { + priority = 9; + st_faceindex = ST_DEADFACE; + st_facecount = 1; + } + } + + if (priority < 9) { + if (plyr->bonuscount) { + // picking up bonus + doevilgrin = false; + + for (i = 0; i < NUMWEAPONS; i++) { + if (oldweaponsowned[i] != plyr->weaponowned[i]) { + doevilgrin = true; + oldweaponsowned[i] = plyr->weaponowned[i]; + } + } + if (doevilgrin) { + // evil grin if just picked up weapon + priority = 8; + st_facecount = ST_EVILGRINCOUNT; + st_faceindex = ST_calcPainOffset() + ST_EVILGRINOFFSET; + } + } + } + + if (priority < 8) { + if (plyr->damagecount && plyr->attacker && plyr->attacker != plyr->mo) { + // being attacked + priority = 7; + + if (plyr->health - st_oldhealth > ST_MUCHPAIN) { + st_facecount = ST_TURNCOUNT; + st_faceindex = ST_calcPainOffset() + ST_OUCHOFFSET; + } else { + badguyangle = R_PointToAngle2(plyr->mo->x, plyr->mo->y, + plyr->attacker->x, plyr->attacker->y); + + if (badguyangle > plyr->mo->angle) { + // whether right or left + diffang = badguyangle - plyr->mo->angle; + i = diffang > ANG180; + } else { + // whether left or right + diffang = plyr->mo->angle - badguyangle; + i = diffang <= ANG180; + } // confusing, aint it? + + st_facecount = ST_TURNCOUNT; + st_faceindex = ST_calcPainOffset(); + + if (diffang < ANG45) { + // head-on + st_faceindex += ST_RAMPAGEOFFSET; + } else if (i) { + // turn face right + st_faceindex += ST_TURNOFFSET; + } else { + // turn face left + st_faceindex += ST_TURNOFFSET + 1; + } + } + } + } + + if (priority < 7) { + // getting hurt because of your own damn stupidity + if (plyr->damagecount) { + if (plyr->health - st_oldhealth > ST_MUCHPAIN) { + priority = 7; + st_facecount = ST_TURNCOUNT; + st_faceindex = ST_calcPainOffset() + ST_OUCHOFFSET; + } else { + priority = 6; + st_facecount = ST_TURNCOUNT; + st_faceindex = ST_calcPainOffset() + ST_RAMPAGEOFFSET; + } + } + } + + if (priority < 6) { + // rapid firing + if (plyr->attackdown) { + if (lastattackdown == -1) + lastattackdown = ST_RAMPAGEDELAY; + else if (!--lastattackdown) { + priority = 5; + st_faceindex = ST_calcPainOffset() + ST_RAMPAGEOFFSET; + st_facecount = 1; + lastattackdown = 1; + } + } else + lastattackdown = -1; + } + + if (priority < 5) { + // invulnerability + if ((plyr->cheats & CF_GODMODE) || plyr->powers[pw_invulnerability]) { + priority = 4; + + st_faceindex = ST_GODFACE; + st_facecount = 1; + } + } + + // look left or look right if the facecount has timed out + if (!st_facecount) { + st_faceindex = ST_calcPainOffset() + (st_randomnumber % 3); + st_facecount = ST_STRAIGHTFACECOUNT; + priority = 0; + } + + st_facecount--; +} + +void ST_updateWidgets(void) { + static int largeammo = 1994; // means "n/a" + int i; + + // must redirect the pointer if the ready weapon has changed. + // if (w_ready.data != plyr->readyweapon) + // { + if (weaponinfo[plyr->readyweapon].ammo == am_noammo) + w_ready.num = &largeammo; + else + w_ready.num = &plyr->ammo[weaponinfo[plyr->readyweapon].ammo]; + //{ + // static int tic=0; + // static int dir=-1; + // if (!(tic&15)) + // plyr->ammo[weaponinfo[plyr->readyweapon].ammo]+=dir; + // if (plyr->ammo[weaponinfo[plyr->readyweapon].ammo] == -100) + // dir = 1; + // tic++; + // } + w_ready.data = plyr->readyweapon; + + // if (*w_ready.on) + // STlib_updateNum(&w_ready, true); + // refresh weapon change + // } + + // update keycard multiple widgets + for (i = 0; i < 3; i++) { + keyboxes[i] = plyr->cards[i] ? i : -1; + + if (plyr->cards[i + 3]) + keyboxes[i] = i + 3; + } + + // refresh everything if this is him coming back to life + ST_updateFaceWidget(); + + // used by the w_armsbg widget + st_notdeathmatch = !deathmatch; + + // used by w_arms[] widgets + st_armson = st_statusbaron && !deathmatch; + + // used by w_frags widget + st_fragson = deathmatch && st_statusbaron; + st_fragscount = 0; + + for (i = 0; i < MAXPLAYERS; i++) { + if (i != consoleplayer) + st_fragscount += plyr->frags[i]; + else + st_fragscount -= plyr->frags[i]; + } + + // get rid of chat window if up because of message + if (!--st_msgcounter) + st_chat = st_oldchat; +} + +void ST_Ticker(void) { + + st_clock++; + st_randomnumber = M_Random(); + ST_updateWidgets(); + st_oldhealth = plyr->health; +} + +static int st_palette = 0; + +void ST_doPaletteStuff(void) { + + int palette; + byte *pal; + int cnt; + int bzc; + + cnt = plyr->damagecount; + + if (plyr->powers[pw_strength]) { + // slowly fade the berzerk out + bzc = 12 - (plyr->powers[pw_strength] >> 6); + + if (bzc > cnt) + cnt = bzc; + } + + if (cnt) { + palette = (cnt + 7) >> 3; + + if (palette >= NUMREDPALS) + palette = NUMREDPALS - 1; + + palette += STARTREDPALS; + } + + else if (plyr->bonuscount) { + palette = (plyr->bonuscount + 7) >> 3; + + if (palette >= NUMBONUSPALS) + palette = NUMBONUSPALS - 1; + + palette += STARTBONUSPALS; + } + + else if (plyr->powers[pw_ironfeet] > 4 * 32 || plyr->powers[pw_ironfeet] & 8) + palette = RADIATIONPAL; + else + palette = 0; + + // In Chex Quest, the player never sees red. Instead, the + // radiation suit palette is used to tint the screen green, + // as though the player is being covered in goo by an + // attacking flemoid. + + if (gameversion == exe_chex && palette >= STARTREDPALS && + palette < STARTREDPALS + NUMREDPALS) { + palette = RADIATIONPAL; + } + + if (palette != st_palette) { + st_palette = palette; + pal = (byte *)W_CacheLumpNum(lu_palette, PU_CACHE) + palette * 768; + I_SetPalette(pal); + } +} + +void ST_drawWidgets(boolean refresh) { + int i; + + // used by w_arms[] widgets + st_armson = st_statusbaron && !deathmatch; + + // used by w_frags widget + st_fragson = deathmatch && st_statusbaron; + + STlib_updateNum(&w_ready, refresh); + + for (i = 0; i < 4; i++) { + STlib_updateNum(&w_ammo[i], refresh); + STlib_updateNum(&w_maxammo[i], refresh); + } + + STlib_updatePercent(&w_health, refresh); + STlib_updatePercent(&w_armor, refresh); + + STlib_updateBinIcon(&w_armsbg, refresh); + + for (i = 0; i < 6; i++) + STlib_updateMultIcon(&w_arms[i], refresh); + + STlib_updateMultIcon(&w_faces, refresh); + + for (i = 0; i < 3; i++) + STlib_updateMultIcon(&w_keyboxes[i], refresh); + + STlib_updateNum(&w_frags, refresh); +} + +void ST_doRefresh(void) { + + st_firsttime = false; + + // draw status bar background to off-screen buff + ST_refreshBackground(); + + // and refresh all widgets + ST_drawWidgets(true); +} + +void ST_diffDraw(void) { + // update all widgets + ST_drawWidgets(false); +} + +void ST_Drawer(boolean fullscreen, boolean refresh) { + + st_statusbaron = (!fullscreen) || automapactive; + st_firsttime = st_firsttime || refresh; + + // Do red-/gold-shifts from damage/items + ST_doPaletteStuff(); + + // If just after ST_Start(), refresh all + if (st_firsttime) + ST_doRefresh(); + // Otherwise, update as little as possible + else + ST_diffDraw(); +} + +typedef void (*load_callback_t)(char *lumpname, patch_t **variable); + +// Iterates through all graphics to be loaded or unloaded, along with +// the variable they use, invoking the specified callback function. + +static void ST_loadUnloadGraphics(load_callback_t callback) { + + int i; + int j; + int facenum; + + char namebuf[9]; + + // Load the numbers, tall and short + for (i = 0; i < 10; i++) { + M_snprintf(namebuf, 9, "STTNUM%d", i); + callback(namebuf, &tallnum[i]); + + M_snprintf(namebuf, 9, "STYSNUM%d", i); + callback(namebuf, &shortnum[i]); + } + + // Load percent key. + // Note: why not load STMINUS here, too? + + callback("STTPRCNT", &tallpercent); + + // key cards + for (i = 0; i < NUMCARDS; i++) { + M_snprintf(namebuf, 9, "STKEYS%d", i); + callback(namebuf, &keys[i]); + } + + // arms background + callback("STARMS", &armsbg); + + // arms ownership widgets + for (i = 0; i < 6; i++) { + M_snprintf(namebuf, 9, "STGNUM%d", i + 2); + + // gray # + callback(namebuf, &arms[i][0]); + + // yellow # + arms[i][1] = shortnum[i + 2]; + } + + // face backgrounds for different color players + M_snprintf(namebuf, 9, "STFB%d", consoleplayer); + callback(namebuf, &faceback); + + // status bar background bits + callback("STBAR", &sbar); + + // face states + facenum = 0; + for (i = 0; i < ST_NUMPAINFACES; i++) { + for (j = 0; j < ST_NUMSTRAIGHTFACES; j++) { + M_snprintf(namebuf, 9, "STFST%d%d", i, j); + callback(namebuf, &faces[facenum]); + ++facenum; + } + M_snprintf(namebuf, 9, "STFTR%d0", i); // turn right + callback(namebuf, &faces[facenum]); + ++facenum; + M_snprintf(namebuf, 9, "STFTL%d0", i); // turn left + callback(namebuf, &faces[facenum]); + ++facenum; + M_snprintf(namebuf, 9, "STFOUCH%d", i); // ouch! + callback(namebuf, &faces[facenum]); + ++facenum; + M_snprintf(namebuf, 9, "STFEVL%d", i); // evil grin ;) + callback(namebuf, &faces[facenum]); + ++facenum; + M_snprintf(namebuf, 9, "STFKILL%d", i); // pissed off + callback(namebuf, &faces[facenum]); + ++facenum; + } + + callback("STFGOD0", &faces[facenum]); + ++facenum; + callback("STFDEAD0", &faces[facenum]); + ++facenum; +} + +static void ST_loadCallback(char *lumpname, patch_t **variable) { + *variable = W_CacheLumpName(lumpname, PU_STATIC); +} + +void ST_loadGraphics(void) { ST_loadUnloadGraphics(ST_loadCallback); } + +void ST_loadData(void) { + lu_palette = W_GetNumForName("PLAYPAL"); + ST_loadGraphics(); +} + +static void ST_unloadCallback(char *lumpname, patch_t **variable) { + W_ReleaseLumpName(lumpname); + *variable = NULL; +} + +void ST_unloadGraphics(void) { ST_loadUnloadGraphics(ST_unloadCallback); } + +void ST_unloadData(void) { ST_unloadGraphics(); } + +void ST_initData(void) { + + int i; + + st_firsttime = true; + plyr = &players[consoleplayer]; + + st_clock = 0; + st_chatstate = StartChatState; + st_gamestate = FirstPersonState; + + st_statusbaron = true; + st_oldchat = st_chat = false; + st_cursoron = false; + + st_faceindex = 0; + st_palette = -1; + + st_oldhealth = -1; + + for (i = 0; i < NUMWEAPONS; i++) + oldweaponsowned[i] = plyr->weaponowned[i]; + + for (i = 0; i < 3; i++) + keyboxes[i] = -1; + + STlib_init(); +} + +void ST_createWidgets(void) { + + int i; + + // ready weapon ammo + STlib_initNum(&w_ready, ST_AMMOX, ST_AMMOY, tallnum, + &plyr->ammo[weaponinfo[plyr->readyweapon].ammo], + &st_statusbaron, ST_AMMOWIDTH); + + // the last weapon type + w_ready.data = plyr->readyweapon; + + // health percentage + STlib_initPercent(&w_health, ST_HEALTHX, ST_HEALTHY, tallnum, &plyr->health, + &st_statusbaron, tallpercent); + + // arms background + STlib_initBinIcon(&w_armsbg, ST_ARMSBGX, ST_ARMSBGY, armsbg, + &st_notdeathmatch, &st_statusbaron); + + // weapons owned + for (i = 0; i < 6; i++) { + STlib_initMultIcon(&w_arms[i], ST_ARMSX + (i % 3) * ST_ARMSXSPACE, + ST_ARMSY + (i / 3) * ST_ARMSYSPACE, arms[i], + &plyr->weaponowned[i + 1], &st_armson); + } + + // frags sum + STlib_initNum(&w_frags, ST_FRAGSX, ST_FRAGSY, tallnum, &st_fragscount, + &st_fragson, ST_FRAGSWIDTH); + + // faces + STlib_initMultIcon(&w_faces, ST_FACESX, ST_FACESY, faces, &st_faceindex, + &st_statusbaron); + + // armor percentage - should be colored later + STlib_initPercent(&w_armor, ST_ARMORX, ST_ARMORY, tallnum, &plyr->armorpoints, + &st_statusbaron, tallpercent); + + // keyboxes 0-2 + STlib_initMultIcon(&w_keyboxes[0], ST_KEY0X, ST_KEY0Y, keys, &keyboxes[0], + &st_statusbaron); + + STlib_initMultIcon(&w_keyboxes[1], ST_KEY1X, ST_KEY1Y, keys, &keyboxes[1], + &st_statusbaron); + + STlib_initMultIcon(&w_keyboxes[2], ST_KEY2X, ST_KEY2Y, keys, &keyboxes[2], + &st_statusbaron); + + // ammo count (all four kinds) + STlib_initNum(&w_ammo[0], ST_AMMO0X, ST_AMMO0Y, shortnum, &plyr->ammo[0], + &st_statusbaron, ST_AMMO0WIDTH); + + STlib_initNum(&w_ammo[1], ST_AMMO1X, ST_AMMO1Y, shortnum, &plyr->ammo[1], + &st_statusbaron, ST_AMMO1WIDTH); + + STlib_initNum(&w_ammo[2], ST_AMMO2X, ST_AMMO2Y, shortnum, &plyr->ammo[2], + &st_statusbaron, ST_AMMO2WIDTH); + + STlib_initNum(&w_ammo[3], ST_AMMO3X, ST_AMMO3Y, shortnum, &plyr->ammo[3], + &st_statusbaron, ST_AMMO3WIDTH); + + // max ammo count (all four kinds) + STlib_initNum(&w_maxammo[0], ST_MAXAMMO0X, ST_MAXAMMO0Y, shortnum, + &plyr->maxammo[0], &st_statusbaron, ST_MAXAMMO0WIDTH); + + STlib_initNum(&w_maxammo[1], ST_MAXAMMO1X, ST_MAXAMMO1Y, shortnum, + &plyr->maxammo[1], &st_statusbaron, ST_MAXAMMO1WIDTH); + + STlib_initNum(&w_maxammo[2], ST_MAXAMMO2X, ST_MAXAMMO2Y, shortnum, + &plyr->maxammo[2], &st_statusbaron, ST_MAXAMMO2WIDTH); + + STlib_initNum(&w_maxammo[3], ST_MAXAMMO3X, ST_MAXAMMO3Y, shortnum, + &plyr->maxammo[3], &st_statusbaron, ST_MAXAMMO3WIDTH); +} + +static boolean st_stopped = true; + +void ST_Start(void) { + + if (!st_stopped) + ST_Stop(); + + ST_initData(); + ST_createWidgets(); + st_stopped = false; +} + +void ST_Stop(void) { + if (st_stopped) + return; + + I_SetPalette(W_CacheLumpNum(lu_palette, PU_CACHE)); + + st_stopped = true; +} + +void ST_Init(void) { + ST_loadData(); + st_backing_screen = (pixel_t *)Z_Malloc( + ST_WIDTH * ST_HEIGHT * sizeof(*st_backing_screen), PU_STATIC, 0); +} diff --git a/client/src/st_stuff.h b/client/src/st_stuff.h new file mode 100644 index 0000000..ca4ad84 --- /dev/null +++ b/client/src/st_stuff.h @@ -0,0 +1,81 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Status bar code. +// Does the face/direction indicator animatin. +// Does palette indicators as well (red pain/berserk, bright pickup) +// + +#ifndef __STSTUFF_H__ +#define __STSTUFF_H__ + +#include "d_event.h" +#include "doomtype.h" +#include "i_video.h" +#include "m_cheat.h" + +// Size of statusbar. +// Now sensitive for scaling. +#define ST_HEIGHT 32 +#define ST_WIDTH SCREENWIDTH +#define ST_Y (SCREENHEIGHT - ST_HEIGHT) + +// +// STATUS BAR +// + +// Called by main loop. +boolean ST_Responder(event_t *ev); + +// Called by main loop. +void ST_Ticker(void); + +// Called by main loop. +void ST_Drawer(boolean fullscreen, boolean refresh); + +// Called when the console player is spawned on each level. +void ST_Start(void); + +// Called by startup code. +void ST_Init(void); + +// States for status bar code. +typedef enum { + AutomapState, + FirstPersonState + +} st_stateenum_t; + +// States for the chat code. +typedef enum { + StartChatState, + WaitDestState, + GetChatState + +} st_chatstateenum_t; + +extern byte *st_backing_screen; +extern cheatseq_t cheat_mus; +extern cheatseq_t cheat_god; +extern cheatseq_t cheat_ammo; +extern cheatseq_t cheat_ammonokey; +extern cheatseq_t cheat_noclip; +extern cheatseq_t cheat_commercial_noclip; +extern cheatseq_t cheat_powerup[7]; +extern cheatseq_t cheat_choppers; +extern cheatseq_t cheat_clev; +extern cheatseq_t cheat_mypos; + +#endif diff --git a/client/src/statdump.c b/client/src/statdump.c new file mode 100644 index 0000000..4fc1620 --- /dev/null +++ b/client/src/statdump.c @@ -0,0 +1,310 @@ +/* + +Copyright(C) 2005-2014 Simon Howard + +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. + +-- + +Functions for presenting the information captured from the statistics +buffer to a file. + +*/ + +#include +#include + +#include "d_mode.h" +#include "d_player.h" +#include "doomdef.h" +#include "i_timer.h" +#include "m_argv.h" +#include "statdump.h" + +/* Par times for E1M1-E1M9. */ +static const int doom1_par_times[] = { + 30, 75, 120, 90, 165, 180, 180, 30, 165, +}; + +/* Par times for MAP01-MAP09. */ +static const int doom2_par_times[] = { + 30, 90, 120, 120, 90, 150, 120, 120, 270, +}; + +/* Player colors. */ +static const char *player_colors[] = {"Green", "Indigo", "Brown", "Red"}; + +// Array of end-of-level statistics that have been captured. + +#define MAX_CAPTURES 32 +static wbstartstruct_t captured_stats[MAX_CAPTURES]; +static int num_captured_stats = 0; + +static GameMission_t discovered_gamemission = none; + +/* Try to work out whether this is a Doom 1 or Doom 2 game, by looking + * at the episode and map, and the par times. This is used to decide + * how to format the level name. Unfortunately, in some cases it is + * impossible to determine whether this is Doom 1 or Doom 2. */ + +static void DiscoverGamemode(wbstartstruct_t *stats, int num_stats) { + int partime; + int level; + int i; + + if (discovered_gamemission != none) { + return; + } + + for (i = 0; i < num_stats; ++i) { + level = stats[i].last; + + /* If episode 2, 3 or 4, this is Doom 1. */ + + if (stats[i].epsd > 0) { + discovered_gamemission = doom; + return; + } + + /* This is episode 1. If this is level 10 or higher, + it must be Doom 2. */ + + if (level >= 9) { + discovered_gamemission = doom2; + return; + } + + /* Try to work out if this is Doom 1 or Doom 2 by looking + at the par time. */ + + partime = stats[i].partime; + + if (partime == doom1_par_times[level] * TICRATE && + partime != doom2_par_times[level] * TICRATE) { + discovered_gamemission = doom; + return; + } + + if (partime != doom1_par_times[level] * TICRATE && + partime == doom2_par_times[level] * TICRATE) { + discovered_gamemission = doom2; + return; + } + } +} + +/* Returns the number of players active in the given stats buffer. */ + +static int GetNumPlayers(wbstartstruct_t *stats) { + int i; + int num_players = 0; + + for (i = 0; i < MAXPLAYERS; ++i) { + if (stats->plyr[i].in) { + ++num_players; + } + } + + return num_players; +} + +static void PrintBanner(FILE *stream) { + fprintf(stream, "===========================================\n"); +} + +static void PrintPercentage(FILE *stream, int amount, int total) { + if (total == 0) { + fprintf(stream, "0"); + } else { + fprintf(stream, "%i / %i", amount, total); + + // statdump.exe is a 16-bit program, so very occasionally an + // integer overflow can occur when doing this calculation with + // a large value. Therefore, cast to short to give the same + // output. + + fprintf(stream, " (%i%%)", (short)(amount * 100) / total); + } +} + +/* Display statistics for a single player. */ + +static void PrintPlayerStats(FILE *stream, wbstartstruct_t *stats, + int player_num) { + wbplayerstruct_t *player = &stats->plyr[player_num]; + + fprintf(stream, "Player %i (%s):\n", player_num + 1, + player_colors[player_num]); + + /* Kills percentage */ + + fprintf(stream, "\tKills: "); + PrintPercentage(stream, player->skills, stats->maxkills); + fprintf(stream, "\n"); + + /* Items percentage */ + + fprintf(stream, "\tItems: "); + PrintPercentage(stream, player->sitems, stats->maxitems); + fprintf(stream, "\n"); + + /* Secrets percentage */ + + fprintf(stream, "\tSecrets: "); + PrintPercentage(stream, player->ssecret, stats->maxsecret); + fprintf(stream, "\n"); +} + +/* Frags table for multiplayer games. */ + +static void PrintFragsTable(FILE *stream, wbstartstruct_t *stats) { + int x, y; + + fprintf(stream, "Frags:\n"); + + /* Print header */ + + fprintf(stream, "\t\t"); + + for (x = 0; x < MAXPLAYERS; ++x) { + + if (!stats->plyr[x].in) { + continue; + } + + fprintf(stream, "%s\t", player_colors[x]); + } + + fprintf(stream, "\n"); + + fprintf(stream, "\t\t-------------------------------- VICTIMS\n"); + + /* Print table */ + + for (y = 0; y < MAXPLAYERS; ++y) { + if (!stats->plyr[y].in) { + continue; + } + + fprintf(stream, "\t%s\t|", player_colors[y]); + + for (x = 0; x < MAXPLAYERS; ++x) { + if (!stats->plyr[x].in) { + continue; + } + + fprintf(stream, "%i\t", stats->plyr[y].frags[x]); + } + + fprintf(stream, "\n"); + } + + fprintf(stream, "\t\t|\n"); + fprintf(stream, "\t KILLERS\n"); +} + +/* Displays the level name: MAPxy or ExMy, depending on game mode. */ + +static void PrintLevelName(FILE *stream, int episode, int level) { + PrintBanner(stream); + + switch (discovered_gamemission) { + + case doom: + fprintf(stream, "E%iM%i\n", episode + 1, level + 1); + break; + case doom2: + fprintf(stream, "MAP%02i\n", level + 1); + break; + default: + case none: + fprintf(stream, "E%iM%i / MAP%02i\n", episode + 1, level + 1, level + 1); + break; + } + + PrintBanner(stream); +} + +/* Print details of a statistics buffer to the given file. */ + +static void PrintStats(FILE *stream, wbstartstruct_t *stats) { + int leveltime, partime; + int i; + + PrintLevelName(stream, stats->epsd, stats->last); + fprintf(stream, "\n"); + + leveltime = stats->plyr[0].stime / TICRATE; + partime = stats->partime / TICRATE; + fprintf(stream, "Time: %i:%02i", leveltime / 60, leveltime % 60); + fprintf(stream, " (par: %i:%02i)\n", partime / 60, partime % 60); + fprintf(stream, "\n"); + + for (i = 0; i < MAXPLAYERS; ++i) { + if (stats->plyr[i].in) { + PrintPlayerStats(stream, stats, i); + } + } + + if (GetNumPlayers(stats) >= 2) { + PrintFragsTable(stream, stats); + } + + fprintf(stream, "\n"); +} + +void StatCopy(wbstartstruct_t *stats) { + if (M_ParmExists("-statdump") && num_captured_stats < MAX_CAPTURES) { + memcpy(&captured_stats[num_captured_stats], stats, sizeof(wbstartstruct_t)); + ++num_captured_stats; + } +} + +void StatDump(void) { + FILE *dumpfile; + int i; + + //! + // @category compat + // @arg + // + // Dump statistics information to the specified file on the levels + // that were played. The output from this option matches the output + // from statdump.exe (see ctrlapi.zip in the /idgames archive). + // + + i = M_CheckParmWithArgs("-statdump", 1); + + if (i > 0) { + printf("Statistics captured for %i level(s)\n", num_captured_stats); + + // We actually know what the real gamemission is, but this has + // to match the output from statdump.exe. + + DiscoverGamemode(captured_stats, num_captured_stats); + + // Allow "-" as output file, for stdout. + + if (strcmp(myargv[i + 1], "-") != 0) { + dumpfile = fopen(myargv[i + 1], "w"); + } else { + dumpfile = NULL; + } + + for (i = 0; i < num_captured_stats; ++i) { + PrintStats(dumpfile, &captured_stats[i]); + } + + if (dumpfile != NULL) { + fclose(dumpfile); + } + } +} diff --git a/client/src/statdump.h b/client/src/statdump.h new file mode 100644 index 0000000..e75719c --- /dev/null +++ b/client/src/statdump.h @@ -0,0 +1,25 @@ +/* + +Copyright(C) 2005-2014 Simon Howard + +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. + +*/ + +#ifndef DOOM_STATDUMP_H +#define DOOM_STATDUMP_H + +#include "d_player.h" + +void StatCopy(wbstartstruct_t *stats); +void StatDump(void); + +#endif /* #ifndef DOOM_STATDUMP_H */ diff --git a/client/src/streaming.c b/client/src/streaming.c new file mode 100644 index 0000000..562824f --- /dev/null +++ b/client/src/streaming.c @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i_video.h" +#include "streaming.h" + + +static int sockfd; +static struct sockaddr_in servaddr; +static unsigned int serveraddr_len; + +void connect_to_server() { + // Creating socket file descriptor + if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) { + perror("socket creation failed"); + exit(EXIT_FAILURE); + } + memset(&servaddr, 0, sizeof(servaddr)); + + // Filling server information + servaddr.sin_family = AF_INET; + servaddr.sin_port = htons(STREAMPORT); + servaddr.sin_addr.s_addr = INADDR_ANY; + + StreamMsg msg; + msg.type = MSGT_HELLO; + char response[] = "Where are my frames?"; + memcpy(msg.data, response, sizeof(response)); + sendto( + sockfd, + (const char *)(&msg), sizeof(msg), + MSG_CONFIRM, + (const struct sockaddr *) &servaddr, sizeof(servaddr) + ); + + recvfrom( + sockfd, + (char *)(&msg), sizeof(msg), + MSG_WAITALL, + (struct sockaddr *) &servaddr, &serveraddr_len + ); + if (msg.type == MSGT_HELLO) { + printf("Server says hello\n"); + } else { + printf("No hello from server, are you reconnecting?\n"); + } + + return; +} + + +void recv_msgs() { + while (1) { + StreamMsg msg; + ssize_t n = recvfrom( + sockfd, + (char *)(&msg), sizeof(msg), + MSG_DONTWAIT, + (struct sockaddr *) &servaddr, &serveraddr_len + ); + if (n == -1) return; + + switch (msg.type) { + case MSGT_SCANLINE: + memcpy(&I_VideoBuffer[msg.idx*STREAMWIDTH], msg.data, STREAMSCANSIZE); + break; + + default: + perror("Received unknown msg type\n"); + exit(EXIT_FAILURE); + } + } +} + + +void recv_loop() { + unsigned char last_scanline = 0; + + while (1) { + StreamMsg msg; + ssize_t n = recvfrom( + sockfd, + (char *)(&msg), sizeof(msg), + MSG_DONTWAIT, + (struct sockaddr *) &servaddr, &serveraddr_len + ); + if (n == -1) { + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 1000; + if(nanosleep(&ts, &ts)) { + printf("Nanosleep failed\n"); + exit(1); + } + continue; + } + + switch (msg.type) { + case MSGT_SCANLINE: + memcpy(&I_VideoBuffer[msg.idx*STREAMWIDTH], msg.data, STREAMSCANSIZE); + if (msg.idx < last_scanline) { + I_FinishUpdate(); + } + last_scanline = msg.idx; + break; + + default: + perror("Received unknown msg type\n"); + exit(EXIT_FAILURE); + } + } +} \ No newline at end of file diff --git a/client/src/streaming.h b/client/src/streaming.h new file mode 100644 index 0000000..4e118fd --- /dev/null +++ b/client/src/streaming.h @@ -0,0 +1,12 @@ +#ifndef __STREAMING__ +#define __STREAMING__ + +#include "../../common/streaming.h" + + +void connect_to_server(); +void recv_msgs(); +void recv_loop(); + + +#endif // __STREAMING__ \ No newline at end of file diff --git a/client/src/tables.c b/client/src/tables.c new file mode 100644 index 0000000..698ae98 --- /dev/null +++ b/client/src/tables.c @@ -0,0 +1,2227 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Lookup tables. +// Do not try to look them up :-). +// In the order of appearance: +// +// int finetangent[4096] - Tangens LUT. +// Should work with BAM fairly well (12 of 16bit, +// effectively, by shifting). +// +// int finesine[10240] - Sine lookup. +// Guess what, serves as cosine, too. +// Remarkable thing is, how to use BAMs with this? +// +// int tantoangle[2049] - ArcTan LUT, +// maps tan(angle) to angle fast. Gotta search. +// +// + +#include "tables.h" + +// to get a global angle from cartesian coordinates, the coordinates are +// flipped until they are in the first octant of the coordinate system, then +// the y (<=x) is scaled and divided by x to get a tangent (slope) value +// which is looked up in the tantoangle[] table. The +1 size is to handle +// the case when x==y without additional checking. + +int SlopeDiv(unsigned int num, unsigned int den) +{ + unsigned ans; + + if (den < 512) + { + return SLOPERANGE; + } + else + { + ans = (num << 3) / (den >> 8); + + if (ans <= SLOPERANGE) + { + return ans; + } + else + { + return SLOPERANGE; + } + } +} + +const fixed_t finetangent[4096] = +{ + -170910304,-56965752,-34178904,-24413316,-18988036,-15535599,-13145455,-11392683, + -10052327,-8994149,-8137527,-7429880,-6835455,-6329090,-5892567,-5512368, + -5178251,-4882318,-4618375,-4381502,-4167737,-3973855,-3797206,-3635590, + -3487165,-3350381,-3223918,-3106651,-2997613,-2895966,-2800983,-2712030, + -2628549,-2550052,-2476104,-2406322,-2340362,-2277919,-2218719,-2162516, + -2109087,-2058233,-2009771,-1963536,-1919378,-1877161,-1836758,-1798063, + -1760956,-1725348,-1691149,-1658278,-1626658,-1596220,-1566898,-1538632, + -1511367,-1485049,-1459630,-1435065,-1411312,-1388330,-1366084,-1344537, + -1323658,-1303416,-1283783,-1264730,-1246234,-1228269,-1210813,-1193846, + -1177345,-1161294,-1145673,-1130465,-1115654,-1101225,-1087164,-1073455, + -1060087,-1047046,-1034322,-1021901,-1009774,-997931,-986361,-975054, + -964003,-953199,-942633,-932298,-922186,-912289,-902602,-893117, + -883829,-874730,-865817,-857081,-848520,-840127,-831898,-823827, + -815910,-808143,-800521,-793041,-785699,-778490,-771411,-764460, + -757631,-750922,-744331,-737853,-731486,-725227,-719074,-713023, + -707072,-701219,-695462,-689797,-684223,-678737,-673338,-668024, + -662792,-657640,-652568,-647572,-642651,-637803,-633028,-628323, + -623686,-619117,-614613,-610174,-605798,-601483,-597229,-593033, + -588896,-584815,-580789,-576818,-572901,-569035,-565221,-561456, + -557741,-554074,-550455,-546881,-543354,-539870,-536431,-533034, + -529680,-526366,-523094,-519861,-516667,-513512,-510394,-507313, + -504269,-501261,-498287,-495348,-492443,-489571,-486732,-483925, + -481150,-478406,-475692,-473009,-470355,-467730,-465133,-462565, + -460024,-457511,-455024,-452564,-450129,-447720,-445337,-442978, + -440643,-438332,-436045,-433781,-431540,-429321,-427125,-424951, + -422798,-420666,-418555,-416465,-414395,-412344,-410314,-408303, + -406311,-404338,-402384,-400448,-398530,-396630,-394747,-392882, + -391034,-389202,-387387,-385589,-383807,-382040,-380290,-378555, + -376835,-375130,-373440,-371765,-370105,-368459,-366826,-365208, + -363604,-362013,-360436,-358872,-357321,-355783,-354257,-352744, + -351244,-349756,-348280,-346816,-345364,-343924,-342495,-341078, + -339671,-338276,-336892,-335519,-334157,-332805,-331464,-330133, + -328812,-327502,-326201,-324910,-323629,-322358,-321097,-319844, + -318601,-317368,-316143,-314928,-313721,-312524,-311335,-310154, + -308983,-307819,-306664,-305517,-304379,-303248,-302126,-301011, + -299904,-298805,-297714,-296630,-295554,-294485,-293423,-292369, + -291322,-290282,-289249,-288223,-287204,-286192,-285186,-284188, + -283195,-282210,-281231,-280258,-279292,-278332,-277378,-276430, + -275489,-274553,-273624,-272700,-271782,-270871,-269965,-269064, + -268169,-267280,-266397,-265519,-264646,-263779,-262917,-262060, + -261209,-260363,-259522,-258686,-257855,-257029,-256208,-255392, + -254581,-253774,-252973,-252176,-251384,-250596,-249813,-249035, + -248261,-247492,-246727,-245966,-245210,-244458,-243711,-242967, + -242228,-241493,-240763,-240036,-239314,-238595,-237881,-237170, + -236463,-235761,-235062,-234367,-233676,-232988,-232304,-231624, + -230948,-230275,-229606,-228941,-228279,-227621,-226966,-226314, + -225666,-225022,-224381,-223743,-223108,-222477,-221849,-221225, + -220603,-219985,-219370,-218758,-218149,-217544,-216941,-216341, + -215745,-215151,-214561,-213973,-213389,-212807,-212228,-211652, + -211079,-210509,-209941,-209376,-208815,-208255,-207699,-207145, + -206594,-206045,-205500,-204956,-204416,-203878,-203342,-202809, + -202279,-201751,-201226,-200703,-200182,-199664,-199149,-198636, + -198125,-197616,-197110,-196606,-196105,-195606,-195109,-194614, + -194122,-193631,-193143,-192658,-192174,-191693,-191213,-190736, + -190261,-189789,-189318,-188849,-188382,-187918,-187455,-186995, + -186536,-186080,-185625,-185173,-184722,-184274,-183827,-183382, + -182939,-182498,-182059,-181622,-181186,-180753,-180321,-179891, + -179463,-179037,-178612,-178190,-177769,-177349,-176932,-176516, + -176102,-175690,-175279,-174870,-174463,-174057,-173653,-173251, + -172850,-172451,-172053,-171657,-171263,-170870,-170479,-170089, + -169701,-169315,-168930,-168546,-168164,-167784,-167405,-167027, + -166651,-166277,-165904,-165532,-165162,-164793,-164426,-164060, + -163695,-163332,-162970,-162610,-162251,-161893,-161537,-161182, + -160828,-160476,-160125,-159775,-159427,-159079,-158734,-158389, + -158046,-157704,-157363,-157024,-156686,-156349,-156013,-155678, + -155345,-155013,-154682,-154352,-154024,-153697,-153370,-153045, + -152722,-152399,-152077,-151757,-151438,-151120,-150803,-150487, + -150172,-149859,-149546,-149235,-148924,-148615,-148307,-148000, + -147693,-147388,-147084,-146782,-146480,-146179,-145879,-145580, + -145282,-144986,-144690,-144395,-144101,-143808,-143517,-143226, + -142936,-142647,-142359,-142072,-141786,-141501,-141217,-140934, + -140651,-140370,-140090,-139810,-139532,-139254,-138977,-138701, + -138426,-138152,-137879,-137607,-137335,-137065,-136795,-136526, + -136258,-135991,-135725,-135459,-135195,-134931,-134668,-134406, + -134145,-133884,-133625,-133366,-133108,-132851,-132594,-132339, + -132084,-131830,-131576,-131324,-131072,-130821,-130571,-130322, + -130073,-129825,-129578,-129332,-129086,-128841,-128597,-128353, + -128111,-127869,-127627,-127387,-127147,-126908,-126669,-126432, + -126195,-125959,-125723,-125488,-125254,-125020,-124787,-124555, + -124324,-124093,-123863,-123633,-123404,-123176,-122949,-122722, + -122496,-122270,-122045,-121821,-121597,-121374,-121152,-120930, + -120709,-120489,-120269,-120050,-119831,-119613,-119396,-119179, + -118963,-118747,-118532,-118318,-118104,-117891,-117678,-117466, + -117254,-117044,-116833,-116623,-116414,-116206,-115998,-115790, + -115583,-115377,-115171,-114966,-114761,-114557,-114354,-114151, + -113948,-113746,-113545,-113344,-113143,-112944,-112744,-112546, + -112347,-112150,-111952,-111756,-111560,-111364,-111169,-110974, + -110780,-110586,-110393,-110200,-110008,-109817,-109626,-109435, + -109245,-109055,-108866,-108677,-108489,-108301,-108114,-107927, + -107741,-107555,-107369,-107184,-107000,-106816,-106632,-106449, + -106266,-106084,-105902,-105721,-105540,-105360,-105180,-105000, + -104821,-104643,-104465,-104287,-104109,-103933,-103756,-103580, + -103404,-103229,-103054,-102880,-102706,-102533,-102360,-102187, + -102015,-101843,-101671,-101500,-101330,-101159,-100990,-100820, + -100651,-100482,-100314,-100146,-99979,-99812,-99645,-99479, + -99313,-99148,-98982,-98818,-98653,-98489,-98326,-98163, + -98000,-97837,-97675,-97513,-97352,-97191,-97030,-96870, + -96710,-96551,-96391,-96233,-96074,-95916,-95758,-95601, + -95444,-95287,-95131,-94975,-94819,-94664,-94509,-94354, + -94200,-94046,-93892,-93739,-93586,-93434,-93281,-93129, + -92978,-92826,-92675,-92525,-92375,-92225,-92075,-91926, + -91777,-91628,-91480,-91332,-91184,-91036,-90889,-90742, + -90596,-90450,-90304,-90158,-90013,-89868,-89724,-89579, + -89435,-89292,-89148,-89005,-88862,-88720,-88577,-88435, + -88294,-88152,-88011,-87871,-87730,-87590,-87450,-87310, + -87171,-87032,-86893,-86755,-86616,-86479,-86341,-86204, + -86066,-85930,-85793,-85657,-85521,-85385,-85250,-85114, + -84980,-84845,-84710,-84576,-84443,-84309,-84176,-84043, + -83910,-83777,-83645,-83513,-83381,-83250,-83118,-82987, + -82857,-82726,-82596,-82466,-82336,-82207,-82078,-81949, + -81820,-81691,-81563,-81435,-81307,-81180,-81053,-80925, + -80799,-80672,-80546,-80420,-80294,-80168,-80043,-79918, + -79793,-79668,-79544,-79420,-79296,-79172,-79048,-78925, + -78802,-78679,-78557,-78434,-78312,-78190,-78068,-77947, + -77826,-77705,-77584,-77463,-77343,-77223,-77103,-76983, + -76864,-76744,-76625,-76506,-76388,-76269,-76151,-76033, + -75915,-75797,-75680,-75563,-75446,-75329,-75213,-75096, + -74980,-74864,-74748,-74633,-74517,-74402,-74287,-74172, + -74058,-73944,-73829,-73715,-73602,-73488,-73375,-73262, + -73149,-73036,-72923,-72811,-72699,-72587,-72475,-72363, + -72252,-72140,-72029,-71918,-71808,-71697,-71587,-71477, + -71367,-71257,-71147,-71038,-70929,-70820,-70711,-70602, + -70494,-70385,-70277,-70169,-70061,-69954,-69846,-69739, + -69632,-69525,-69418,-69312,-69205,-69099,-68993,-68887, + -68781,-68676,-68570,-68465,-68360,-68255,-68151,-68046, + -67942,-67837,-67733,-67629,-67526,-67422,-67319,-67216, + -67113,-67010,-66907,-66804,-66702,-66600,-66498,-66396, + -66294,-66192,-66091,-65989,-65888,-65787,-65686,-65586, + -65485,-65385,-65285,-65185,-65085,-64985,-64885,-64786, + -64687,-64587,-64488,-64389,-64291,-64192,-64094,-63996, + -63897,-63799,-63702,-63604,-63506,-63409,-63312,-63215, + -63118,-63021,-62924,-62828,-62731,-62635,-62539,-62443, + -62347,-62251,-62156,-62060,-61965,-61870,-61775,-61680, + -61585,-61491,-61396,-61302,-61208,-61114,-61020,-60926, + -60833,-60739,-60646,-60552,-60459,-60366,-60273,-60181, + -60088,-59996,-59903,-59811,-59719,-59627,-59535,-59444, + -59352,-59261,-59169,-59078,-58987,-58896,-58805,-58715, + -58624,-58534,-58443,-58353,-58263,-58173,-58083,-57994, + -57904,-57815,-57725,-57636,-57547,-57458,-57369,-57281, + -57192,-57104,-57015,-56927,-56839,-56751,-56663,-56575, + -56487,-56400,-56312,-56225,-56138,-56051,-55964,-55877, + -55790,-55704,-55617,-55531,-55444,-55358,-55272,-55186, + -55100,-55015,-54929,-54843,-54758,-54673,-54587,-54502, + -54417,-54333,-54248,-54163,-54079,-53994,-53910,-53826, + -53741,-53657,-53574,-53490,-53406,-53322,-53239,-53156, + -53072,-52989,-52906,-52823,-52740,-52657,-52575,-52492, + -52410,-52327,-52245,-52163,-52081,-51999,-51917,-51835, + -51754,-51672,-51591,-51509,-51428,-51347,-51266,-51185, + -51104,-51023,-50942,-50862,-50781,-50701,-50621,-50540, + -50460,-50380,-50300,-50221,-50141,-50061,-49982,-49902, + -49823,-49744,-49664,-49585,-49506,-49427,-49349,-49270, + -49191,-49113,-49034,-48956,-48878,-48799,-48721,-48643, + -48565,-48488,-48410,-48332,-48255,-48177,-48100,-48022, + -47945,-47868,-47791,-47714,-47637,-47560,-47484,-47407, + -47331,-47254,-47178,-47102,-47025,-46949,-46873,-46797, + -46721,-46646,-46570,-46494,-46419,-46343,-46268,-46193, + -46118,-46042,-45967,-45892,-45818,-45743,-45668,-45593, + -45519,-45444,-45370,-45296,-45221,-45147,-45073,-44999, + -44925,-44851,-44778,-44704,-44630,-44557,-44483,-44410, + -44337,-44263,-44190,-44117,-44044,-43971,-43898,-43826, + -43753,-43680,-43608,-43535,-43463,-43390,-43318,-43246, + -43174,-43102,-43030,-42958,-42886,-42814,-42743,-42671, + -42600,-42528,-42457,-42385,-42314,-42243,-42172,-42101, + -42030,-41959,-41888,-41817,-41747,-41676,-41605,-41535, + -41465,-41394,-41324,-41254,-41184,-41113,-41043,-40973, + -40904,-40834,-40764,-40694,-40625,-40555,-40486,-40416, + -40347,-40278,-40208,-40139,-40070,-40001,-39932,-39863, + -39794,-39726,-39657,-39588,-39520,-39451,-39383,-39314, + -39246,-39178,-39110,-39042,-38973,-38905,-38837,-38770, + -38702,-38634,-38566,-38499,-38431,-38364,-38296,-38229, + -38161,-38094,-38027,-37960,-37893,-37826,-37759,-37692, + -37625,-37558,-37491,-37425,-37358,-37291,-37225,-37158, + -37092,-37026,-36959,-36893,-36827,-36761,-36695,-36629, + -36563,-36497,-36431,-36365,-36300,-36234,-36168,-36103, + -36037,-35972,-35907,-35841,-35776,-35711,-35646,-35580, + -35515,-35450,-35385,-35321,-35256,-35191,-35126,-35062, + -34997,-34932,-34868,-34803,-34739,-34675,-34610,-34546, + -34482,-34418,-34354,-34289,-34225,-34162,-34098,-34034, + -33970,-33906,-33843,-33779,-33715,-33652,-33588,-33525, + -33461,-33398,-33335,-33272,-33208,-33145,-33082,-33019, + -32956,-32893,-32830,-32767,-32705,-32642,-32579,-32516, + -32454,-32391,-32329,-32266,-32204,-32141,-32079,-32017, + -31955,-31892,-31830,-31768,-31706,-31644,-31582,-31520, + -31458,-31396,-31335,-31273,-31211,-31150,-31088,-31026, + -30965,-30904,-30842,-30781,-30719,-30658,-30597,-30536, + -30474,-30413,-30352,-30291,-30230,-30169,-30108,-30048, + -29987,-29926,-29865,-29805,-29744,-29683,-29623,-29562, + -29502,-29441,-29381,-29321,-29260,-29200,-29140,-29080, + -29020,-28959,-28899,-28839,-28779,-28719,-28660,-28600, + -28540,-28480,-28420,-28361,-28301,-28241,-28182,-28122, + -28063,-28003,-27944,-27884,-27825,-27766,-27707,-27647, + -27588,-27529,-27470,-27411,-27352,-27293,-27234,-27175, + -27116,-27057,-26998,-26940,-26881,-26822,-26763,-26705, + -26646,-26588,-26529,-26471,-26412,-26354,-26295,-26237, + -26179,-26120,-26062,-26004,-25946,-25888,-25830,-25772, + -25714,-25656,-25598,-25540,-25482,-25424,-25366,-25308, + -25251,-25193,-25135,-25078,-25020,-24962,-24905,-24847, + -24790,-24732,-24675,-24618,-24560,-24503,-24446,-24389, + -24331,-24274,-24217,-24160,-24103,-24046,-23989,-23932, + -23875,-23818,-23761,-23704,-23647,-23591,-23534,-23477, + -23420,-23364,-23307,-23250,-23194,-23137,-23081,-23024, + -22968,-22911,-22855,-22799,-22742,-22686,-22630,-22573, + -22517,-22461,-22405,-22349,-22293,-22237,-22181,-22125, + -22069,-22013,-21957,-21901,-21845,-21789,-21733,-21678, + -21622,-21566,-21510,-21455,-21399,-21343,-21288,-21232, + -21177,-21121,-21066,-21010,-20955,-20900,-20844,-20789, + -20734,-20678,-20623,-20568,-20513,-20457,-20402,-20347, + -20292,-20237,-20182,-20127,-20072,-20017,-19962,-19907, + -19852,-19797,-19742,-19688,-19633,-19578,-19523,-19469, + -19414,-19359,-19305,-19250,-19195,-19141,-19086,-19032, + -18977,-18923,-18868,-18814,-18760,-18705,-18651,-18597, + -18542,-18488,-18434,-18380,-18325,-18271,-18217,-18163, + -18109,-18055,-18001,-17946,-17892,-17838,-17784,-17731, + -17677,-17623,-17569,-17515,-17461,-17407,-17353,-17300, + -17246,-17192,-17138,-17085,-17031,-16977,-16924,-16870, + -16817,-16763,-16710,-16656,-16603,-16549,-16496,-16442, + -16389,-16335,-16282,-16229,-16175,-16122,-16069,-16015, + -15962,-15909,-15856,-15802,-15749,-15696,-15643,-15590, + -15537,-15484,-15431,-15378,-15325,-15272,-15219,-15166, + -15113,-15060,-15007,-14954,-14901,-14848,-14795,-14743, + -14690,-14637,-14584,-14531,-14479,-14426,-14373,-14321, + -14268,-14215,-14163,-14110,-14057,-14005,-13952,-13900, + -13847,-13795,-13742,-13690,-13637,-13585,-13533,-13480, + -13428,-13375,-13323,-13271,-13218,-13166,-13114,-13062, + -13009,-12957,-12905,-12853,-12800,-12748,-12696,-12644, + -12592,-12540,-12488,-12436,-12383,-12331,-12279,-12227, + -12175,-12123,-12071,-12019,-11967,-11916,-11864,-11812, + -11760,-11708,-11656,-11604,-11552,-11501,-11449,-11397, + -11345,-11293,-11242,-11190,-11138,-11086,-11035,-10983, + -10931,-10880,-10828,-10777,-10725,-10673,-10622,-10570, + -10519,-10467,-10415,-10364,-10312,-10261,-10209,-10158, + -10106,-10055,-10004,-9952,-9901,-9849,-9798,-9747, + -9695,-9644,-9592,-9541,-9490,-9438,-9387,-9336, + -9285,-9233,-9182,-9131,-9080,-9028,-8977,-8926, + -8875,-8824,-8772,-8721,-8670,-8619,-8568,-8517, + -8466,-8414,-8363,-8312,-8261,-8210,-8159,-8108, + -8057,-8006,-7955,-7904,-7853,-7802,-7751,-7700, + -7649,-7598,-7547,-7496,-7445,-7395,-7344,-7293, + -7242,-7191,-7140,-7089,-7038,-6988,-6937,-6886, + -6835,-6784,-6733,-6683,-6632,-6581,-6530,-6480, + -6429,-6378,-6327,-6277,-6226,-6175,-6124,-6074, + -6023,-5972,-5922,-5871,-5820,-5770,-5719,-5668, + -5618,-5567,-5517,-5466,-5415,-5365,-5314,-5264, + -5213,-5162,-5112,-5061,-5011,-4960,-4910,-4859, + -4808,-4758,-4707,-4657,-4606,-4556,-4505,-4455, + -4404,-4354,-4303,-4253,-4202,-4152,-4101,-4051, + -4001,-3950,-3900,-3849,-3799,-3748,-3698,-3648, + -3597,-3547,-3496,-3446,-3395,-3345,-3295,-3244, + -3194,-3144,-3093,-3043,-2992,-2942,-2892,-2841, + -2791,-2741,-2690,-2640,-2590,-2539,-2489,-2439, + -2388,-2338,-2288,-2237,-2187,-2137,-2086,-2036, + -1986,-1935,-1885,-1835,-1784,-1734,-1684,-1633, + -1583,-1533,-1483,-1432,-1382,-1332,-1281,-1231, + -1181,-1131,-1080,-1030,-980,-929,-879,-829, + -779,-728,-678,-628,-578,-527,-477,-427, + -376,-326,-276,-226,-175,-125,-75,-25, + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1131,1181, + 1231,1281,1332,1382,1432,1483,1533,1583, + 1633,1684,1734,1784,1835,1885,1935,1986, + 2036,2086,2137,2187,2237,2288,2338,2388, + 2439,2489,2539,2590,2640,2690,2741,2791, + 2841,2892,2942,2992,3043,3093,3144,3194, + 3244,3295,3345,3395,3446,3496,3547,3597, + 3648,3698,3748,3799,3849,3900,3950,4001, + 4051,4101,4152,4202,4253,4303,4354,4404, + 4455,4505,4556,4606,4657,4707,4758,4808, + 4859,4910,4960,5011,5061,5112,5162,5213, + 5264,5314,5365,5415,5466,5517,5567,5618, + 5668,5719,5770,5820,5871,5922,5972,6023, + 6074,6124,6175,6226,6277,6327,6378,6429, + 6480,6530,6581,6632,6683,6733,6784,6835, + 6886,6937,6988,7038,7089,7140,7191,7242, + 7293,7344,7395,7445,7496,7547,7598,7649, + 7700,7751,7802,7853,7904,7955,8006,8057, + 8108,8159,8210,8261,8312,8363,8414,8466, + 8517,8568,8619,8670,8721,8772,8824,8875, + 8926,8977,9028,9080,9131,9182,9233,9285, + 9336,9387,9438,9490,9541,9592,9644,9695, + 9747,9798,9849,9901,9952,10004,10055,10106, + 10158,10209,10261,10312,10364,10415,10467,10519, + 10570,10622,10673,10725,10777,10828,10880,10931, + 10983,11035,11086,11138,11190,11242,11293,11345, + 11397,11449,11501,11552,11604,11656,11708,11760, + 11812,11864,11916,11967,12019,12071,12123,12175, + 12227,12279,12331,12383,12436,12488,12540,12592, + 12644,12696,12748,12800,12853,12905,12957,13009, + 13062,13114,13166,13218,13271,13323,13375,13428, + 13480,13533,13585,13637,13690,13742,13795,13847, + 13900,13952,14005,14057,14110,14163,14215,14268, + 14321,14373,14426,14479,14531,14584,14637,14690, + 14743,14795,14848,14901,14954,15007,15060,15113, + 15166,15219,15272,15325,15378,15431,15484,15537, + 15590,15643,15696,15749,15802,15856,15909,15962, + 16015,16069,16122,16175,16229,16282,16335,16389, + 16442,16496,16549,16603,16656,16710,16763,16817, + 16870,16924,16977,17031,17085,17138,17192,17246, + 17300,17353,17407,17461,17515,17569,17623,17677, + 17731,17784,17838,17892,17946,18001,18055,18109, + 18163,18217,18271,18325,18380,18434,18488,18542, + 18597,18651,18705,18760,18814,18868,18923,18977, + 19032,19086,19141,19195,19250,19305,19359,19414, + 19469,19523,19578,19633,19688,19742,19797,19852, + 19907,19962,20017,20072,20127,20182,20237,20292, + 20347,20402,20457,20513,20568,20623,20678,20734, + 20789,20844,20900,20955,21010,21066,21121,21177, + 21232,21288,21343,21399,21455,21510,21566,21622, + 21678,21733,21789,21845,21901,21957,22013,22069, + 22125,22181,22237,22293,22349,22405,22461,22517, + 22573,22630,22686,22742,22799,22855,22911,22968, + 23024,23081,23137,23194,23250,23307,23364,23420, + 23477,23534,23591,23647,23704,23761,23818,23875, + 23932,23989,24046,24103,24160,24217,24274,24331, + 24389,24446,24503,24560,24618,24675,24732,24790, + 24847,24905,24962,25020,25078,25135,25193,25251, + 25308,25366,25424,25482,25540,25598,25656,25714, + 25772,25830,25888,25946,26004,26062,26120,26179, + 26237,26295,26354,26412,26471,26529,26588,26646, + 26705,26763,26822,26881,26940,26998,27057,27116, + 27175,27234,27293,27352,27411,27470,27529,27588, + 27647,27707,27766,27825,27884,27944,28003,28063, + 28122,28182,28241,28301,28361,28420,28480,28540, + 28600,28660,28719,28779,28839,28899,28959,29020, + 29080,29140,29200,29260,29321,29381,29441,29502, + 29562,29623,29683,29744,29805,29865,29926,29987, + 30048,30108,30169,30230,30291,30352,30413,30474, + 30536,30597,30658,30719,30781,30842,30904,30965, + 31026,31088,31150,31211,31273,31335,31396,31458, + 31520,31582,31644,31706,31768,31830,31892,31955, + 32017,32079,32141,32204,32266,32329,32391,32454, + 32516,32579,32642,32705,32767,32830,32893,32956, + 33019,33082,33145,33208,33272,33335,33398,33461, + 33525,33588,33652,33715,33779,33843,33906,33970, + 34034,34098,34162,34225,34289,34354,34418,34482, + 34546,34610,34675,34739,34803,34868,34932,34997, + 35062,35126,35191,35256,35321,35385,35450,35515, + 35580,35646,35711,35776,35841,35907,35972,36037, + 36103,36168,36234,36300,36365,36431,36497,36563, + 36629,36695,36761,36827,36893,36959,37026,37092, + 37158,37225,37291,37358,37425,37491,37558,37625, + 37692,37759,37826,37893,37960,38027,38094,38161, + 38229,38296,38364,38431,38499,38566,38634,38702, + 38770,38837,38905,38973,39042,39110,39178,39246, + 39314,39383,39451,39520,39588,39657,39726,39794, + 39863,39932,40001,40070,40139,40208,40278,40347, + 40416,40486,40555,40625,40694,40764,40834,40904, + 40973,41043,41113,41184,41254,41324,41394,41465, + 41535,41605,41676,41747,41817,41888,41959,42030, + 42101,42172,42243,42314,42385,42457,42528,42600, + 42671,42743,42814,42886,42958,43030,43102,43174, + 43246,43318,43390,43463,43535,43608,43680,43753, + 43826,43898,43971,44044,44117,44190,44263,44337, + 44410,44483,44557,44630,44704,44778,44851,44925, + 44999,45073,45147,45221,45296,45370,45444,45519, + 45593,45668,45743,45818,45892,45967,46042,46118, + 46193,46268,46343,46419,46494,46570,46646,46721, + 46797,46873,46949,47025,47102,47178,47254,47331, + 47407,47484,47560,47637,47714,47791,47868,47945, + 48022,48100,48177,48255,48332,48410,48488,48565, + 48643,48721,48799,48878,48956,49034,49113,49191, + 49270,49349,49427,49506,49585,49664,49744,49823, + 49902,49982,50061,50141,50221,50300,50380,50460, + 50540,50621,50701,50781,50862,50942,51023,51104, + 51185,51266,51347,51428,51509,51591,51672,51754, + 51835,51917,51999,52081,52163,52245,52327,52410, + 52492,52575,52657,52740,52823,52906,52989,53072, + 53156,53239,53322,53406,53490,53574,53657,53741, + 53826,53910,53994,54079,54163,54248,54333,54417, + 54502,54587,54673,54758,54843,54929,55015,55100, + 55186,55272,55358,55444,55531,55617,55704,55790, + 55877,55964,56051,56138,56225,56312,56400,56487, + 56575,56663,56751,56839,56927,57015,57104,57192, + 57281,57369,57458,57547,57636,57725,57815,57904, + 57994,58083,58173,58263,58353,58443,58534,58624, + 58715,58805,58896,58987,59078,59169,59261,59352, + 59444,59535,59627,59719,59811,59903,59996,60088, + 60181,60273,60366,60459,60552,60646,60739,60833, + 60926,61020,61114,61208,61302,61396,61491,61585, + 61680,61775,61870,61965,62060,62156,62251,62347, + 62443,62539,62635,62731,62828,62924,63021,63118, + 63215,63312,63409,63506,63604,63702,63799,63897, + 63996,64094,64192,64291,64389,64488,64587,64687, + 64786,64885,64985,65085,65185,65285,65385,65485, + 65586,65686,65787,65888,65989,66091,66192,66294, + 66396,66498,66600,66702,66804,66907,67010,67113, + 67216,67319,67422,67526,67629,67733,67837,67942, + 68046,68151,68255,68360,68465,68570,68676,68781, + 68887,68993,69099,69205,69312,69418,69525,69632, + 69739,69846,69954,70061,70169,70277,70385,70494, + 70602,70711,70820,70929,71038,71147,71257,71367, + 71477,71587,71697,71808,71918,72029,72140,72252, + 72363,72475,72587,72699,72811,72923,73036,73149, + 73262,73375,73488,73602,73715,73829,73944,74058, + 74172,74287,74402,74517,74633,74748,74864,74980, + 75096,75213,75329,75446,75563,75680,75797,75915, + 76033,76151,76269,76388,76506,76625,76744,76864, + 76983,77103,77223,77343,77463,77584,77705,77826, + 77947,78068,78190,78312,78434,78557,78679,78802, + 78925,79048,79172,79296,79420,79544,79668,79793, + 79918,80043,80168,80294,80420,80546,80672,80799, + 80925,81053,81180,81307,81435,81563,81691,81820, + 81949,82078,82207,82336,82466,82596,82726,82857, + 82987,83118,83250,83381,83513,83645,83777,83910, + 84043,84176,84309,84443,84576,84710,84845,84980, + 85114,85250,85385,85521,85657,85793,85930,86066, + 86204,86341,86479,86616,86755,86893,87032,87171, + 87310,87450,87590,87730,87871,88011,88152,88294, + 88435,88577,88720,88862,89005,89148,89292,89435, + 89579,89724,89868,90013,90158,90304,90450,90596, + 90742,90889,91036,91184,91332,91480,91628,91777, + 91926,92075,92225,92375,92525,92675,92826,92978, + 93129,93281,93434,93586,93739,93892,94046,94200, + 94354,94509,94664,94819,94975,95131,95287,95444, + 95601,95758,95916,96074,96233,96391,96551,96710, + 96870,97030,97191,97352,97513,97675,97837,98000, + 98163,98326,98489,98653,98818,98982,99148,99313, + 99479,99645,99812,99979,100146,100314,100482,100651, + 100820,100990,101159,101330,101500,101671,101843,102015, + 102187,102360,102533,102706,102880,103054,103229,103404, + 103580,103756,103933,104109,104287,104465,104643,104821, + 105000,105180,105360,105540,105721,105902,106084,106266, + 106449,106632,106816,107000,107184,107369,107555,107741, + 107927,108114,108301,108489,108677,108866,109055,109245, + 109435,109626,109817,110008,110200,110393,110586,110780, + 110974,111169,111364,111560,111756,111952,112150,112347, + 112546,112744,112944,113143,113344,113545,113746,113948, + 114151,114354,114557,114761,114966,115171,115377,115583, + 115790,115998,116206,116414,116623,116833,117044,117254, + 117466,117678,117891,118104,118318,118532,118747,118963, + 119179,119396,119613,119831,120050,120269,120489,120709, + 120930,121152,121374,121597,121821,122045,122270,122496, + 122722,122949,123176,123404,123633,123863,124093,124324, + 124555,124787,125020,125254,125488,125723,125959,126195, + 126432,126669,126908,127147,127387,127627,127869,128111, + 128353,128597,128841,129086,129332,129578,129825,130073, + 130322,130571,130821,131072,131324,131576,131830,132084, + 132339,132594,132851,133108,133366,133625,133884,134145, + 134406,134668,134931,135195,135459,135725,135991,136258, + 136526,136795,137065,137335,137607,137879,138152,138426, + 138701,138977,139254,139532,139810,140090,140370,140651, + 140934,141217,141501,141786,142072,142359,142647,142936, + 143226,143517,143808,144101,144395,144690,144986,145282, + 145580,145879,146179,146480,146782,147084,147388,147693, + 148000,148307,148615,148924,149235,149546,149859,150172, + 150487,150803,151120,151438,151757,152077,152399,152722, + 153045,153370,153697,154024,154352,154682,155013,155345, + 155678,156013,156349,156686,157024,157363,157704,158046, + 158389,158734,159079,159427,159775,160125,160476,160828, + 161182,161537,161893,162251,162610,162970,163332,163695, + 164060,164426,164793,165162,165532,165904,166277,166651, + 167027,167405,167784,168164,168546,168930,169315,169701, + 170089,170479,170870,171263,171657,172053,172451,172850, + 173251,173653,174057,174463,174870,175279,175690,176102, + 176516,176932,177349,177769,178190,178612,179037,179463, + 179891,180321,180753,181186,181622,182059,182498,182939, + 183382,183827,184274,184722,185173,185625,186080,186536, + 186995,187455,187918,188382,188849,189318,189789,190261, + 190736,191213,191693,192174,192658,193143,193631,194122, + 194614,195109,195606,196105,196606,197110,197616,198125, + 198636,199149,199664,200182,200703,201226,201751,202279, + 202809,203342,203878,204416,204956,205500,206045,206594, + 207145,207699,208255,208815,209376,209941,210509,211079, + 211652,212228,212807,213389,213973,214561,215151,215745, + 216341,216941,217544,218149,218758,219370,219985,220603, + 221225,221849,222477,223108,223743,224381,225022,225666, + 226314,226966,227621,228279,228941,229606,230275,230948, + 231624,232304,232988,233676,234367,235062,235761,236463, + 237170,237881,238595,239314,240036,240763,241493,242228, + 242967,243711,244458,245210,245966,246727,247492,248261, + 249035,249813,250596,251384,252176,252973,253774,254581, + 255392,256208,257029,257855,258686,259522,260363,261209, + 262060,262917,263779,264646,265519,266397,267280,268169, + 269064,269965,270871,271782,272700,273624,274553,275489, + 276430,277378,278332,279292,280258,281231,282210,283195, + 284188,285186,286192,287204,288223,289249,290282,291322, + 292369,293423,294485,295554,296630,297714,298805,299904, + 301011,302126,303248,304379,305517,306664,307819,308983, + 310154,311335,312524,313721,314928,316143,317368,318601, + 319844,321097,322358,323629,324910,326201,327502,328812, + 330133,331464,332805,334157,335519,336892,338276,339671, + 341078,342495,343924,345364,346816,348280,349756,351244, + 352744,354257,355783,357321,358872,360436,362013,363604, + 365208,366826,368459,370105,371765,373440,375130,376835, + 378555,380290,382040,383807,385589,387387,389202,391034, + 392882,394747,396630,398530,400448,402384,404338,406311, + 408303,410314,412344,414395,416465,418555,420666,422798, + 424951,427125,429321,431540,433781,436045,438332,440643, + 442978,445337,447720,450129,452564,455024,457511,460024, + 462565,465133,467730,470355,473009,475692,478406,481150, + 483925,486732,489571,492443,495348,498287,501261,504269, + 507313,510394,513512,516667,519861,523094,526366,529680, + 533034,536431,539870,543354,546881,550455,554074,557741, + 561456,565221,569035,572901,576818,580789,584815,588896, + 593033,597229,601483,605798,610174,614613,619117,623686, + 628323,633028,637803,642651,647572,652568,657640,662792, + 668024,673338,678737,684223,689797,695462,701219,707072, + 713023,719074,725227,731486,737853,744331,750922,757631, + 764460,771411,778490,785699,793041,800521,808143,815910, + 823827,831898,840127,848520,857081,865817,874730,883829, + 893117,902602,912289,922186,932298,942633,953199,964003, + 975054,986361,997931,1009774,1021901,1034322,1047046,1060087, + 1073455,1087164,1101225,1115654,1130465,1145673,1161294,1177345, + 1193846,1210813,1228269,1246234,1264730,1283783,1303416,1323658, + 1344537,1366084,1388330,1411312,1435065,1459630,1485049,1511367, + 1538632,1566898,1596220,1626658,1658278,1691149,1725348,1760956, + 1798063,1836758,1877161,1919378,1963536,2009771,2058233,2109087, + 2162516,2218719,2277919,2340362,2406322,2476104,2550052,2628549, + 2712030,2800983,2895966,2997613,3106651,3223918,3350381,3487165, + 3635590,3797206,3973855,4167737,4381502,4618375,4882318,5178251, + 5512368,5892567,6329090,6835455,7429880,8137527,8994149,10052327, + 11392683,13145455,15535599,18988036,24413316,34178904,56965752,170910304 +}; + + +const fixed_t finesine[10240] = +{ + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1130,1181, + 1231,1281,1331,1382,1432,1482,1532,1583, + 1633,1683,1733,1784,1834,1884,1934,1985, + 2035,2085,2135,2186,2236,2286,2336,2387, + 2437,2487,2537,2587,2638,2688,2738,2788, + 2839,2889,2939,2989,3039,3090,3140,3190, + 3240,3291,3341,3391,3441,3491,3541,3592, + 3642,3692,3742,3792,3843,3893,3943,3993, + 4043,4093,4144,4194,4244,4294,4344,4394, + 4445,4495,4545,4595,4645,4695,4745,4796, + 4846,4896,4946,4996,5046,5096,5146,5197, + 5247,5297,5347,5397,5447,5497,5547,5597, + 5647,5697,5748,5798,5848,5898,5948,5998, + 6048,6098,6148,6198,6248,6298,6348,6398, + 6448,6498,6548,6598,6648,6698,6748,6798, + 6848,6898,6948,6998,7048,7098,7148,7198, + 7248,7298,7348,7398,7448,7498,7548,7598, + 7648,7697,7747,7797,7847,7897,7947,7997, + 8047,8097,8147,8196,8246,8296,8346,8396, + 8446,8496,8545,8595,8645,8695,8745,8794, + 8844,8894,8944,8994,9043,9093,9143,9193, + 9243,9292,9342,9392,9442,9491,9541,9591, + 9640,9690,9740,9790,9839,9889,9939,9988, + 10038,10088,10137,10187,10237,10286,10336,10386, + 10435,10485,10534,10584,10634,10683,10733,10782, + 10832,10882,10931,10981,11030,11080,11129,11179, + 11228,11278,11327,11377,11426,11476,11525,11575, + 11624,11674,11723,11773,11822,11872,11921,11970, + 12020,12069,12119,12168,12218,12267,12316,12366, + 12415,12464,12514,12563,12612,12662,12711,12760, + 12810,12859,12908,12957,13007,13056,13105,13154, + 13204,13253,13302,13351,13401,13450,13499,13548, + 13597,13647,13696,13745,13794,13843,13892,13941, + 13990,14040,14089,14138,14187,14236,14285,14334, + 14383,14432,14481,14530,14579,14628,14677,14726, + 14775,14824,14873,14922,14971,15020,15069,15118, + 15167,15215,15264,15313,15362,15411,15460,15509, + 15557,15606,15655,15704,15753,15802,15850,15899, + 15948,15997,16045,16094,16143,16191,16240,16289, + 16338,16386,16435,16484,16532,16581,16629,16678, + 16727,16775,16824,16872,16921,16970,17018,17067, + 17115,17164,17212,17261,17309,17358,17406,17455, + 17503,17551,17600,17648,17697,17745,17793,17842, + 17890,17939,17987,18035,18084,18132,18180,18228, + 18277,18325,18373,18421,18470,18518,18566,18614, + 18663,18711,18759,18807,18855,18903,18951,19000, + 19048,19096,19144,19192,19240,19288,19336,19384, + 19432,19480,19528,19576,19624,19672,19720,19768, + 19816,19864,19912,19959,20007,20055,20103,20151, + 20199,20246,20294,20342,20390,20438,20485,20533, + 20581,20629,20676,20724,20772,20819,20867,20915, + 20962,21010,21057,21105,21153,21200,21248,21295, + 21343,21390,21438,21485,21533,21580,21628,21675, + 21723,21770,21817,21865,21912,21960,22007,22054, + 22102,22149,22196,22243,22291,22338,22385,22433, + 22480,22527,22574,22621,22668,22716,22763,22810, + 22857,22904,22951,22998,23045,23092,23139,23186, + 23233,23280,23327,23374,23421,23468,23515,23562, + 23609,23656,23703,23750,23796,23843,23890,23937, + 23984,24030,24077,24124,24171,24217,24264,24311, + 24357,24404,24451,24497,24544,24591,24637,24684, + 24730,24777,24823,24870,24916,24963,25009,25056, + 25102,25149,25195,25241,25288,25334,25381,25427, + 25473,25520,25566,25612,25658,25705,25751,25797, + 25843,25889,25936,25982,26028,26074,26120,26166, + 26212,26258,26304,26350,26396,26442,26488,26534, + 26580,26626,26672,26718,26764,26810,26856,26902, + 26947,26993,27039,27085,27131,27176,27222,27268, + 27313,27359,27405,27450,27496,27542,27587,27633, + 27678,27724,27770,27815,27861,27906,27952,27997, + 28042,28088,28133,28179,28224,28269,28315,28360, + 28405,28451,28496,28541,28586,28632,28677,28722, + 28767,28812,28858,28903,28948,28993,29038,29083, + 29128,29173,29218,29263,29308,29353,29398,29443, + 29488,29533,29577,29622,29667,29712,29757,29801, + 29846,29891,29936,29980,30025,30070,30114,30159, + 30204,30248,30293,30337,30382,30426,30471,30515, + 30560,30604,30649,30693,30738,30782,30826,30871, + 30915,30959,31004,31048,31092,31136,31181,31225, + 31269,31313,31357,31402,31446,31490,31534,31578, + 31622,31666,31710,31754,31798,31842,31886,31930, + 31974,32017,32061,32105,32149,32193,32236,32280, + 32324,32368,32411,32455,32499,32542,32586,32630, + 32673,32717,32760,32804,32847,32891,32934,32978, + 33021,33065,33108,33151,33195,33238,33281,33325, + 33368,33411,33454,33498,33541,33584,33627,33670, + 33713,33756,33799,33843,33886,33929,33972,34015, + 34057,34100,34143,34186,34229,34272,34315,34358, + 34400,34443,34486,34529,34571,34614,34657,34699, + 34742,34785,34827,34870,34912,34955,34997,35040, + 35082,35125,35167,35210,35252,35294,35337,35379, + 35421,35464,35506,35548,35590,35633,35675,35717, + 35759,35801,35843,35885,35927,35969,36011,36053, + 36095,36137,36179,36221,36263,36305,36347,36388, + 36430,36472,36514,36555,36597,36639,36681,36722, + 36764,36805,36847,36889,36930,36972,37013,37055, + 37096,37137,37179,37220,37262,37303,37344,37386, + 37427,37468,37509,37551,37592,37633,37674,37715, + 37756,37797,37838,37879,37920,37961,38002,38043, + 38084,38125,38166,38207,38248,38288,38329,38370, + 38411,38451,38492,38533,38573,38614,38655,38695, + 38736,38776,38817,38857,38898,38938,38979,39019, + 39059,39100,39140,39180,39221,39261,39301,39341, + 39382,39422,39462,39502,39542,39582,39622,39662, + 39702,39742,39782,39822,39862,39902,39942,39982, + 40021,40061,40101,40141,40180,40220,40260,40300, + 40339,40379,40418,40458,40497,40537,40576,40616, + 40655,40695,40734,40773,40813,40852,40891,40931, + 40970,41009,41048,41087,41127,41166,41205,41244, + 41283,41322,41361,41400,41439,41478,41517,41556, + 41595,41633,41672,41711,41750,41788,41827,41866, + 41904,41943,41982,42020,42059,42097,42136,42174, + 42213,42251,42290,42328,42366,42405,42443,42481, + 42520,42558,42596,42634,42672,42711,42749,42787, + 42825,42863,42901,42939,42977,43015,43053,43091, + 43128,43166,43204,43242,43280,43317,43355,43393, + 43430,43468,43506,43543,43581,43618,43656,43693, + 43731,43768,43806,43843,43880,43918,43955,43992, + 44029,44067,44104,44141,44178,44215,44252,44289, + 44326,44363,44400,44437,44474,44511,44548,44585, + 44622,44659,44695,44732,44769,44806,44842,44879, + 44915,44952,44989,45025,45062,45098,45135,45171, + 45207,45244,45280,45316,45353,45389,45425,45462, + 45498,45534,45570,45606,45642,45678,45714,45750, + 45786,45822,45858,45894,45930,45966,46002,46037, + 46073,46109,46145,46180,46216,46252,46287,46323, + 46358,46394,46429,46465,46500,46536,46571,46606, + 46642,46677,46712,46747,46783,46818,46853,46888, + 46923,46958,46993,47028,47063,47098,47133,47168, + 47203,47238,47273,47308,47342,47377,47412,47446, + 47481,47516,47550,47585,47619,47654,47688,47723, + 47757,47792,47826,47860,47895,47929,47963,47998, + 48032,48066,48100,48134,48168,48202,48237,48271, + 48305,48338,48372,48406,48440,48474,48508,48542, + 48575,48609,48643,48676,48710,48744,48777,48811, + 48844,48878,48911,48945,48978,49012,49045,49078, + 49112,49145,49178,49211,49244,49278,49311,49344, + 49377,49410,49443,49476,49509,49542,49575,49608, + 49640,49673,49706,49739,49771,49804,49837,49869, + 49902,49935,49967,50000,50032,50065,50097,50129, + 50162,50194,50226,50259,50291,50323,50355,50387, + 50420,50452,50484,50516,50548,50580,50612,50644, + 50675,50707,50739,50771,50803,50834,50866,50898, + 50929,50961,50993,51024,51056,51087,51119,51150, + 51182,51213,51244,51276,51307,51338,51369,51401, + 51432,51463,51494,51525,51556,51587,51618,51649, + 51680,51711,51742,51773,51803,51834,51865,51896, + 51926,51957,51988,52018,52049,52079,52110,52140, + 52171,52201,52231,52262,52292,52322,52353,52383, + 52413,52443,52473,52503,52534,52564,52594,52624, + 52653,52683,52713,52743,52773,52803,52832,52862, + 52892,52922,52951,52981,53010,53040,53069,53099, + 53128,53158,53187,53216,53246,53275,53304,53334, + 53363,53392,53421,53450,53479,53508,53537,53566, + 53595,53624,53653,53682,53711,53739,53768,53797, + 53826,53854,53883,53911,53940,53969,53997,54026, + 54054,54082,54111,54139,54167,54196,54224,54252, + 54280,54308,54337,54365,54393,54421,54449,54477, + 54505,54533,54560,54588,54616,54644,54672,54699, + 54727,54755,54782,54810,54837,54865,54892,54920, + 54947,54974,55002,55029,55056,55084,55111,55138, + 55165,55192,55219,55246,55274,55300,55327,55354, + 55381,55408,55435,55462,55489,55515,55542,55569, + 55595,55622,55648,55675,55701,55728,55754,55781, + 55807,55833,55860,55886,55912,55938,55965,55991, + 56017,56043,56069,56095,56121,56147,56173,56199, + 56225,56250,56276,56302,56328,56353,56379,56404, + 56430,56456,56481,56507,56532,56557,56583,56608, + 56633,56659,56684,56709,56734,56760,56785,56810, + 56835,56860,56885,56910,56935,56959,56984,57009, + 57034,57059,57083,57108,57133,57157,57182,57206, + 57231,57255,57280,57304,57329,57353,57377,57402, + 57426,57450,57474,57498,57522,57546,57570,57594, + 57618,57642,57666,57690,57714,57738,57762,57785, + 57809,57833,57856,57880,57903,57927,57950,57974, + 57997,58021,58044,58067,58091,58114,58137,58160, + 58183,58207,58230,58253,58276,58299,58322,58345, + 58367,58390,58413,58436,58459,58481,58504,58527, + 58549,58572,58594,58617,58639,58662,58684,58706, + 58729,58751,58773,58795,58818,58840,58862,58884, + 58906,58928,58950,58972,58994,59016,59038,59059, + 59081,59103,59125,59146,59168,59190,59211,59233, + 59254,59276,59297,59318,59340,59361,59382,59404, + 59425,59446,59467,59488,59509,59530,59551,59572, + 59593,59614,59635,59656,59677,59697,59718,59739, + 59759,59780,59801,59821,59842,59862,59883,59903, + 59923,59944,59964,59984,60004,60025,60045,60065, + 60085,60105,60125,60145,60165,60185,60205,60225, + 60244,60264,60284,60304,60323,60343,60363,60382, + 60402,60421,60441,60460,60479,60499,60518,60537, + 60556,60576,60595,60614,60633,60652,60671,60690, + 60709,60728,60747,60766,60785,60803,60822,60841, + 60859,60878,60897,60915,60934,60952,60971,60989, + 61007,61026,61044,61062,61081,61099,61117,61135, + 61153,61171,61189,61207,61225,61243,61261,61279, + 61297,61314,61332,61350,61367,61385,61403,61420, + 61438,61455,61473,61490,61507,61525,61542,61559, + 61577,61594,61611,61628,61645,61662,61679,61696, + 61713,61730,61747,61764,61780,61797,61814,61831, + 61847,61864,61880,61897,61913,61930,61946,61963, + 61979,61995,62012,62028,62044,62060,62076,62092, + 62108,62125,62141,62156,62172,62188,62204,62220, + 62236,62251,62267,62283,62298,62314,62329,62345, + 62360,62376,62391,62407,62422,62437,62453,62468, + 62483,62498,62513,62528,62543,62558,62573,62588, + 62603,62618,62633,62648,62662,62677,62692,62706, + 62721,62735,62750,62764,62779,62793,62808,62822, + 62836,62850,62865,62879,62893,62907,62921,62935, + 62949,62963,62977,62991,63005,63019,63032,63046, + 63060,63074,63087,63101,63114,63128,63141,63155, + 63168,63182,63195,63208,63221,63235,63248,63261, + 63274,63287,63300,63313,63326,63339,63352,63365, + 63378,63390,63403,63416,63429,63441,63454,63466, + 63479,63491,63504,63516,63528,63541,63553,63565, + 63578,63590,63602,63614,63626,63638,63650,63662, + 63674,63686,63698,63709,63721,63733,63745,63756, + 63768,63779,63791,63803,63814,63825,63837,63848, + 63859,63871,63882,63893,63904,63915,63927,63938, + 63949,63960,63971,63981,63992,64003,64014,64025, + 64035,64046,64057,64067,64078,64088,64099,64109, + 64120,64130,64140,64151,64161,64171,64181,64192, + 64202,64212,64222,64232,64242,64252,64261,64271, + 64281,64291,64301,64310,64320,64330,64339,64349, + 64358,64368,64377,64387,64396,64405,64414,64424, + 64433,64442,64451,64460,64469,64478,64487,64496, + 64505,64514,64523,64532,64540,64549,64558,64566, + 64575,64584,64592,64601,64609,64617,64626,64634, + 64642,64651,64659,64667,64675,64683,64691,64699, + 64707,64715,64723,64731,64739,64747,64754,64762, + 64770,64777,64785,64793,64800,64808,64815,64822, + 64830,64837,64844,64852,64859,64866,64873,64880, + 64887,64895,64902,64908,64915,64922,64929,64936, + 64943,64949,64956,64963,64969,64976,64982,64989, + 64995,65002,65008,65015,65021,65027,65033,65040, + 65046,65052,65058,65064,65070,65076,65082,65088, + 65094,65099,65105,65111,65117,65122,65128,65133, + 65139,65144,65150,65155,65161,65166,65171,65177, + 65182,65187,65192,65197,65202,65207,65212,65217, + 65222,65227,65232,65237,65242,65246,65251,65256, + 65260,65265,65270,65274,65279,65283,65287,65292, + 65296,65300,65305,65309,65313,65317,65321,65325, + 65329,65333,65337,65341,65345,65349,65352,65356, + 65360,65363,65367,65371,65374,65378,65381,65385, + 65388,65391,65395,65398,65401,65404,65408,65411, + 65414,65417,65420,65423,65426,65429,65431,65434, + 65437,65440,65442,65445,65448,65450,65453,65455, + 65458,65460,65463,65465,65467,65470,65472,65474, + 65476,65478,65480,65482,65484,65486,65488,65490, + 65492,65494,65496,65497,65499,65501,65502,65504, + 65505,65507,65508,65510,65511,65513,65514,65515, + 65516,65518,65519,65520,65521,65522,65523,65524, + 65525,65526,65527,65527,65528,65529,65530,65530, + 65531,65531,65532,65532,65533,65533,65534,65534, + 65534,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65534, + 65534,65534,65533,65533,65532,65532,65531,65531, + 65530,65530,65529,65528,65527,65527,65526,65525, + 65524,65523,65522,65521,65520,65519,65518,65516, + 65515,65514,65513,65511,65510,65508,65507,65505, + 65504,65502,65501,65499,65497,65496,65494,65492, + 65490,65488,65486,65484,65482,65480,65478,65476, + 65474,65472,65470,65467,65465,65463,65460,65458, + 65455,65453,65450,65448,65445,65442,65440,65437, + 65434,65431,65429,65426,65423,65420,65417,65414, + 65411,65408,65404,65401,65398,65395,65391,65388, + 65385,65381,65378,65374,65371,65367,65363,65360, + 65356,65352,65349,65345,65341,65337,65333,65329, + 65325,65321,65317,65313,65309,65305,65300,65296, + 65292,65287,65283,65279,65274,65270,65265,65260, + 65256,65251,65246,65242,65237,65232,65227,65222, + 65217,65212,65207,65202,65197,65192,65187,65182, + 65177,65171,65166,65161,65155,65150,65144,65139, + 65133,65128,65122,65117,65111,65105,65099,65094, + 65088,65082,65076,65070,65064,65058,65052,65046, + 65040,65033,65027,65021,65015,65008,65002,64995, + 64989,64982,64976,64969,64963,64956,64949,64943, + 64936,64929,64922,64915,64908,64902,64895,64887, + 64880,64873,64866,64859,64852,64844,64837,64830, + 64822,64815,64808,64800,64793,64785,64777,64770, + 64762,64754,64747,64739,64731,64723,64715,64707, + 64699,64691,64683,64675,64667,64659,64651,64642, + 64634,64626,64617,64609,64600,64592,64584,64575, + 64566,64558,64549,64540,64532,64523,64514,64505, + 64496,64487,64478,64469,64460,64451,64442,64433, + 64424,64414,64405,64396,64387,64377,64368,64358, + 64349,64339,64330,64320,64310,64301,64291,64281, + 64271,64261,64252,64242,64232,64222,64212,64202, + 64192,64181,64171,64161,64151,64140,64130,64120, + 64109,64099,64088,64078,64067,64057,64046,64035, + 64025,64014,64003,63992,63981,63971,63960,63949, + 63938,63927,63915,63904,63893,63882,63871,63859, + 63848,63837,63825,63814,63803,63791,63779,63768, + 63756,63745,63733,63721,63709,63698,63686,63674, + 63662,63650,63638,63626,63614,63602,63590,63578, + 63565,63553,63541,63528,63516,63504,63491,63479, + 63466,63454,63441,63429,63416,63403,63390,63378, + 63365,63352,63339,63326,63313,63300,63287,63274, + 63261,63248,63235,63221,63208,63195,63182,63168, + 63155,63141,63128,63114,63101,63087,63074,63060, + 63046,63032,63019,63005,62991,62977,62963,62949, + 62935,62921,62907,62893,62879,62865,62850,62836, + 62822,62808,62793,62779,62764,62750,62735,62721, + 62706,62692,62677,62662,62648,62633,62618,62603, + 62588,62573,62558,62543,62528,62513,62498,62483, + 62468,62453,62437,62422,62407,62391,62376,62360, + 62345,62329,62314,62298,62283,62267,62251,62236, + 62220,62204,62188,62172,62156,62141,62125,62108, + 62092,62076,62060,62044,62028,62012,61995,61979, + 61963,61946,61930,61913,61897,61880,61864,61847, + 61831,61814,61797,61780,61764,61747,61730,61713, + 61696,61679,61662,61645,61628,61611,61594,61577, + 61559,61542,61525,61507,61490,61473,61455,61438, + 61420,61403,61385,61367,61350,61332,61314,61297, + 61279,61261,61243,61225,61207,61189,61171,61153, + 61135,61117,61099,61081,61062,61044,61026,61007, + 60989,60971,60952,60934,60915,60897,60878,60859, + 60841,60822,60803,60785,60766,60747,60728,60709, + 60690,60671,60652,60633,60614,60595,60576,60556, + 60537,60518,60499,60479,60460,60441,60421,60402, + 60382,60363,60343,60323,60304,60284,60264,60244, + 60225,60205,60185,60165,60145,60125,60105,60085, + 60065,60045,60025,60004,59984,59964,59944,59923, + 59903,59883,59862,59842,59821,59801,59780,59759, + 59739,59718,59697,59677,59656,59635,59614,59593, + 59572,59551,59530,59509,59488,59467,59446,59425, + 59404,59382,59361,59340,59318,59297,59276,59254, + 59233,59211,59190,59168,59146,59125,59103,59081, + 59059,59038,59016,58994,58972,58950,58928,58906, + 58884,58862,58840,58818,58795,58773,58751,58729, + 58706,58684,58662,58639,58617,58594,58572,58549, + 58527,58504,58481,58459,58436,58413,58390,58367, + 58345,58322,58299,58276,58253,58230,58207,58183, + 58160,58137,58114,58091,58067,58044,58021,57997, + 57974,57950,57927,57903,57880,57856,57833,57809, + 57785,57762,57738,57714,57690,57666,57642,57618, + 57594,57570,57546,57522,57498,57474,57450,57426, + 57402,57377,57353,57329,57304,57280,57255,57231, + 57206,57182,57157,57133,57108,57083,57059,57034, + 57009,56984,56959,56935,56910,56885,56860,56835, + 56810,56785,56760,56734,56709,56684,56659,56633, + 56608,56583,56557,56532,56507,56481,56456,56430, + 56404,56379,56353,56328,56302,56276,56250,56225, + 56199,56173,56147,56121,56095,56069,56043,56017, + 55991,55965,55938,55912,55886,55860,55833,55807, + 55781,55754,55728,55701,55675,55648,55622,55595, + 55569,55542,55515,55489,55462,55435,55408,55381, + 55354,55327,55300,55274,55246,55219,55192,55165, + 55138,55111,55084,55056,55029,55002,54974,54947, + 54920,54892,54865,54837,54810,54782,54755,54727, + 54699,54672,54644,54616,54588,54560,54533,54505, + 54477,54449,54421,54393,54365,54337,54308,54280, + 54252,54224,54196,54167,54139,54111,54082,54054, + 54026,53997,53969,53940,53911,53883,53854,53826, + 53797,53768,53739,53711,53682,53653,53624,53595, + 53566,53537,53508,53479,53450,53421,53392,53363, + 53334,53304,53275,53246,53216,53187,53158,53128, + 53099,53069,53040,53010,52981,52951,52922,52892, + 52862,52832,52803,52773,52743,52713,52683,52653, + 52624,52594,52564,52534,52503,52473,52443,52413, + 52383,52353,52322,52292,52262,52231,52201,52171, + 52140,52110,52079,52049,52018,51988,51957,51926, + 51896,51865,51834,51803,51773,51742,51711,51680, + 51649,51618,51587,51556,51525,51494,51463,51432, + 51401,51369,51338,51307,51276,51244,51213,51182, + 51150,51119,51087,51056,51024,50993,50961,50929, + 50898,50866,50834,50803,50771,50739,50707,50675, + 50644,50612,50580,50548,50516,50484,50452,50420, + 50387,50355,50323,50291,50259,50226,50194,50162, + 50129,50097,50065,50032,50000,49967,49935,49902, + 49869,49837,49804,49771,49739,49706,49673,49640, + 49608,49575,49542,49509,49476,49443,49410,49377, + 49344,49311,49278,49244,49211,49178,49145,49112, + 49078,49045,49012,48978,48945,48911,48878,48844, + 48811,48777,48744,48710,48676,48643,48609,48575, + 48542,48508,48474,48440,48406,48372,48338,48304, + 48271,48237,48202,48168,48134,48100,48066,48032, + 47998,47963,47929,47895,47860,47826,47792,47757, + 47723,47688,47654,47619,47585,47550,47516,47481, + 47446,47412,47377,47342,47308,47273,47238,47203, + 47168,47133,47098,47063,47028,46993,46958,46923, + 46888,46853,46818,46783,46747,46712,46677,46642, + 46606,46571,46536,46500,46465,46429,46394,46358, + 46323,46287,46252,46216,46180,46145,46109,46073, + 46037,46002,45966,45930,45894,45858,45822,45786, + 45750,45714,45678,45642,45606,45570,45534,45498, + 45462,45425,45389,45353,45316,45280,45244,45207, + 45171,45135,45098,45062,45025,44989,44952,44915, + 44879,44842,44806,44769,44732,44695,44659,44622, + 44585,44548,44511,44474,44437,44400,44363,44326, + 44289,44252,44215,44178,44141,44104,44067,44029, + 43992,43955,43918,43880,43843,43806,43768,43731, + 43693,43656,43618,43581,43543,43506,43468,43430, + 43393,43355,43317,43280,43242,43204,43166,43128, + 43091,43053,43015,42977,42939,42901,42863,42825, + 42787,42749,42711,42672,42634,42596,42558,42520, + 42481,42443,42405,42366,42328,42290,42251,42213, + 42174,42136,42097,42059,42020,41982,41943,41904, + 41866,41827,41788,41750,41711,41672,41633,41595, + 41556,41517,41478,41439,41400,41361,41322,41283, + 41244,41205,41166,41127,41088,41048,41009,40970, + 40931,40891,40852,40813,40773,40734,40695,40655, + 40616,40576,40537,40497,40458,40418,40379,40339, + 40300,40260,40220,40180,40141,40101,40061,40021, + 39982,39942,39902,39862,39822,39782,39742,39702, + 39662,39622,39582,39542,39502,39462,39422,39382, + 39341,39301,39261,39221,39180,39140,39100,39059, + 39019,38979,38938,38898,38857,38817,38776,38736, + 38695,38655,38614,38573,38533,38492,38451,38411, + 38370,38329,38288,38248,38207,38166,38125,38084, + 38043,38002,37961,37920,37879,37838,37797,37756, + 37715,37674,37633,37592,37551,37509,37468,37427, + 37386,37344,37303,37262,37220,37179,37137,37096, + 37055,37013,36972,36930,36889,36847,36805,36764, + 36722,36681,36639,36597,36556,36514,36472,36430, + 36388,36347,36305,36263,36221,36179,36137,36095, + 36053,36011,35969,35927,35885,35843,35801,35759, + 35717,35675,35633,35590,35548,35506,35464,35421, + 35379,35337,35294,35252,35210,35167,35125,35082, + 35040,34997,34955,34912,34870,34827,34785,34742, + 34699,34657,34614,34571,34529,34486,34443,34400, + 34358,34315,34272,34229,34186,34143,34100,34057, + 34015,33972,33929,33886,33843,33799,33756,33713, + 33670,33627,33584,33541,33498,33454,33411,33368, + 33325,33281,33238,33195,33151,33108,33065,33021, + 32978,32934,32891,32847,32804,32760,32717,32673, + 32630,32586,32542,32499,32455,32411,32368,32324, + 32280,32236,32193,32149,32105,32061,32017,31974, + 31930,31886,31842,31798,31754,31710,31666,31622, + 31578,31534,31490,31446,31402,31357,31313,31269, + 31225,31181,31136,31092,31048,31004,30959,30915, + 30871,30826,30782,30738,30693,30649,30604,30560, + 30515,30471,30426,30382,30337,30293,30248,30204, + 30159,30114,30070,30025,29980,29936,29891,29846, + 29801,29757,29712,29667,29622,29577,29533,29488, + 29443,29398,29353,29308,29263,29218,29173,29128, + 29083,29038,28993,28948,28903,28858,28812,28767, + 28722,28677,28632,28586,28541,28496,28451,28405, + 28360,28315,28269,28224,28179,28133,28088,28042, + 27997,27952,27906,27861,27815,27770,27724,27678, + 27633,27587,27542,27496,27450,27405,27359,27313, + 27268,27222,27176,27131,27085,27039,26993,26947, + 26902,26856,26810,26764,26718,26672,26626,26580, + 26534,26488,26442,26396,26350,26304,26258,26212, + 26166,26120,26074,26028,25982,25936,25889,25843, + 25797,25751,25705,25658,25612,25566,25520,25473, + 25427,25381,25334,25288,25241,25195,25149,25102, + 25056,25009,24963,24916,24870,24823,24777,24730, + 24684,24637,24591,24544,24497,24451,24404,24357, + 24311,24264,24217,24171,24124,24077,24030,23984, + 23937,23890,23843,23796,23750,23703,23656,23609, + 23562,23515,23468,23421,23374,23327,23280,23233, + 23186,23139,23092,23045,22998,22951,22904,22857, + 22810,22763,22716,22668,22621,22574,22527,22480, + 22433,22385,22338,22291,22243,22196,22149,22102, + 22054,22007,21960,21912,21865,21817,21770,21723, + 21675,21628,21580,21533,21485,21438,21390,21343, + 21295,21248,21200,21153,21105,21057,21010,20962, + 20915,20867,20819,20772,20724,20676,20629,20581, + 20533,20485,20438,20390,20342,20294,20246,20199, + 20151,20103,20055,20007,19959,19912,19864,19816, + 19768,19720,19672,19624,19576,19528,19480,19432, + 19384,19336,19288,19240,19192,19144,19096,19048, + 19000,18951,18903,18855,18807,18759,18711,18663, + 18614,18566,18518,18470,18421,18373,18325,18277, + 18228,18180,18132,18084,18035,17987,17939,17890, + 17842,17793,17745,17697,17648,17600,17551,17503, + 17455,17406,17358,17309,17261,17212,17164,17115, + 17067,17018,16970,16921,16872,16824,16775,16727, + 16678,16629,16581,16532,16484,16435,16386,16338, + 16289,16240,16191,16143,16094,16045,15997,15948, + 15899,15850,15802,15753,15704,15655,15606,15557, + 15509,15460,15411,15362,15313,15264,15215,15167, + 15118,15069,15020,14971,14922,14873,14824,14775, + 14726,14677,14628,14579,14530,14481,14432,14383, + 14334,14285,14236,14187,14138,14089,14040,13990, + 13941,13892,13843,13794,13745,13696,13646,13597, + 13548,13499,13450,13401,13351,13302,13253,13204, + 13154,13105,13056,13007,12957,12908,12859,12810, + 12760,12711,12662,12612,12563,12514,12464,12415, + 12366,12316,12267,12218,12168,12119,12069,12020, + 11970,11921,11872,11822,11773,11723,11674,11624, + 11575,11525,11476,11426,11377,11327,11278,11228, + 11179,11129,11080,11030,10981,10931,10882,10832, + 10782,10733,10683,10634,10584,10534,10485,10435, + 10386,10336,10286,10237,10187,10137,10088,10038, + 9988,9939,9889,9839,9790,9740,9690,9640, + 9591,9541,9491,9442,9392,9342,9292,9243, + 9193,9143,9093,9043,8994,8944,8894,8844, + 8794,8745,8695,8645,8595,8545,8496,8446, + 8396,8346,8296,8246,8196,8147,8097,8047, + 7997,7947,7897,7847,7797,7747,7697,7648, + 7598,7548,7498,7448,7398,7348,7298,7248, + 7198,7148,7098,7048,6998,6948,6898,6848, + 6798,6748,6698,6648,6598,6548,6498,6448, + 6398,6348,6298,6248,6198,6148,6098,6048, + 5998,5948,5898,5848,5798,5748,5697,5647, + 5597,5547,5497,5447,5397,5347,5297,5247, + 5197,5146,5096,5046,4996,4946,4896,4846, + 4796,4745,4695,4645,4595,4545,4495,4445, + 4394,4344,4294,4244,4194,4144,4093,4043, + 3993,3943,3893,3843,3792,3742,3692,3642, + 3592,3541,3491,3441,3391,3341,3291,3240, + 3190,3140,3090,3039,2989,2939,2889,2839, + 2788,2738,2688,2638,2587,2537,2487,2437, + 2387,2336,2286,2236,2186,2135,2085,2035, + 1985,1934,1884,1834,1784,1733,1683,1633, + 1583,1532,1482,1432,1382,1331,1281,1231, + 1181,1130,1080,1030,980,929,879,829, + 779,728,678,628,578,527,477,427, + 376,326,276,226,175,125,75,25, + -25,-75,-125,-175,-226,-276,-326,-376, + -427,-477,-527,-578,-628,-678,-728,-779, + -829,-879,-929,-980,-1030,-1080,-1130,-1181, + -1231,-1281,-1331,-1382,-1432,-1482,-1532,-1583, + -1633,-1683,-1733,-1784,-1834,-1884,-1934,-1985, + -2035,-2085,-2135,-2186,-2236,-2286,-2336,-2387, + -2437,-2487,-2537,-2588,-2638,-2688,-2738,-2788, + -2839,-2889,-2939,-2989,-3039,-3090,-3140,-3190, + -3240,-3291,-3341,-3391,-3441,-3491,-3541,-3592, + -3642,-3692,-3742,-3792,-3843,-3893,-3943,-3993, + -4043,-4093,-4144,-4194,-4244,-4294,-4344,-4394, + -4445,-4495,-4545,-4595,-4645,-4695,-4745,-4796, + -4846,-4896,-4946,-4996,-5046,-5096,-5146,-5197, + -5247,-5297,-5347,-5397,-5447,-5497,-5547,-5597, + -5647,-5697,-5748,-5798,-5848,-5898,-5948,-5998, + -6048,-6098,-6148,-6198,-6248,-6298,-6348,-6398, + -6448,-6498,-6548,-6598,-6648,-6698,-6748,-6798, + -6848,-6898,-6948,-6998,-7048,-7098,-7148,-7198, + -7248,-7298,-7348,-7398,-7448,-7498,-7548,-7598, + -7648,-7697,-7747,-7797,-7847,-7897,-7947,-7997, + -8047,-8097,-8147,-8196,-8246,-8296,-8346,-8396, + -8446,-8496,-8545,-8595,-8645,-8695,-8745,-8794, + -8844,-8894,-8944,-8994,-9043,-9093,-9143,-9193, + -9243,-9292,-9342,-9392,-9442,-9491,-9541,-9591, + -9640,-9690,-9740,-9790,-9839,-9889,-9939,-9988, + -10038,-10088,-10137,-10187,-10237,-10286,-10336,-10386, + -10435,-10485,-10534,-10584,-10634,-10683,-10733,-10782, + -10832,-10882,-10931,-10981,-11030,-11080,-11129,-11179, + -11228,-11278,-11327,-11377,-11426,-11476,-11525,-11575, + -11624,-11674,-11723,-11773,-11822,-11872,-11921,-11970, + -12020,-12069,-12119,-12168,-12218,-12267,-12316,-12366, + -12415,-12464,-12514,-12563,-12612,-12662,-12711,-12760, + -12810,-12859,-12908,-12957,-13007,-13056,-13105,-13154, + -13204,-13253,-13302,-13351,-13401,-13450,-13499,-13548, + -13597,-13647,-13696,-13745,-13794,-13843,-13892,-13941, + -13990,-14040,-14089,-14138,-14187,-14236,-14285,-14334, + -14383,-14432,-14481,-14530,-14579,-14628,-14677,-14726, + -14775,-14824,-14873,-14922,-14971,-15020,-15069,-15118, + -15167,-15215,-15264,-15313,-15362,-15411,-15460,-15509, + -15557,-15606,-15655,-15704,-15753,-15802,-15850,-15899, + -15948,-15997,-16045,-16094,-16143,-16191,-16240,-16289, + -16338,-16386,-16435,-16484,-16532,-16581,-16629,-16678, + -16727,-16775,-16824,-16872,-16921,-16970,-17018,-17067, + -17115,-17164,-17212,-17261,-17309,-17358,-17406,-17455, + -17503,-17551,-17600,-17648,-17697,-17745,-17793,-17842, + -17890,-17939,-17987,-18035,-18084,-18132,-18180,-18228, + -18277,-18325,-18373,-18421,-18470,-18518,-18566,-18614, + -18663,-18711,-18759,-18807,-18855,-18903,-18951,-19000, + -19048,-19096,-19144,-19192,-19240,-19288,-19336,-19384, + -19432,-19480,-19528,-19576,-19624,-19672,-19720,-19768, + -19816,-19864,-19912,-19959,-20007,-20055,-20103,-20151, + -20199,-20246,-20294,-20342,-20390,-20438,-20485,-20533, + -20581,-20629,-20676,-20724,-20772,-20819,-20867,-20915, + -20962,-21010,-21057,-21105,-21153,-21200,-21248,-21295, + -21343,-21390,-21438,-21485,-21533,-21580,-21628,-21675, + -21723,-21770,-21817,-21865,-21912,-21960,-22007,-22054, + -22102,-22149,-22196,-22243,-22291,-22338,-22385,-22433, + -22480,-22527,-22574,-22621,-22668,-22716,-22763,-22810, + -22857,-22904,-22951,-22998,-23045,-23092,-23139,-23186, + -23233,-23280,-23327,-23374,-23421,-23468,-23515,-23562, + -23609,-23656,-23703,-23750,-23796,-23843,-23890,-23937, + -23984,-24030,-24077,-24124,-24171,-24217,-24264,-24311, + -24357,-24404,-24451,-24497,-24544,-24591,-24637,-24684, + -24730,-24777,-24823,-24870,-24916,-24963,-25009,-25056, + -25102,-25149,-25195,-25241,-25288,-25334,-25381,-25427, + -25473,-25520,-25566,-25612,-25658,-25705,-25751,-25797, + -25843,-25889,-25936,-25982,-26028,-26074,-26120,-26166, + -26212,-26258,-26304,-26350,-26396,-26442,-26488,-26534, + -26580,-26626,-26672,-26718,-26764,-26810,-26856,-26902, + -26947,-26993,-27039,-27085,-27131,-27176,-27222,-27268, + -27313,-27359,-27405,-27450,-27496,-27542,-27587,-27633, + -27678,-27724,-27770,-27815,-27861,-27906,-27952,-27997, + -28042,-28088,-28133,-28179,-28224,-28269,-28315,-28360, + -28405,-28451,-28496,-28541,-28586,-28632,-28677,-28722, + -28767,-28812,-28858,-28903,-28948,-28993,-29038,-29083, + -29128,-29173,-29218,-29263,-29308,-29353,-29398,-29443, + -29488,-29533,-29577,-29622,-29667,-29712,-29757,-29801, + -29846,-29891,-29936,-29980,-30025,-30070,-30114,-30159, + -30204,-30248,-30293,-30337,-30382,-30426,-30471,-30515, + -30560,-30604,-30649,-30693,-30738,-30782,-30826,-30871, + -30915,-30959,-31004,-31048,-31092,-31136,-31181,-31225, + -31269,-31313,-31357,-31402,-31446,-31490,-31534,-31578, + -31622,-31666,-31710,-31754,-31798,-31842,-31886,-31930, + -31974,-32017,-32061,-32105,-32149,-32193,-32236,-32280, + -32324,-32368,-32411,-32455,-32499,-32542,-32586,-32630, + -32673,-32717,-32760,-32804,-32847,-32891,-32934,-32978, + -33021,-33065,-33108,-33151,-33195,-33238,-33281,-33325, + -33368,-33411,-33454,-33498,-33541,-33584,-33627,-33670, + -33713,-33756,-33799,-33843,-33886,-33929,-33972,-34015, + -34057,-34100,-34143,-34186,-34229,-34272,-34315,-34358, + -34400,-34443,-34486,-34529,-34571,-34614,-34657,-34699, + -34742,-34785,-34827,-34870,-34912,-34955,-34997,-35040, + -35082,-35125,-35167,-35210,-35252,-35294,-35337,-35379, + -35421,-35464,-35506,-35548,-35590,-35633,-35675,-35717, + -35759,-35801,-35843,-35885,-35927,-35969,-36011,-36053, + -36095,-36137,-36179,-36221,-36263,-36305,-36347,-36388, + -36430,-36472,-36514,-36555,-36597,-36639,-36681,-36722, + -36764,-36805,-36847,-36889,-36930,-36972,-37013,-37055, + -37096,-37137,-37179,-37220,-37262,-37303,-37344,-37386, + -37427,-37468,-37509,-37551,-37592,-37633,-37674,-37715, + -37756,-37797,-37838,-37879,-37920,-37961,-38002,-38043, + -38084,-38125,-38166,-38207,-38248,-38288,-38329,-38370, + -38411,-38451,-38492,-38533,-38573,-38614,-38655,-38695, + -38736,-38776,-38817,-38857,-38898,-38938,-38979,-39019, + -39059,-39100,-39140,-39180,-39221,-39261,-39301,-39341, + -39382,-39422,-39462,-39502,-39542,-39582,-39622,-39662, + -39702,-39742,-39782,-39822,-39862,-39902,-39942,-39982, + -40021,-40061,-40101,-40141,-40180,-40220,-40260,-40299, + -40339,-40379,-40418,-40458,-40497,-40537,-40576,-40616, + -40655,-40695,-40734,-40773,-40813,-40852,-40891,-40931, + -40970,-41009,-41048,-41087,-41127,-41166,-41205,-41244, + -41283,-41322,-41361,-41400,-41439,-41478,-41517,-41556, + -41595,-41633,-41672,-41711,-41750,-41788,-41827,-41866, + -41904,-41943,-41982,-42020,-42059,-42097,-42136,-42174, + -42213,-42251,-42290,-42328,-42366,-42405,-42443,-42481, + -42520,-42558,-42596,-42634,-42672,-42711,-42749,-42787, + -42825,-42863,-42901,-42939,-42977,-43015,-43053,-43091, + -43128,-43166,-43204,-43242,-43280,-43317,-43355,-43393, + -43430,-43468,-43506,-43543,-43581,-43618,-43656,-43693, + -43731,-43768,-43806,-43843,-43880,-43918,-43955,-43992, + -44029,-44067,-44104,-44141,-44178,-44215,-44252,-44289, + -44326,-44363,-44400,-44437,-44474,-44511,-44548,-44585, + -44622,-44659,-44695,-44732,-44769,-44806,-44842,-44879, + -44915,-44952,-44989,-45025,-45062,-45098,-45135,-45171, + -45207,-45244,-45280,-45316,-45353,-45389,-45425,-45462, + -45498,-45534,-45570,-45606,-45642,-45678,-45714,-45750, + -45786,-45822,-45858,-45894,-45930,-45966,-46002,-46037, + -46073,-46109,-46145,-46180,-46216,-46252,-46287,-46323, + -46358,-46394,-46429,-46465,-46500,-46536,-46571,-46606, + -46642,-46677,-46712,-46747,-46783,-46818,-46853,-46888, + -46923,-46958,-46993,-47028,-47063,-47098,-47133,-47168, + -47203,-47238,-47273,-47308,-47342,-47377,-47412,-47446, + -47481,-47516,-47550,-47585,-47619,-47654,-47688,-47723, + -47757,-47792,-47826,-47860,-47895,-47929,-47963,-47998, + -48032,-48066,-48100,-48134,-48168,-48202,-48236,-48271, + -48304,-48338,-48372,-48406,-48440,-48474,-48508,-48542, + -48575,-48609,-48643,-48676,-48710,-48744,-48777,-48811, + -48844,-48878,-48911,-48945,-48978,-49012,-49045,-49078, + -49112,-49145,-49178,-49211,-49244,-49278,-49311,-49344, + -49377,-49410,-49443,-49476,-49509,-49542,-49575,-49608, + -49640,-49673,-49706,-49739,-49771,-49804,-49837,-49869, + -49902,-49935,-49967,-50000,-50032,-50065,-50097,-50129, + -50162,-50194,-50226,-50259,-50291,-50323,-50355,-50387, + -50420,-50452,-50484,-50516,-50548,-50580,-50612,-50644, + -50675,-50707,-50739,-50771,-50803,-50834,-50866,-50898, + -50929,-50961,-50993,-51024,-51056,-51087,-51119,-51150, + -51182,-51213,-51244,-51276,-51307,-51338,-51369,-51401, + -51432,-51463,-51494,-51525,-51556,-51587,-51618,-51649, + -51680,-51711,-51742,-51773,-51803,-51834,-51865,-51896, + -51926,-51957,-51988,-52018,-52049,-52079,-52110,-52140, + -52171,-52201,-52231,-52262,-52292,-52322,-52353,-52383, + -52413,-52443,-52473,-52503,-52534,-52564,-52594,-52624, + -52653,-52683,-52713,-52743,-52773,-52803,-52832,-52862, + -52892,-52922,-52951,-52981,-53010,-53040,-53069,-53099, + -53128,-53158,-53187,-53216,-53246,-53275,-53304,-53334, + -53363,-53392,-53421,-53450,-53479,-53508,-53537,-53566, + -53595,-53624,-53653,-53682,-53711,-53739,-53768,-53797, + -53826,-53854,-53883,-53911,-53940,-53969,-53997,-54026, + -54054,-54082,-54111,-54139,-54167,-54196,-54224,-54252, + -54280,-54308,-54337,-54365,-54393,-54421,-54449,-54477, + -54505,-54533,-54560,-54588,-54616,-54644,-54672,-54699, + -54727,-54755,-54782,-54810,-54837,-54865,-54892,-54920, + -54947,-54974,-55002,-55029,-55056,-55084,-55111,-55138, + -55165,-55192,-55219,-55246,-55274,-55300,-55327,-55354, + -55381,-55408,-55435,-55462,-55489,-55515,-55542,-55569, + -55595,-55622,-55648,-55675,-55701,-55728,-55754,-55781, + -55807,-55833,-55860,-55886,-55912,-55938,-55965,-55991, + -56017,-56043,-56069,-56095,-56121,-56147,-56173,-56199, + -56225,-56250,-56276,-56302,-56328,-56353,-56379,-56404, + -56430,-56456,-56481,-56507,-56532,-56557,-56583,-56608, + -56633,-56659,-56684,-56709,-56734,-56760,-56785,-56810, + -56835,-56860,-56885,-56910,-56935,-56959,-56984,-57009, + -57034,-57059,-57083,-57108,-57133,-57157,-57182,-57206, + -57231,-57255,-57280,-57304,-57329,-57353,-57377,-57402, + -57426,-57450,-57474,-57498,-57522,-57546,-57570,-57594, + -57618,-57642,-57666,-57690,-57714,-57738,-57762,-57785, + -57809,-57833,-57856,-57880,-57903,-57927,-57950,-57974, + -57997,-58021,-58044,-58067,-58091,-58114,-58137,-58160, + -58183,-58207,-58230,-58253,-58276,-58299,-58322,-58345, + -58367,-58390,-58413,-58436,-58459,-58481,-58504,-58527, + -58549,-58572,-58594,-58617,-58639,-58662,-58684,-58706, + -58729,-58751,-58773,-58795,-58818,-58840,-58862,-58884, + -58906,-58928,-58950,-58972,-58994,-59016,-59038,-59059, + -59081,-59103,-59125,-59146,-59168,-59190,-59211,-59233, + -59254,-59276,-59297,-59318,-59340,-59361,-59382,-59404, + -59425,-59446,-59467,-59488,-59509,-59530,-59551,-59572, + -59593,-59614,-59635,-59656,-59677,-59697,-59718,-59739, + -59759,-59780,-59801,-59821,-59842,-59862,-59883,-59903, + -59923,-59944,-59964,-59984,-60004,-60025,-60045,-60065, + -60085,-60105,-60125,-60145,-60165,-60185,-60205,-60225, + -60244,-60264,-60284,-60304,-60323,-60343,-60363,-60382, + -60402,-60421,-60441,-60460,-60479,-60499,-60518,-60537, + -60556,-60576,-60595,-60614,-60633,-60652,-60671,-60690, + -60709,-60728,-60747,-60766,-60785,-60803,-60822,-60841, + -60859,-60878,-60897,-60915,-60934,-60952,-60971,-60989, + -61007,-61026,-61044,-61062,-61081,-61099,-61117,-61135, + -61153,-61171,-61189,-61207,-61225,-61243,-61261,-61279, + -61297,-61314,-61332,-61350,-61367,-61385,-61403,-61420, + -61438,-61455,-61473,-61490,-61507,-61525,-61542,-61559, + -61577,-61594,-61611,-61628,-61645,-61662,-61679,-61696, + -61713,-61730,-61747,-61764,-61780,-61797,-61814,-61831, + -61847,-61864,-61880,-61897,-61913,-61930,-61946,-61963, + -61979,-61995,-62012,-62028,-62044,-62060,-62076,-62092, + -62108,-62125,-62141,-62156,-62172,-62188,-62204,-62220, + -62236,-62251,-62267,-62283,-62298,-62314,-62329,-62345, + -62360,-62376,-62391,-62407,-62422,-62437,-62453,-62468, + -62483,-62498,-62513,-62528,-62543,-62558,-62573,-62588, + -62603,-62618,-62633,-62648,-62662,-62677,-62692,-62706, + -62721,-62735,-62750,-62764,-62779,-62793,-62808,-62822, + -62836,-62850,-62865,-62879,-62893,-62907,-62921,-62935, + -62949,-62963,-62977,-62991,-63005,-63019,-63032,-63046, + -63060,-63074,-63087,-63101,-63114,-63128,-63141,-63155, + -63168,-63182,-63195,-63208,-63221,-63235,-63248,-63261, + -63274,-63287,-63300,-63313,-63326,-63339,-63352,-63365, + -63378,-63390,-63403,-63416,-63429,-63441,-63454,-63466, + -63479,-63491,-63504,-63516,-63528,-63541,-63553,-63565, + -63578,-63590,-63602,-63614,-63626,-63638,-63650,-63662, + -63674,-63686,-63698,-63709,-63721,-63733,-63745,-63756, + -63768,-63779,-63791,-63803,-63814,-63825,-63837,-63848, + -63859,-63871,-63882,-63893,-63904,-63915,-63927,-63938, + -63949,-63960,-63971,-63981,-63992,-64003,-64014,-64025, + -64035,-64046,-64057,-64067,-64078,-64088,-64099,-64109, + -64120,-64130,-64140,-64151,-64161,-64171,-64181,-64192, + -64202,-64212,-64222,-64232,-64242,-64252,-64261,-64271, + -64281,-64291,-64301,-64310,-64320,-64330,-64339,-64349, + -64358,-64368,-64377,-64387,-64396,-64405,-64414,-64424, + -64433,-64442,-64451,-64460,-64469,-64478,-64487,-64496, + -64505,-64514,-64523,-64532,-64540,-64549,-64558,-64566, + -64575,-64584,-64592,-64601,-64609,-64617,-64626,-64634, + -64642,-64651,-64659,-64667,-64675,-64683,-64691,-64699, + -64707,-64715,-64723,-64731,-64739,-64747,-64754,-64762, + -64770,-64777,-64785,-64793,-64800,-64808,-64815,-64822, + -64830,-64837,-64844,-64852,-64859,-64866,-64873,-64880, + -64887,-64895,-64902,-64908,-64915,-64922,-64929,-64936, + -64943,-64949,-64956,-64963,-64969,-64976,-64982,-64989, + -64995,-65002,-65008,-65015,-65021,-65027,-65033,-65040, + -65046,-65052,-65058,-65064,-65070,-65076,-65082,-65088, + -65094,-65099,-65105,-65111,-65117,-65122,-65128,-65133, + -65139,-65144,-65150,-65155,-65161,-65166,-65171,-65177, + -65182,-65187,-65192,-65197,-65202,-65207,-65212,-65217, + -65222,-65227,-65232,-65237,-65242,-65246,-65251,-65256, + -65260,-65265,-65270,-65274,-65279,-65283,-65287,-65292, + -65296,-65300,-65305,-65309,-65313,-65317,-65321,-65325, + -65329,-65333,-65337,-65341,-65345,-65349,-65352,-65356, + -65360,-65363,-65367,-65371,-65374,-65378,-65381,-65385, + -65388,-65391,-65395,-65398,-65401,-65404,-65408,-65411, + -65414,-65417,-65420,-65423,-65426,-65429,-65431,-65434, + -65437,-65440,-65442,-65445,-65448,-65450,-65453,-65455, + -65458,-65460,-65463,-65465,-65467,-65470,-65472,-65474, + -65476,-65478,-65480,-65482,-65484,-65486,-65488,-65490, + -65492,-65494,-65496,-65497,-65499,-65501,-65502,-65504, + -65505,-65507,-65508,-65510,-65511,-65513,-65514,-65515, + -65516,-65518,-65519,-65520,-65521,-65522,-65523,-65524, + -65525,-65526,-65527,-65527,-65528,-65529,-65530,-65530, + -65531,-65531,-65532,-65532,-65533,-65533,-65534,-65534, + -65534,-65535,-65535,-65535,-65535,-65535,-65535,-65535, + -65535,-65535,-65535,-65535,-65535,-65535,-65535,-65534, + -65534,-65534,-65533,-65533,-65532,-65532,-65531,-65531, + -65530,-65530,-65529,-65528,-65527,-65527,-65526,-65525, + -65524,-65523,-65522,-65521,-65520,-65519,-65518,-65516, + -65515,-65514,-65513,-65511,-65510,-65508,-65507,-65505, + -65504,-65502,-65501,-65499,-65497,-65496,-65494,-65492, + -65490,-65488,-65486,-65484,-65482,-65480,-65478,-65476, + -65474,-65472,-65470,-65467,-65465,-65463,-65460,-65458, + -65455,-65453,-65450,-65448,-65445,-65442,-65440,-65437, + -65434,-65431,-65429,-65426,-65423,-65420,-65417,-65414, + -65411,-65408,-65404,-65401,-65398,-65395,-65391,-65388, + -65385,-65381,-65378,-65374,-65371,-65367,-65363,-65360, + -65356,-65352,-65349,-65345,-65341,-65337,-65333,-65329, + -65325,-65321,-65317,-65313,-65309,-65305,-65300,-65296, + -65292,-65287,-65283,-65279,-65274,-65270,-65265,-65260, + -65256,-65251,-65246,-65242,-65237,-65232,-65227,-65222, + -65217,-65212,-65207,-65202,-65197,-65192,-65187,-65182, + -65177,-65171,-65166,-65161,-65155,-65150,-65144,-65139, + -65133,-65128,-65122,-65117,-65111,-65105,-65099,-65094, + -65088,-65082,-65076,-65070,-65064,-65058,-65052,-65046, + -65040,-65033,-65027,-65021,-65015,-65008,-65002,-64995, + -64989,-64982,-64976,-64969,-64963,-64956,-64949,-64943, + -64936,-64929,-64922,-64915,-64908,-64902,-64895,-64887, + -64880,-64873,-64866,-64859,-64852,-64844,-64837,-64830, + -64822,-64815,-64808,-64800,-64793,-64785,-64777,-64770, + -64762,-64754,-64747,-64739,-64731,-64723,-64715,-64707, + -64699,-64691,-64683,-64675,-64667,-64659,-64651,-64642, + -64634,-64626,-64617,-64609,-64601,-64592,-64584,-64575, + -64566,-64558,-64549,-64540,-64532,-64523,-64514,-64505, + -64496,-64487,-64478,-64469,-64460,-64451,-64442,-64433, + -64424,-64414,-64405,-64396,-64387,-64377,-64368,-64358, + -64349,-64339,-64330,-64320,-64310,-64301,-64291,-64281, + -64271,-64261,-64252,-64242,-64232,-64222,-64212,-64202, + -64192,-64181,-64171,-64161,-64151,-64140,-64130,-64120, + -64109,-64099,-64088,-64078,-64067,-64057,-64046,-64035, + -64025,-64014,-64003,-63992,-63981,-63971,-63960,-63949, + -63938,-63927,-63915,-63904,-63893,-63882,-63871,-63859, + -63848,-63837,-63825,-63814,-63803,-63791,-63779,-63768, + -63756,-63745,-63733,-63721,-63709,-63698,-63686,-63674, + -63662,-63650,-63638,-63626,-63614,-63602,-63590,-63578, + -63565,-63553,-63541,-63528,-63516,-63504,-63491,-63479, + -63466,-63454,-63441,-63429,-63416,-63403,-63390,-63378, + -63365,-63352,-63339,-63326,-63313,-63300,-63287,-63274, + -63261,-63248,-63235,-63221,-63208,-63195,-63182,-63168, + -63155,-63141,-63128,-63114,-63101,-63087,-63074,-63060, + -63046,-63032,-63019,-63005,-62991,-62977,-62963,-62949, + -62935,-62921,-62907,-62893,-62879,-62865,-62850,-62836, + -62822,-62808,-62793,-62779,-62764,-62750,-62735,-62721, + -62706,-62692,-62677,-62662,-62648,-62633,-62618,-62603, + -62588,-62573,-62558,-62543,-62528,-62513,-62498,-62483, + -62468,-62453,-62437,-62422,-62407,-62391,-62376,-62360, + -62345,-62329,-62314,-62298,-62283,-62267,-62251,-62236, + -62220,-62204,-62188,-62172,-62156,-62141,-62125,-62108, + -62092,-62076,-62060,-62044,-62028,-62012,-61995,-61979, + -61963,-61946,-61930,-61913,-61897,-61880,-61864,-61847, + -61831,-61814,-61797,-61780,-61764,-61747,-61730,-61713, + -61696,-61679,-61662,-61645,-61628,-61611,-61594,-61577, + -61559,-61542,-61525,-61507,-61490,-61473,-61455,-61438, + -61420,-61403,-61385,-61367,-61350,-61332,-61314,-61297, + -61279,-61261,-61243,-61225,-61207,-61189,-61171,-61153, + -61135,-61117,-61099,-61081,-61062,-61044,-61026,-61007, + -60989,-60971,-60952,-60934,-60915,-60897,-60878,-60859, + -60841,-60822,-60803,-60785,-60766,-60747,-60728,-60709, + -60690,-60671,-60652,-60633,-60614,-60595,-60576,-60556, + -60537,-60518,-60499,-60479,-60460,-60441,-60421,-60402, + -60382,-60363,-60343,-60323,-60304,-60284,-60264,-60244, + -60225,-60205,-60185,-60165,-60145,-60125,-60105,-60085, + -60065,-60045,-60025,-60004,-59984,-59964,-59944,-59923, + -59903,-59883,-59862,-59842,-59821,-59801,-59780,-59759, + -59739,-59718,-59697,-59677,-59656,-59635,-59614,-59593, + -59572,-59551,-59530,-59509,-59488,-59467,-59446,-59425, + -59404,-59382,-59361,-59340,-59318,-59297,-59276,-59254, + -59233,-59211,-59189,-59168,-59146,-59125,-59103,-59081, + -59059,-59038,-59016,-58994,-58972,-58950,-58928,-58906, + -58884,-58862,-58840,-58818,-58795,-58773,-58751,-58729, + -58706,-58684,-58662,-58639,-58617,-58594,-58572,-58549, + -58527,-58504,-58481,-58459,-58436,-58413,-58390,-58367, + -58345,-58322,-58299,-58276,-58253,-58230,-58207,-58183, + -58160,-58137,-58114,-58091,-58067,-58044,-58021,-57997, + -57974,-57950,-57927,-57903,-57880,-57856,-57833,-57809, + -57785,-57762,-57738,-57714,-57690,-57666,-57642,-57618, + -57594,-57570,-57546,-57522,-57498,-57474,-57450,-57426, + -57402,-57377,-57353,-57329,-57304,-57280,-57255,-57231, + -57206,-57182,-57157,-57133,-57108,-57083,-57059,-57034, + -57009,-56984,-56959,-56935,-56910,-56885,-56860,-56835, + -56810,-56785,-56760,-56734,-56709,-56684,-56659,-56633, + -56608,-56583,-56557,-56532,-56507,-56481,-56456,-56430, + -56404,-56379,-56353,-56328,-56302,-56276,-56250,-56225, + -56199,-56173,-56147,-56121,-56095,-56069,-56043,-56017, + -55991,-55965,-55938,-55912,-55886,-55860,-55833,-55807, + -55781,-55754,-55728,-55701,-55675,-55648,-55622,-55595, + -55569,-55542,-55515,-55489,-55462,-55435,-55408,-55381, + -55354,-55327,-55300,-55274,-55246,-55219,-55192,-55165, + -55138,-55111,-55084,-55056,-55029,-55002,-54974,-54947, + -54920,-54892,-54865,-54837,-54810,-54782,-54755,-54727, + -54699,-54672,-54644,-54616,-54588,-54560,-54533,-54505, + -54477,-54449,-54421,-54393,-54365,-54337,-54308,-54280, + -54252,-54224,-54196,-54167,-54139,-54111,-54082,-54054, + -54026,-53997,-53969,-53940,-53911,-53883,-53854,-53826, + -53797,-53768,-53739,-53711,-53682,-53653,-53624,-53595, + -53566,-53537,-53508,-53479,-53450,-53421,-53392,-53363, + -53334,-53304,-53275,-53246,-53216,-53187,-53158,-53128, + -53099,-53069,-53040,-53010,-52981,-52951,-52922,-52892, + -52862,-52832,-52803,-52773,-52743,-52713,-52683,-52653, + -52624,-52594,-52564,-52534,-52503,-52473,-52443,-52413, + -52383,-52353,-52322,-52292,-52262,-52231,-52201,-52171, + -52140,-52110,-52079,-52049,-52018,-51988,-51957,-51926, + -51896,-51865,-51834,-51803,-51773,-51742,-51711,-51680, + -51649,-51618,-51587,-51556,-51525,-51494,-51463,-51432, + -51401,-51369,-51338,-51307,-51276,-51244,-51213,-51182, + -51150,-51119,-51087,-51056,-51024,-50993,-50961,-50929, + -50898,-50866,-50834,-50803,-50771,-50739,-50707,-50675, + -50644,-50612,-50580,-50548,-50516,-50484,-50452,-50420, + -50387,-50355,-50323,-50291,-50259,-50226,-50194,-50162, + -50129,-50097,-50065,-50032,-50000,-49967,-49935,-49902, + -49869,-49837,-49804,-49771,-49739,-49706,-49673,-49640, + -49608,-49575,-49542,-49509,-49476,-49443,-49410,-49377, + -49344,-49311,-49278,-49244,-49211,-49178,-49145,-49112, + -49078,-49045,-49012,-48978,-48945,-48911,-48878,-48844, + -48811,-48777,-48744,-48710,-48676,-48643,-48609,-48575, + -48542,-48508,-48474,-48440,-48406,-48372,-48338,-48305, + -48271,-48237,-48202,-48168,-48134,-48100,-48066,-48032, + -47998,-47963,-47929,-47895,-47860,-47826,-47792,-47757, + -47723,-47688,-47654,-47619,-47585,-47550,-47516,-47481, + -47446,-47412,-47377,-47342,-47307,-47273,-47238,-47203, + -47168,-47133,-47098,-47063,-47028,-46993,-46958,-46923, + -46888,-46853,-46818,-46783,-46747,-46712,-46677,-46642, + -46606,-46571,-46536,-46500,-46465,-46429,-46394,-46358, + -46323,-46287,-46251,-46216,-46180,-46145,-46109,-46073, + -46037,-46002,-45966,-45930,-45894,-45858,-45822,-45786, + -45750,-45714,-45678,-45642,-45606,-45570,-45534,-45498, + -45462,-45425,-45389,-45353,-45316,-45280,-45244,-45207, + -45171,-45135,-45098,-45062,-45025,-44989,-44952,-44915, + -44879,-44842,-44806,-44769,-44732,-44695,-44659,-44622, + -44585,-44548,-44511,-44474,-44437,-44400,-44363,-44326, + -44289,-44252,-44215,-44178,-44141,-44104,-44067,-44029, + -43992,-43955,-43918,-43880,-43843,-43806,-43768,-43731, + -43693,-43656,-43618,-43581,-43543,-43506,-43468,-43430, + -43393,-43355,-43317,-43280,-43242,-43204,-43166,-43128, + -43091,-43053,-43015,-42977,-42939,-42901,-42863,-42825, + -42787,-42749,-42711,-42672,-42634,-42596,-42558,-42520, + -42481,-42443,-42405,-42366,-42328,-42290,-42251,-42213, + -42174,-42136,-42097,-42059,-42020,-41982,-41943,-41904, + -41866,-41827,-41788,-41750,-41711,-41672,-41633,-41595, + -41556,-41517,-41478,-41439,-41400,-41361,-41322,-41283, + -41244,-41205,-41166,-41127,-41087,-41048,-41009,-40970, + -40931,-40891,-40852,-40813,-40773,-40734,-40695,-40655, + -40616,-40576,-40537,-40497,-40458,-40418,-40379,-40339, + -40299,-40260,-40220,-40180,-40141,-40101,-40061,-40021, + -39982,-39942,-39902,-39862,-39822,-39782,-39742,-39702, + -39662,-39622,-39582,-39542,-39502,-39462,-39422,-39382, + -39341,-39301,-39261,-39221,-39180,-39140,-39100,-39059, + -39019,-38979,-38938,-38898,-38857,-38817,-38776,-38736, + -38695,-38655,-38614,-38573,-38533,-38492,-38451,-38411, + -38370,-38329,-38288,-38248,-38207,-38166,-38125,-38084, + -38043,-38002,-37961,-37920,-37879,-37838,-37797,-37756, + -37715,-37674,-37633,-37592,-37550,-37509,-37468,-37427, + -37386,-37344,-37303,-37262,-37220,-37179,-37137,-37096, + -37055,-37013,-36972,-36930,-36889,-36847,-36805,-36764, + -36722,-36681,-36639,-36597,-36556,-36514,-36472,-36430, + -36388,-36347,-36305,-36263,-36221,-36179,-36137,-36095, + -36053,-36011,-35969,-35927,-35885,-35843,-35801,-35759, + -35717,-35675,-35633,-35590,-35548,-35506,-35464,-35421, + -35379,-35337,-35294,-35252,-35210,-35167,-35125,-35082, + -35040,-34997,-34955,-34912,-34870,-34827,-34785,-34742, + -34699,-34657,-34614,-34571,-34529,-34486,-34443,-34400, + -34358,-34315,-34272,-34229,-34186,-34143,-34100,-34057, + -34015,-33972,-33929,-33886,-33843,-33799,-33756,-33713, + -33670,-33627,-33584,-33541,-33498,-33454,-33411,-33368, + -33325,-33281,-33238,-33195,-33151,-33108,-33065,-33021, + -32978,-32934,-32891,-32847,-32804,-32760,-32717,-32673, + -32630,-32586,-32542,-32499,-32455,-32411,-32368,-32324, + -32280,-32236,-32193,-32149,-32105,-32061,-32017,-31974, + -31930,-31886,-31842,-31798,-31754,-31710,-31666,-31622, + -31578,-31534,-31490,-31446,-31402,-31357,-31313,-31269, + -31225,-31181,-31136,-31092,-31048,-31004,-30959,-30915, + -30871,-30826,-30782,-30738,-30693,-30649,-30604,-30560, + -30515,-30471,-30426,-30382,-30337,-30293,-30248,-30204, + -30159,-30114,-30070,-30025,-29980,-29936,-29891,-29846, + -29801,-29757,-29712,-29667,-29622,-29577,-29533,-29488, + -29443,-29398,-29353,-29308,-29263,-29218,-29173,-29128, + -29083,-29038,-28993,-28948,-28903,-28858,-28812,-28767, + -28722,-28677,-28632,-28586,-28541,-28496,-28451,-28405, + -28360,-28315,-28269,-28224,-28179,-28133,-28088,-28042, + -27997,-27952,-27906,-27861,-27815,-27770,-27724,-27678, + -27633,-27587,-27542,-27496,-27450,-27405,-27359,-27313, + -27268,-27222,-27176,-27131,-27085,-27039,-26993,-26947, + -26902,-26856,-26810,-26764,-26718,-26672,-26626,-26580, + -26534,-26488,-26442,-26396,-26350,-26304,-26258,-26212, + -26166,-26120,-26074,-26028,-25982,-25936,-25889,-25843, + -25797,-25751,-25705,-25658,-25612,-25566,-25520,-25473, + -25427,-25381,-25334,-25288,-25241,-25195,-25149,-25102, + -25056,-25009,-24963,-24916,-24870,-24823,-24777,-24730, + -24684,-24637,-24591,-24544,-24497,-24451,-24404,-24357, + -24311,-24264,-24217,-24171,-24124,-24077,-24030,-23984, + -23937,-23890,-23843,-23796,-23750,-23703,-23656,-23609, + -23562,-23515,-23468,-23421,-23374,-23327,-23280,-23233, + -23186,-23139,-23092,-23045,-22998,-22951,-22904,-22857, + -22810,-22763,-22716,-22668,-22621,-22574,-22527,-22480, + -22432,-22385,-22338,-22291,-22243,-22196,-22149,-22102, + -22054,-22007,-21960,-21912,-21865,-21817,-21770,-21723, + -21675,-21628,-21580,-21533,-21485,-21438,-21390,-21343, + -21295,-21248,-21200,-21153,-21105,-21057,-21010,-20962, + -20915,-20867,-20819,-20772,-20724,-20676,-20629,-20581, + -20533,-20485,-20438,-20390,-20342,-20294,-20246,-20199, + -20151,-20103,-20055,-20007,-19959,-19912,-19864,-19816, + -19768,-19720,-19672,-19624,-19576,-19528,-19480,-19432, + -19384,-19336,-19288,-19240,-19192,-19144,-19096,-19048, + -19000,-18951,-18903,-18855,-18807,-18759,-18711,-18663, + -18614,-18566,-18518,-18470,-18421,-18373,-18325,-18277, + -18228,-18180,-18132,-18084,-18035,-17987,-17939,-17890, + -17842,-17793,-17745,-17697,-17648,-17600,-17551,-17503, + -17455,-17406,-17358,-17309,-17261,-17212,-17164,-17115, + -17067,-17018,-16970,-16921,-16872,-16824,-16775,-16727, + -16678,-16629,-16581,-16532,-16484,-16435,-16386,-16338, + -16289,-16240,-16191,-16143,-16094,-16045,-15997,-15948, + -15899,-15850,-15802,-15753,-15704,-15655,-15606,-15557, + -15509,-15460,-15411,-15362,-15313,-15264,-15215,-15167, + -15118,-15069,-15020,-14971,-14922,-14873,-14824,-14775, + -14726,-14677,-14628,-14579,-14530,-14481,-14432,-14383, + -14334,-14285,-14236,-14187,-14138,-14089,-14040,-13990, + -13941,-13892,-13843,-13794,-13745,-13696,-13647,-13597, + -13548,-13499,-13450,-13401,-13351,-13302,-13253,-13204, + -13154,-13105,-13056,-13007,-12957,-12908,-12859,-12810, + -12760,-12711,-12662,-12612,-12563,-12514,-12464,-12415, + -12366,-12316,-12267,-12217,-12168,-12119,-12069,-12020, + -11970,-11921,-11872,-11822,-11773,-11723,-11674,-11624, + -11575,-11525,-11476,-11426,-11377,-11327,-11278,-11228, + -11179,-11129,-11080,-11030,-10981,-10931,-10882,-10832, + -10782,-10733,-10683,-10634,-10584,-10534,-10485,-10435, + -10386,-10336,-10286,-10237,-10187,-10137,-10088,-10038, + -9988,-9939,-9889,-9839,-9790,-9740,-9690,-9640, + -9591,-9541,-9491,-9442,-9392,-9342,-9292,-9243, + -9193,-9143,-9093,-9043,-8994,-8944,-8894,-8844, + -8794,-8745,-8695,-8645,-8595,-8545,-8496,-8446, + -8396,-8346,-8296,-8246,-8196,-8147,-8097,-8047, + -7997,-7947,-7897,-7847,-7797,-7747,-7697,-7648, + -7598,-7548,-7498,-7448,-7398,-7348,-7298,-7248, + -7198,-7148,-7098,-7048,-6998,-6948,-6898,-6848, + -6798,-6748,-6698,-6648,-6598,-6548,-6498,-6448, + -6398,-6348,-6298,-6248,-6198,-6148,-6098,-6048, + -5998,-5948,-5898,-5848,-5798,-5747,-5697,-5647, + -5597,-5547,-5497,-5447,-5397,-5347,-5297,-5247, + -5197,-5146,-5096,-5046,-4996,-4946,-4896,-4846, + -4796,-4745,-4695,-4645,-4595,-4545,-4495,-4445, + -4394,-4344,-4294,-4244,-4194,-4144,-4093,-4043, + -3993,-3943,-3893,-3843,-3792,-3742,-3692,-3642, + -3592,-3541,-3491,-3441,-3391,-3341,-3291,-3240, + -3190,-3140,-3090,-3039,-2989,-2939,-2889,-2839, + -2788,-2738,-2688,-2638,-2588,-2537,-2487,-2437, + -2387,-2336,-2286,-2236,-2186,-2135,-2085,-2035, + -1985,-1934,-1884,-1834,-1784,-1733,-1683,-1633, + -1583,-1532,-1482,-1432,-1382,-1331,-1281,-1231, + -1181,-1130,-1080,-1030,-980,-929,-879,-829, + -779,-728,-678,-628,-578,-527,-477,-427, + -376,-326,-276,-226,-175,-125,-75,-25, + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1130,1181, + 1231,1281,1331,1382,1432,1482,1532,1583, + 1633,1683,1733,1784,1834,1884,1934,1985, + 2035,2085,2135,2186,2236,2286,2336,2387, + 2437,2487,2537,2587,2638,2688,2738,2788, + 2839,2889,2939,2989,3039,3090,3140,3190, + 3240,3291,3341,3391,3441,3491,3542,3592, + 3642,3692,3742,3792,3843,3893,3943,3993, + 4043,4093,4144,4194,4244,4294,4344,4394, + 4445,4495,4545,4595,4645,4695,4745,4796, + 4846,4896,4946,4996,5046,5096,5146,5197, + 5247,5297,5347,5397,5447,5497,5547,5597, + 5647,5697,5747,5798,5848,5898,5948,5998, + 6048,6098,6148,6198,6248,6298,6348,6398, + 6448,6498,6548,6598,6648,6698,6748,6798, + 6848,6898,6948,6998,7048,7098,7148,7198, + 7248,7298,7348,7398,7448,7498,7548,7598, + 7648,7697,7747,7797,7847,7897,7947,7997, + 8047,8097,8147,8196,8246,8296,8346,8396, + 8446,8496,8545,8595,8645,8695,8745,8794, + 8844,8894,8944,8994,9043,9093,9143,9193, + 9243,9292,9342,9392,9442,9491,9541,9591, + 9640,9690,9740,9790,9839,9889,9939,9988, + 10038,10088,10137,10187,10237,10286,10336,10386, + 10435,10485,10534,10584,10634,10683,10733,10782, + 10832,10882,10931,10981,11030,11080,11129,11179, + 11228,11278,11327,11377,11426,11476,11525,11575, + 11624,11674,11723,11773,11822,11872,11921,11970, + 12020,12069,12119,12168,12218,12267,12316,12366, + 12415,12464,12514,12563,12612,12662,12711,12760, + 12810,12859,12908,12957,13007,13056,13105,13154, + 13204,13253,13302,13351,13401,13450,13499,13548, + 13597,13647,13696,13745,13794,13843,13892,13941, + 13990,14040,14089,14138,14187,14236,14285,14334, + 14383,14432,14481,14530,14579,14628,14677,14726, + 14775,14824,14873,14922,14971,15020,15069,15118, + 15167,15215,15264,15313,15362,15411,15460,15509, + 15557,15606,15655,15704,15753,15802,15850,15899, + 15948,15997,16045,16094,16143,16191,16240,16289, + 16338,16386,16435,16484,16532,16581,16629,16678, + 16727,16775,16824,16872,16921,16970,17018,17067, + 17115,17164,17212,17261,17309,17358,17406,17455, + 17503,17551,17600,17648,17697,17745,17793,17842, + 17890,17939,17987,18035,18084,18132,18180,18228, + 18277,18325,18373,18421,18470,18518,18566,18614, + 18663,18711,18759,18807,18855,18903,18951,19000, + 19048,19096,19144,19192,19240,19288,19336,19384, + 19432,19480,19528,19576,19624,19672,19720,19768, + 19816,19864,19912,19959,20007,20055,20103,20151, + 20199,20246,20294,20342,20390,20438,20485,20533, + 20581,20629,20676,20724,20772,20819,20867,20915, + 20962,21010,21057,21105,21153,21200,21248,21295, + 21343,21390,21438,21485,21533,21580,21628,21675, + 21723,21770,21817,21865,21912,21960,22007,22054, + 22102,22149,22196,22243,22291,22338,22385,22432, + 22480,22527,22574,22621,22668,22716,22763,22810, + 22857,22904,22951,22998,23045,23092,23139,23186, + 23233,23280,23327,23374,23421,23468,23515,23562, + 23609,23656,23703,23750,23796,23843,23890,23937, + 23984,24030,24077,24124,24171,24217,24264,24311, + 24357,24404,24451,24497,24544,24591,24637,24684, + 24730,24777,24823,24870,24916,24963,25009,25056, + 25102,25149,25195,25241,25288,25334,25381,25427, + 25473,25520,25566,25612,25658,25705,25751,25797, + 25843,25889,25936,25982,26028,26074,26120,26166, + 26212,26258,26304,26350,26396,26442,26488,26534, + 26580,26626,26672,26718,26764,26810,26856,26902, + 26947,26993,27039,27085,27131,27176,27222,27268, + 27313,27359,27405,27450,27496,27542,27587,27633, + 27678,27724,27770,27815,27861,27906,27952,27997, + 28042,28088,28133,28179,28224,28269,28315,28360, + 28405,28451,28496,28541,28586,28632,28677,28722, + 28767,28812,28858,28903,28948,28993,29038,29083, + 29128,29173,29218,29263,29308,29353,29398,29443, + 29488,29533,29577,29622,29667,29712,29757,29801, + 29846,29891,29936,29980,30025,30070,30114,30159, + 30204,30248,30293,30337,30382,30427,30471,30516, + 30560,30604,30649,30693,30738,30782,30826,30871, + 30915,30959,31004,31048,31092,31136,31181,31225, + 31269,31313,31357,31402,31446,31490,31534,31578, + 31622,31666,31710,31754,31798,31842,31886,31930, + 31974,32017,32061,32105,32149,32193,32236,32280, + 32324,32368,32411,32455,32499,32542,32586,32630, + 32673,32717,32760,32804,32847,32891,32934,32978, + 33021,33065,33108,33151,33195,33238,33281,33325, + 33368,33411,33454,33498,33541,33584,33627,33670, + 33713,33756,33799,33843,33886,33929,33972,34015, + 34057,34100,34143,34186,34229,34272,34315,34358, + 34400,34443,34486,34529,34571,34614,34657,34699, + 34742,34785,34827,34870,34912,34955,34997,35040, + 35082,35125,35167,35210,35252,35294,35337,35379, + 35421,35464,35506,35548,35590,35633,35675,35717, + 35759,35801,35843,35885,35927,35969,36011,36053, + 36095,36137,36179,36221,36263,36305,36347,36388, + 36430,36472,36514,36556,36597,36639,36681,36722, + 36764,36805,36847,36889,36930,36972,37013,37055, + 37096,37137,37179,37220,37262,37303,37344,37386, + 37427,37468,37509,37551,37592,37633,37674,37715, + 37756,37797,37838,37879,37920,37961,38002,38043, + 38084,38125,38166,38207,38248,38288,38329,38370, + 38411,38451,38492,38533,38573,38614,38655,38695, + 38736,38776,38817,38857,38898,38938,38979,39019, + 39059,39100,39140,39180,39221,39261,39301,39341, + 39382,39422,39462,39502,39542,39582,39622,39662, + 39702,39742,39782,39822,39862,39902,39942,39982, + 40021,40061,40101,40141,40180,40220,40260,40299, + 40339,40379,40418,40458,40497,40537,40576,40616, + 40655,40695,40734,40773,40813,40852,40891,40931, + 40970,41009,41048,41087,41127,41166,41205,41244, + 41283,41322,41361,41400,41439,41478,41517,41556, + 41595,41633,41672,41711,41750,41788,41827,41866, + 41904,41943,41982,42020,42059,42097,42136,42174, + 42213,42251,42290,42328,42366,42405,42443,42481, + 42520,42558,42596,42634,42672,42711,42749,42787, + 42825,42863,42901,42939,42977,43015,43053,43091, + 43128,43166,43204,43242,43280,43317,43355,43393, + 43430,43468,43506,43543,43581,43618,43656,43693, + 43731,43768,43806,43843,43880,43918,43955,43992, + 44029,44067,44104,44141,44178,44215,44252,44289, + 44326,44363,44400,44437,44474,44511,44548,44585, + 44622,44659,44695,44732,44769,44806,44842,44879, + 44915,44952,44989,45025,45062,45098,45135,45171, + 45207,45244,45280,45316,45353,45389,45425,45462, + 45498,45534,45570,45606,45642,45678,45714,45750, + 45786,45822,45858,45894,45930,45966,46002,46037, + 46073,46109,46145,46180,46216,46252,46287,46323, + 46358,46394,46429,46465,46500,46536,46571,46606, + 46642,46677,46712,46747,46783,46818,46853,46888, + 46923,46958,46993,47028,47063,47098,47133,47168, + 47203,47238,47273,47308,47342,47377,47412,47446, + 47481,47516,47550,47585,47619,47654,47688,47723, + 47757,47792,47826,47861,47895,47929,47963,47998, + 48032,48066,48100,48134,48168,48202,48237,48271, + 48305,48338,48372,48406,48440,48474,48508,48542, + 48575,48609,48643,48676,48710,48744,48777,48811, + 48844,48878,48911,48945,48978,49012,49045,49078, + 49112,49145,49178,49211,49244,49278,49311,49344, + 49377,49410,49443,49476,49509,49542,49575,49608, + 49640,49673,49706,49739,49771,49804,49837,49869, + 49902,49935,49967,50000,50032,50064,50097,50129, + 50162,50194,50226,50259,50291,50323,50355,50387, + 50420,50452,50484,50516,50548,50580,50612,50644, + 50675,50707,50739,50771,50803,50834,50866,50898, + 50929,50961,50993,51024,51056,51087,51119,51150, + 51182,51213,51244,51276,51307,51338,51369,51401, + 51432,51463,51494,51525,51556,51587,51618,51649, + 51680,51711,51742,51773,51803,51834,51865,51896, + 51926,51957,51988,52018,52049,52079,52110,52140, + 52171,52201,52231,52262,52292,52322,52353,52383, + 52413,52443,52473,52503,52534,52564,52594,52624, + 52653,52683,52713,52743,52773,52803,52832,52862, + 52892,52922,52951,52981,53010,53040,53069,53099, + 53128,53158,53187,53216,53246,53275,53304,53334, + 53363,53392,53421,53450,53479,53508,53537,53566, + 53595,53624,53653,53682,53711,53739,53768,53797, + 53826,53854,53883,53912,53940,53969,53997,54026, + 54054,54082,54111,54139,54167,54196,54224,54252, + 54280,54309,54337,54365,54393,54421,54449,54477, + 54505,54533,54560,54588,54616,54644,54672,54699, + 54727,54755,54782,54810,54837,54865,54892,54920, + 54947,54974,55002,55029,55056,55084,55111,55138, + 55165,55192,55219,55246,55274,55300,55327,55354, + 55381,55408,55435,55462,55489,55515,55542,55569, + 55595,55622,55648,55675,55701,55728,55754,55781, + 55807,55833,55860,55886,55912,55938,55965,55991, + 56017,56043,56069,56095,56121,56147,56173,56199, + 56225,56250,56276,56302,56328,56353,56379,56404, + 56430,56456,56481,56507,56532,56557,56583,56608, + 56633,56659,56684,56709,56734,56760,56785,56810, + 56835,56860,56885,56910,56935,56959,56984,57009, + 57034,57059,57083,57108,57133,57157,57182,57206, + 57231,57255,57280,57304,57329,57353,57377,57402, + 57426,57450,57474,57498,57522,57546,57570,57594, + 57618,57642,57666,57690,57714,57738,57762,57785, + 57809,57833,57856,57880,57903,57927,57950,57974, + 57997,58021,58044,58067,58091,58114,58137,58160, + 58183,58207,58230,58253,58276,58299,58322,58345, + 58367,58390,58413,58436,58459,58481,58504,58527, + 58549,58572,58594,58617,58639,58662,58684,58706, + 58729,58751,58773,58795,58818,58840,58862,58884, + 58906,58928,58950,58972,58994,59016,59038,59059, + 59081,59103,59125,59146,59168,59190,59211,59233, + 59254,59276,59297,59318,59340,59361,59382,59404, + 59425,59446,59467,59488,59509,59530,59551,59572, + 59593,59614,59635,59656,59677,59697,59718,59739, + 59759,59780,59801,59821,59842,59862,59883,59903, + 59923,59944,59964,59984,60004,60025,60045,60065, + 60085,60105,60125,60145,60165,60185,60205,60225, + 60244,60264,60284,60304,60323,60343,60363,60382, + 60402,60421,60441,60460,60479,60499,60518,60537, + 60556,60576,60595,60614,60633,60652,60671,60690, + 60709,60728,60747,60766,60785,60803,60822,60841, + 60859,60878,60897,60915,60934,60952,60971,60989, + 61007,61026,61044,61062,61081,61099,61117,61135, + 61153,61171,61189,61207,61225,61243,61261,61279, + 61297,61314,61332,61350,61367,61385,61403,61420, + 61438,61455,61473,61490,61507,61525,61542,61559, + 61577,61594,61611,61628,61645,61662,61679,61696, + 61713,61730,61747,61764,61780,61797,61814,61831, + 61847,61864,61880,61897,61913,61930,61946,61963, + 61979,61995,62012,62028,62044,62060,62076,62092, + 62108,62125,62141,62156,62172,62188,62204,62220, + 62236,62251,62267,62283,62298,62314,62329,62345, + 62360,62376,62391,62407,62422,62437,62453,62468, + 62483,62498,62513,62528,62543,62558,62573,62588, + 62603,62618,62633,62648,62662,62677,62692,62706, + 62721,62735,62750,62764,62779,62793,62808,62822, + 62836,62850,62865,62879,62893,62907,62921,62935, + 62949,62963,62977,62991,63005,63019,63032,63046, + 63060,63074,63087,63101,63114,63128,63141,63155, + 63168,63182,63195,63208,63221,63235,63248,63261, + 63274,63287,63300,63313,63326,63339,63352,63365, + 63378,63390,63403,63416,63429,63441,63454,63466, + 63479,63491,63504,63516,63528,63541,63553,63565, + 63578,63590,63602,63614,63626,63638,63650,63662, + 63674,63686,63698,63709,63721,63733,63745,63756, + 63768,63779,63791,63803,63814,63825,63837,63848, + 63859,63871,63882,63893,63904,63915,63927,63938, + 63949,63960,63971,63981,63992,64003,64014,64025, + 64035,64046,64057,64067,64078,64088,64099,64109, + 64120,64130,64140,64151,64161,64171,64181,64192, + 64202,64212,64222,64232,64242,64252,64261,64271, + 64281,64291,64301,64310,64320,64330,64339,64349, + 64358,64368,64377,64387,64396,64405,64414,64424, + 64433,64442,64451,64460,64469,64478,64487,64496, + 64505,64514,64523,64532,64540,64549,64558,64566, + 64575,64584,64592,64600,64609,64617,64626,64634, + 64642,64651,64659,64667,64675,64683,64691,64699, + 64707,64715,64723,64731,64739,64747,64754,64762, + 64770,64777,64785,64793,64800,64808,64815,64822, + 64830,64837,64844,64852,64859,64866,64873,64880, + 64887,64895,64902,64908,64915,64922,64929,64936, + 64943,64949,64956,64963,64969,64976,64982,64989, + 64995,65002,65008,65015,65021,65027,65033,65040, + 65046,65052,65058,65064,65070,65076,65082,65088, + 65094,65099,65105,65111,65117,65122,65128,65133, + 65139,65144,65150,65155,65161,65166,65171,65177, + 65182,65187,65192,65197,65202,65207,65212,65217, + 65222,65227,65232,65237,65242,65246,65251,65256, + 65260,65265,65270,65274,65279,65283,65287,65292, + 65296,65300,65305,65309,65313,65317,65321,65325, + 65329,65333,65337,65341,65345,65349,65352,65356, + 65360,65363,65367,65371,65374,65378,65381,65385, + 65388,65391,65395,65398,65401,65404,65408,65411, + 65414,65417,65420,65423,65426,65429,65431,65434, + 65437,65440,65442,65445,65448,65450,65453,65455, + 65458,65460,65463,65465,65467,65470,65472,65474, + 65476,65478,65480,65482,65484,65486,65488,65490, + 65492,65494,65496,65497,65499,65501,65502,65504, + 65505,65507,65508,65510,65511,65513,65514,65515, + 65516,65518,65519,65520,65521,65522,65523,65524, + 65525,65526,65527,65527,65528,65529,65530,65530, + 65531,65531,65532,65532,65533,65533,65534,65534, + 65534,65535,65535,65535,65535,65535,65535,65535 +}; + +const fixed_t *finecosine = &finesine[FINEANGLES/4]; + +const angle_t tantoangle[2049] = +{ + 0,333772,667544,1001315,1335086,1668857,2002626,2336395, + 2670163,3003929,3337694,3671457,4005219,4338979,4672736,5006492, + 5340245,5673995,6007743,6341488,6675230,7008968,7342704,7676435, + 8010164,8343888,8677609,9011325,9345037,9678744,10012447,10346145, + 10679838,11013526,11347209,11680887,12014558,12348225,12681885,13015539, + 13349187,13682829,14016464,14350092,14683714,15017328,15350936,15684536, + 16018129,16351714,16685291,17018860,17352422,17685974,18019518,18353054, + 18686582,19020100,19353610,19687110,20020600,20354080,20687552,21021014, + 21354466,21687906,22021338,22354758,22688168,23021568,23354956,23688332, + 24021698,24355052,24688396,25021726,25355046,25688352,26021648,26354930, + 26688200,27021456,27354702,27687932,28021150,28354356,28687548,29020724, + 29353888,29687038,30020174,30353296,30686404,31019496,31352574,31685636, + 32018684,32351718,32684734,33017736,33350722,33683692,34016648,34349584, + 34682508,35015412,35348300,35681172,36014028,36346868,36679688,37012492, + 37345276,37678044,38010792,38343524,38676240,39008936,39341612,39674272, + 40006912,40339532,40672132,41004716,41337276,41669820,42002344,42334848, + 42667332,42999796,43332236,43664660,43997060,44329444,44661800,44994140, + 45326456,45658752,45991028,46323280,46655512,46987720,47319908,47652072, + 47984212,48316332,48648428,48980500,49312548,49644576,49976580,50308556, + 50640512,50972444,51304352,51636236,51968096,52299928,52631740,52963524, + 53295284,53627020,53958728,54290412,54622068,54953704,55285308,55616888, + 55948444,56279972,56611472,56942948,57274396,57605816,57937212,58268576, + 58599916,58931228,59262512,59593768,59924992,60256192,60587364,60918508, + 61249620,61580704,61911760,62242788,62573788,62904756,63235692,63566604, + 63897480,64228332,64559148,64889940,65220696,65551424,65882120,66212788, + 66543420,66874024,67204600,67535136,67865648,68196120,68526568,68856984, + 69187360,69517712,69848024,70178304,70508560,70838776,71168960,71499112, + 71829224,72159312,72489360,72819376,73149360,73479304,73809216,74139096, + 74468936,74798744,75128520,75458264,75787968,76117632,76447264,76776864, + 77106424,77435952,77765440,78094888,78424304,78753688,79083032,79412336, + 79741608,80070840,80400032,80729192,81058312,81387392,81716432,82045440, + 82374408,82703336,83032224,83361080,83689896,84018664,84347400,84676096, + 85004760,85333376,85661952,85990488,86318984,86647448,86975864,87304240, + 87632576,87960872,88289128,88617344,88945520,89273648,89601736,89929792, + 90257792,90585760,90913688,91241568,91569408,91897200,92224960,92552672, + 92880336,93207968,93535552,93863088,94190584,94518040,94845448,95172816, + 95500136,95827416,96154648,96481832,96808976,97136080,97463136,97790144, + 98117112,98444032,98770904,99097736,99424520,99751256,100077944,100404592, + 100731192,101057744,101384248,101710712,102037128,102363488,102689808,103016080, + 103342312,103668488,103994616,104320696,104646736,104972720,105298656,105624552, + 105950392,106276184,106601928,106927624,107253272,107578872,107904416,108229920, + 108555368,108880768,109206120,109531416,109856664,110181872,110507016,110832120, + 111157168,111482168,111807112,112132008,112456856,112781648,113106392,113431080, + 113755720,114080312,114404848,114729328,115053760,115378136,115702464,116026744, + 116350960,116675128,116999248,117323312,117647320,117971272,118295176,118619024, + 118942816,119266560,119590248,119913880,120237456,120560984,120884456,121207864, + 121531224,121854528,122177784,122500976,122824112,123147200,123470224,123793200, + 124116120,124438976,124761784,125084528,125407224,125729856,126052432,126374960, + 126697424,127019832,127342184,127664472,127986712,128308888,128631008,128953072, + 129275080,129597024,129918912,130240744,130562520,130884232,131205888,131527480, + 131849016,132170496,132491912,132813272,133134576,133455816,133776992,134098120, + 134419184,134740176,135061120,135382000,135702816,136023584,136344272,136664912, + 136985488,137306016,137626464,137946864,138267184,138587456,138907664,139227808, + 139547904,139867920,140187888,140507776,140827616,141147392,141467104,141786752, + 142106336,142425856,142745312,143064720,143384048,143703312,144022512,144341664, + 144660736,144979744,145298704,145617584,145936400,146255168,146573856,146892480, + 147211040,147529536,147847968,148166336,148484640,148802880,149121056,149439152, + 149757200,150075168,150393072,150710912,151028688,151346400,151664048,151981616, + 152299136,152616576,152933952,153251264,153568496,153885680,154202784,154519824, + 154836784,155153696,155470528,155787296,156104000,156420624,156737200,157053696, + 157370112,157686480,158002768,158318976,158635136,158951216,159267232,159583168, + 159899040,160214848,160530592,160846256,161161840,161477376,161792832,162108208, + 162423520,162738768,163053952,163369040,163684080,163999040,164313936,164628752, + 164943504,165258176,165572784,165887312,166201776,166516160,166830480,167144736, + 167458912,167773008,168087040,168400992,168714880,169028688,169342432,169656096, + 169969696,170283216,170596672,170910032,171223344,171536576,171849728,172162800, + 172475808,172788736,173101600,173414384,173727104,174039728,174352288,174664784, + 174977200,175289536,175601792,175913984,176226096,176538144,176850096,177161984, + 177473792,177785536,178097200,178408784,178720288,179031728,179343088,179654368, + 179965568,180276704,180587744,180898720,181209616,181520448,181831184,182141856, + 182452448,182762960,183073408,183383760,183694048,184004240,184314368,184624416, + 184934400,185244288,185554096,185863840,186173504,186483072,186792576,187102000, + 187411344,187720608,188029808,188338912,188647936,188956896,189265760,189574560, + 189883264,190191904,190500448,190808928,191117312,191425632,191733872,192042016, + 192350096,192658096,192966000,193273840,193581584,193889264,194196848,194504352, + 194811792,195119136,195426400,195733584,196040688,196347712,196654656,196961520, + 197268304,197574992,197881616,198188144,198494592,198800960,199107248,199413456, + 199719584,200025616,200331584,200637456,200943248,201248960,201554576,201860128, + 202165584,202470960,202776256,203081456,203386592,203691632,203996592,204301472, + 204606256,204910976,205215600,205520144,205824592,206128960,206433248,206737456, + 207041584,207345616,207649568,207953424,208257216,208560912,208864512,209168048, + 209471488,209774832,210078112,210381296,210684384,210987408,211290336,211593184, + 211895936,212198608,212501184,212803680,213106096,213408432,213710672,214012816, + 214314880,214616864,214918768,215220576,215522288,215823920,216125472,216426928, + 216728304,217029584,217330784,217631904,217932928,218233856,218534704,218835472, + 219136144,219436720,219737216,220037632,220337952,220638192,220938336,221238384, + 221538352,221838240,222138032,222437728,222737344,223036880,223336304,223635664, + 223934912,224234096,224533168,224832160,225131072,225429872,225728608,226027232, + 226325776,226624240,226922608,227220880,227519056,227817152,228115168,228413088, + 228710912,229008640,229306288,229603840,229901312,230198688,230495968,230793152, + 231090256,231387280,231684192,231981024,232277760,232574416,232870960,233167440, + 233463808,233760096,234056288,234352384,234648384,234944304,235240128,235535872, + 235831504,236127056,236422512,236717888,237013152,237308336,237603424,237898416, + 238193328,238488144,238782864,239077488,239372016,239666464,239960816,240255072, + 240549232,240843312,241137280,241431168,241724960,242018656,242312256,242605776, + 242899200,243192512,243485744,243778896,244071936,244364880,244657744,244950496, + 245243168,245535744,245828224,246120608,246412912,246705104,246997216,247289216, + 247581136,247872960,248164688,248456320,248747856,249039296,249330640,249621904, + 249913056,250204128,250495088,250785968,251076736,251367424,251658016,251948512, + 252238912,252529200,252819408,253109520,253399536,253689456,253979280,254269008, + 254558640,254848176,255137632,255426976,255716224,256005376,256294432,256583392, + 256872256,257161024,257449696,257738272,258026752,258315136,258603424,258891600, + 259179696,259467696,259755600,260043392,260331104,260618704,260906224,261193632, + 261480960,261768176,262055296,262342320,262629248,262916080,263202816,263489456, + 263776000,264062432,264348784,264635024,264921168,265207216,265493168,265779024, + 266064784,266350448,266636000,266921472,267206832,267492096,267777264,268062336, + 268347312,268632192,268916960,269201632,269486208,269770688,270055072,270339360, + 270623552,270907616,271191616,271475488,271759296,272042976,272326560,272610048, + 272893440,273176736,273459936,273743040,274026048,274308928,274591744,274874432, + 275157024,275439520,275721920,276004224,276286432,276568512,276850528,277132416, + 277414240,277695936,277977536,278259040,278540448,278821728,279102944,279384032, + 279665056,279945952,280226752,280507456,280788064,281068544,281348960,281629248, + 281909472,282189568,282469568,282749440,283029248,283308960,283588544,283868032, + 284147424,284426720,284705920,284985024,285264000,285542912,285821696,286100384, + 286378976,286657440,286935840,287214112,287492320,287770400,288048384,288326240, + 288604032,288881696,289159264,289436768,289714112,289991392,290268576,290545632, + 290822592,291099456,291376224,291652896,291929440,292205888,292482272,292758528, + 293034656,293310720,293586656,293862496,294138240,294413888,294689440,294964864, + 295240192,295515424,295790560,296065600,296340512,296615360,296890080,297164704, + 297439200,297713632,297987936,298262144,298536256,298810240,299084160,299357952, + 299631648,299905248,300178720,300452128,300725408,300998592,301271680,301544640, + 301817536,302090304,302362976,302635520,302908000,303180352,303452608,303724768, + 303996800,304268768,304540608,304812320,305083968,305355520,305626944,305898272, + 306169472,306440608,306711616,306982528,307253344,307524064,307794656,308065152, + 308335552,308605856,308876032,309146112,309416096,309685984,309955744,310225408, + 310494976,310764448,311033824,311303072,311572224,311841280,312110208,312379040, + 312647776,312916416,313184960,313453376,313721696,313989920,314258016,314526016, + 314793920,315061728,315329408,315597024,315864512,316131872,316399168,316666336, + 316933408,317200384,317467232,317733984,318000640,318267200,318533632,318799968, + 319066208,319332352,319598368,319864288,320130112,320395808,320661408,320926912, + 321192320,321457632,321722816,321987904,322252864,322517760,322782528,323047200, + 323311744,323576192,323840544,324104800,324368928,324632992,324896928,325160736, + 325424448,325688096,325951584,326215008,326478304,326741504,327004608,327267584, + 327530464,327793248,328055904,328318496,328580960,328843296,329105568,329367712, + 329629760,329891680,330153536,330415264,330676864,330938400,331199808,331461120, + 331722304,331983392,332244384,332505280,332766048,333026752,333287296,333547776, + 333808128,334068384,334328544,334588576,334848512,335108352,335368064,335627712, + 335887200,336146624,336405920,336665120,336924224,337183200,337442112,337700864, + 337959552,338218112,338476576,338734944,338993184,339251328,339509376,339767296, + 340025120,340282848,340540480,340797984,341055392,341312704,341569888,341826976, + 342083968,342340832,342597600,342854272,343110848,343367296,343623648,343879904, + 344136032,344392064,344648000,344903808,345159520,345415136,345670656,345926048, + 346181344,346436512,346691616,346946592,347201440,347456224,347710880,347965440, + 348219872,348474208,348728448,348982592,349236608,349490528,349744320,349998048, + 350251648,350505152,350758528,351011808,351264992,351518048,351771040,352023872, + 352276640,352529280,352781824,353034272,353286592,353538816,353790944,354042944, + 354294880,354546656,354798368,355049952,355301440,355552800,355804096,356055264, + 356306304,356557280,356808128,357058848,357309504,357560032,357810464,358060768, + 358311008,358561088,358811104,359060992,359310784,359560480,359810048,360059520, + 360308896,360558144,360807296,361056352,361305312,361554144,361802880,362051488, + 362300032,362548448,362796736,363044960,363293056,363541024,363788928,364036704, + 364284384,364531936,364779392,365026752,365274016,365521152,365768192,366015136, + 366261952,366508672,366755296,367001792,367248192,367494496,367740704,367986784, + 368232768,368478656,368724416,368970080,369215648,369461088,369706432,369951680, + 370196800,370441824,370686752,370931584,371176288,371420896,371665408,371909792, + 372154080,372398272,372642336,372886304,373130176,373373952,373617600,373861152, + 374104608,374347936,374591168,374834304,375077312,375320224,375563040,375805760, + 376048352,376290848,376533248,376775520,377017696,377259776,377501728,377743584, + 377985344,378227008,378468544,378709984,378951328,379192544,379433664,379674688, + 379915584,380156416,380397088,380637696,380878176,381118560,381358848,381599040, + 381839104,382079072,382318912,382558656,382798304,383037856,383277280,383516640, + 383755840,383994976,384233984,384472896,384711712,384950400,385188992,385427488, + 385665888,385904160,386142336,386380384,386618368,386856224,387093984,387331616, + 387569152,387806592,388043936,388281152,388518272,388755296,388992224,389229024, + 389465728,389702336,389938816,390175200,390411488,390647680,390883744,391119712, + 391355584,391591328,391826976,392062528,392297984,392533312,392768544,393003680, + 393238720,393473632,393708448,393943168,394177760,394412256,394646656,394880960, + 395115136,395349216,395583200,395817088,396050848,396284512,396518080,396751520, + 396984864,397218112,397451264,397684288,397917248,398150080,398382784,398615424, + 398847936,399080320,399312640,399544832,399776928,400008928,400240832,400472608, + 400704288,400935872,401167328,401398720,401629984,401861120,402092192,402323136, + 402553984,402784736,403015360,403245888,403476320,403706656,403936896,404167008, + 404397024,404626944,404856736,405086432,405316032,405545536,405774912,406004224, + 406233408,406462464,406691456,406920320,407149088,407377760,407606336,407834784, + 408063136,408291392,408519520,408747584,408975520,409203360,409431072,409658720, + 409886240,410113664,410340992,410568192,410795296,411022304,411249216,411476032, + 411702720,411929312,412155808,412382176,412608480,412834656,413060736,413286720, + 413512576,413738336,413964000,414189568,414415040,414640384,414865632,415090784, + 415315840,415540800,415765632,415990368,416215008,416439552,416663968,416888288, + 417112512,417336640,417560672,417784576,418008384,418232096,418455712,418679200, + 418902624,419125920,419349120,419572192,419795200,420018080,420240864,420463552, + 420686144,420908608,421130976,421353280,421575424,421797504,422019488,422241344, + 422463104,422684768,422906336,423127776,423349120,423570400,423791520,424012576, + 424233536,424454368,424675104,424895744,425116288,425336736,425557056,425777280, + 425997408,426217440,426437376,426657184,426876928,427096544,427316064,427535488, + 427754784,427974016,428193120,428412128,428631040,428849856,429068544,429287168, + 429505664,429724064,429942368,430160576,430378656,430596672,430814560,431032352, + 431250048,431467616,431685120,431902496,432119808,432336992,432554080,432771040, + 432987936,433204736,433421408,433637984,433854464,434070848,434287104,434503296, + 434719360,434935360,435151232,435367008,435582656,435798240,436013696,436229088, + 436444352,436659520,436874592,437089568,437304416,437519200,437733856,437948416, + 438162880,438377248,438591520,438805696,439019744,439233728,439447584,439661344, + 439875008,440088576,440302048,440515392,440728672,440941824,441154880,441367872, + 441580736,441793472,442006144,442218720,442431168,442643552,442855808,443067968, + 443280032,443492000,443703872,443915648,444127296,444338880,444550336,444761696, + 444972992,445184160,445395232,445606176,445817056,446027840,446238496,446449088, + 446659552,446869920,447080192,447290400,447500448,447710432,447920320,448130112, + 448339776,448549376,448758848,448968224,449177536,449386720,449595808,449804800, + 450013664,450222464,450431168,450639776,450848256,451056640,451264960,451473152, + 451681248,451889248,452097152,452304960,452512672,452720288,452927808,453135232, + 453342528,453549760,453756864,453963904,454170816,454377632,454584384,454791008, + 454997536,455203968,455410304,455616544,455822688,456028704,456234656,456440512, + 456646240,456851904,457057472,457262912,457468256,457673536,457878688,458083744, + 458288736,458493600,458698368,458903040,459107616,459312096,459516480,459720768, + 459924960,460129056,460333056,460536960,460740736,460944448,461148064,461351584, + 461554976,461758304,461961536,462164640,462367680,462570592,462773440,462976160, + 463178816,463381344,463583776,463786144,463988384,464190560,464392608,464594560, + 464796448,464998208,465199872,465401472,465602944,465804320,466005600,466206816, + 466407904,466608896,466809824,467010624,467211328,467411936,467612480,467812896, + 468013216,468213440,468413600,468613632,468813568,469013440,469213184,469412832, + 469612416,469811872,470011232,470210528,470409696,470608800,470807776,471006688, + 471205472,471404192,471602784,471801312,471999712,472198048,472396288,472594400, + 472792448,472990400,473188256,473385984,473583648,473781216,473978688,474176064, + 474373344,474570528,474767616,474964608,475161504,475358336,475555040,475751648, + 475948192,476144608,476340928,476537184,476733312,476929376,477125344,477321184, + 477516960,477712640,477908224,478103712,478299104,478494400,478689600,478884704, + 479079744,479274656,479469504,479664224,479858880,480053408,480247872,480442240, + 480636512,480830656,481024736,481218752,481412640,481606432,481800128,481993760, + 482187264,482380704,482574016,482767264,482960416,483153472,483346432,483539296, + 483732064,483924768,484117344,484309856,484502240,484694560,484886784,485078912, + 485270944,485462880,485654720,485846464,486038144,486229696,486421184,486612576, + 486803840,486995040,487186176,487377184,487568096,487758912,487949664,488140320, + 488330880,488521312,488711712,488901984,489092160,489282240,489472256,489662176, + 489851968,490041696,490231328,490420896,490610336,490799712,490988960,491178144, + 491367232,491556224,491745120,491933920,492122656,492311264,492499808,492688256, + 492876608,493064864,493253056,493441120,493629120,493817024,494004832,494192544, + 494380160,494567712,494755136,494942496,495129760,495316928,495504000,495691008, + 495877888,496064704,496251424,496438048,496624608,496811040,496997408,497183680, + 497369856,497555936,497741920,497927840,498113632,498299360,498484992,498670560, + 498856000,499041376,499226656,499411840,499596928,499781920,499966848,500151680, + 500336416,500521056,500705600,500890080,501074464,501258752,501442944,501627040, + 501811072,501995008,502178848,502362592,502546240,502729824,502913312,503096704, + 503280000,503463232,503646368,503829408,504012352,504195200,504377984,504560672, + 504743264,504925760,505108192,505290496,505472736,505654912,505836960,506018944, + 506200832,506382624,506564320,506745952,506927488,507108928,507290272,507471552, + 507652736,507833824,508014816,508195744,508376576,508557312,508737952,508918528, + 509099008,509279392,509459680,509639904,509820032,510000064,510180000,510359872, + 510539648,510719328,510898944,511078432,511257856,511437216,511616448,511795616, + 511974688,512153664,512332576,512511392,512690112,512868768,513047296,513225792, + 513404160,513582432,513760640,513938784,514116800,514294752,514472608,514650368, + 514828064,515005664,515183168,515360608,515537952,515715200,515892352,516069440, + 516246432,516423328,516600160,516776896,516953536,517130112,517306592,517482976, + 517659264,517835488,518011616,518187680,518363648,518539520,518715296,518891008, + 519066624,519242144,519417600,519592960,519768256,519943424,520118528,520293568, + 520468480,520643328,520818112,520992800,521167392,521341888,521516320,521690656, + 521864896,522039072,522213152,522387168,522561056,522734912,522908640,523082304, + 523255872,523429376,523602784,523776096,523949312,524122464,524295552,524468512, + 524641440,524814240,524986976,525159616,525332192,525504640,525677056,525849344, + 526021568,526193728,526365792,526537760,526709632,526881440,527053152,527224800, + 527396352,527567840,527739200,527910528,528081728,528252864,528423936,528594880, + 528765760,528936576,529107296,529277920,529448480,529618944,529789344,529959648, + 530129856,530300000,530470048,530640000,530809888,530979712,531149440,531319072, + 531488608,531658080,531827488,531996800,532166016,532335168,532504224,532673184, + 532842080,533010912,533179616,533348288,533516832,533685312,533853728,534022048, + 534190272,534358432,534526496,534694496,534862400,535030240,535197984,535365632, + 535533216,535700704,535868128,536035456,536202720,536369888,536536992,536704000, + 536870912 +}; + +// Now where did these came from? +const byte gammatable[5][256] = +{ + { + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16, + 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32, + 33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, + 49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64, + 65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80, + 81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96, + 97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112, + 113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 + }, + + { + 2,4,5,7,8,10,11,12,14,15,16,18,19,20,21,23, + 24,25,26,27,29,30,31,32,33,34,36,37,38,39,40,41, + 42,44,45,46,47,48,49,50,51,52,54,55,56,57,58,59, + 60,61,62,63,64,65,66,67,69,70,71,72,73,74,75,76, + 77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92, + 93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108, + 109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124, + 125,126,127,128,129,129,130,131,132,133,134,135,136,137,138,139, + 140,141,142,143,144,145,146,147,148,148,149,150,151,152,153,154, + 155,156,157,158,159,160,161,162,163,163,164,165,166,167,168,169, + 170,171,172,173,174,175,175,176,177,178,179,180,181,182,183,184, + 185,186,186,187,188,189,190,191,192,193,194,195,196,196,197,198, + 199,200,201,202,203,204,205,205,206,207,208,209,210,211,212,213, + 214,214,215,216,217,218,219,220,221,222,222,223,224,225,226,227, + 228,229,230,230,231,232,233,234,235,236,237,237,238,239,240,241, + 242,243,244,245,245,246,247,248,249,250,251,252,252,253,254,255 + }, + + { + 4,7,9,11,13,15,17,19,21,22,24,26,27,29,30,32, + 33,35,36,38,39,40,42,43,45,46,47,48,50,51,52,54, + 55,56,57,59,60,61,62,63,65,66,67,68,69,70,72,73, + 74,75,76,77,78,79,80,82,83,84,85,86,87,88,89,90, + 91,92,93,94,95,96,97,98,100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,114,115,116,117,118,119,120,121,122, + 123,124,125,126,127,128,129,130,131,132,133,133,134,135,136,137, + 138,139,140,141,142,143,144,144,145,146,147,148,149,150,151,152, + 153,153,154,155,156,157,158,159,160,160,161,162,163,164,165,166, + 166,167,168,169,170,171,172,172,173,174,175,176,177,178,178,179, + 180,181,182,183,183,184,185,186,187,188,188,189,190,191,192,193, + 193,194,195,196,197,197,198,199,200,201,201,202,203,204,205,206, + 206,207,208,209,210,210,211,212,213,213,214,215,216,217,217,218, + 219,220,221,221,222,223,224,224,225,226,227,228,228,229,230,231, + 231,232,233,234,235,235,236,237,238,238,239,240,241,241,242,243, + 244,244,245,246,247,247,248,249,250,251,251,252,253,254,254,255 + }, + + { + 8,12,16,19,22,24,27,29,31,34,36,38,40,41,43,45, + 47,49,50,52,53,55,57,58,60,61,63,64,65,67,68,70, + 71,72,74,75,76,77,79,80,81,82,84,85,86,87,88,90, + 91,92,93,94,95,96,98,99,100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123, + 124,125,126,127,128,129,130,131,132,133,134,135,135,136,137,138, + 139,140,141,142,143,143,144,145,146,147,148,149,150,150,151,152, + 153,154,155,155,156,157,158,159,160,160,161,162,163,164,165,165, + 166,167,168,169,169,170,171,172,173,173,174,175,176,176,177,178, + 179,180,180,181,182,183,183,184,185,186,186,187,188,189,189,190, + 191,192,192,193,194,195,195,196,197,197,198,199,200,200,201,202, + 202,203,204,205,205,206,207,207,208,209,210,210,211,212,212,213, + 214,214,215,216,216,217,218,219,219,220,221,221,222,223,223,224, + 225,225,226,227,227,228,229,229,230,231,231,232,233,233,234,235, + 235,236,237,237,238,238,239,240,240,241,242,242,243,244,244,245, + 246,246,247,247,248,249,249,250,251,251,252,253,253,254,254,255 + }, + + + { + 16,23,28,32,36,39,42,45,48,50,53,55,57,60,62,64, + 66,68,69,71,73,75,76,78,80,81,83,84,86,87,89,90, + 92,93,94,96,97,98,100,101,102,103,105,106,107,108,109,110, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,128, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 143,144,145,146,147,148,149,150,150,151,152,153,154,155,155,156, + 157,158,159,159,160,161,162,163,163,164,165,166,166,167,168,169, + 169,170,171,172,172,173,174,175,175,176,177,177,178,179,180,180, + 181,182,182,183,184,184,185,186,187,187,188,189,189,190,191,191, + 192,193,193,194,195,195,196,196,197,198,198,199,200,200,201,202, + 202,203,203,204,205,205,206,207,207,208,208,209,210,210,211,211, + 212,213,213,214,214,215,216,216,217,217,218,219,219,220,220,221, + 221,222,223,223,224,224,225,225,226,227,227,228,228,229,229,230, + 230,231,232,232,233,233,234,234,235,235,236,236,237,237,238,239, + 239,240,240,241,241,242,242,243,243,244,244,245,245,246,246,247, + 247,248,248,249,249,250,250,251,251,252,252,253,254,254,255,255 + } +}; + diff --git a/client/src/tables.h b/client/src/tables.h new file mode 100644 index 0000000..495fd53 --- /dev/null +++ b/client/src/tables.h @@ -0,0 +1,96 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Lookup tables. +// Do not try to look them up :-). +// In the order of appearance: +// +// int finetangent[4096] - Tangens LUT. +// Should work with BAM fairly well (12 of 16bit, +// effectively, by shifting). +// +// int finesine[10240] - Sine lookup. +// Guess what, serves as cosine, too. +// Remarkable thing is, how to use BAMs with this? +// +// int tantoangle[2049] - ArcTan LUT, +// maps tan(angle) to angle fast. Gotta search. +// + + +#ifndef __TABLES__ +#define __TABLES__ + +#include "doomtype.h" + +#include "m_fixed.h" + +#define FINEANGLES 8192 +#define FINEMASK (FINEANGLES-1) + + +// 0x100000000 to 0x2000 +#define ANGLETOFINESHIFT 19 + +// Effective size is 10240. +extern const fixed_t finesine[5*FINEANGLES/4]; + +// Re-use data, is just PI/2 pahse shift. +extern const fixed_t *finecosine; + + +// Effective size is 4096. +extern const fixed_t finetangent[FINEANGLES/2]; + +// Gamma correction tables. +extern const byte gammatable[5][256]; + +// Binary Angle Measument, BAM. + +#define ANG45 0x20000000 +#define ANG90 0x40000000 +#define ANG180 0x80000000 +#define ANG270 0xc0000000 +#define ANG_MAX 0xffffffff + +#define ANG1 (ANG45 / 45) +#define ANG60 (ANG180 / 3) + +// Heretic code uses this definition as though it represents one +// degree, but it is not! This is actually ~1.40 degrees. + +#define ANG1_X 0x01000000 + +#define SLOPERANGE 2048 +#define SLOPEBITS 11 +#define DBITS (FRACBITS-SLOPEBITS) + +typedef unsigned angle_t; + + +// Effective size is 2049; +// The +1 size is to handle the case when x==y +// without additional checking. +extern const angle_t tantoangle[SLOPERANGE+1]; + + +// Utility function, +// called by R_PointToAngle. +int SlopeDiv(unsigned int num, unsigned int den); + + +#endif + diff --git a/client/src/v_patch.h b/client/src/v_patch.h new file mode 100644 index 0000000..767f9e3 --- /dev/null +++ b/client/src/v_patch.h @@ -0,0 +1,50 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Refresh/rendering module, shared data struct definitions. +// + + +#ifndef V_PATCH_H +#define V_PATCH_H + +// Patches. +// A patch holds one or more columns. +// Patches are used for sprites and all masked pictures, +// and we compose textures from the TEXTURE1/2 lists +// of patches. + +typedef PACKED_STRUCT ( +{ + short width; // bounding box size + short height; + short leftoffset; // pixels to the left of origin + short topoffset; // pixels below the origin + int columnofs[8]; // only [width] used + // the [0] is &columnofs[width] +}) patch_t; + +// posts are runs of non masked source pixels +typedef PACKED_STRUCT ( +{ + byte topdelta; // -1 is the last post in a column + byte length; // length data bytes follows +}) post_t; + +// column_t is a list of 0 or more post_t, (byte)-1 terminated +typedef post_t column_t; + +#endif + diff --git a/client/src/v_video.c b/client/src/v_video.c new file mode 100644 index 0000000..13121c4 --- /dev/null +++ b/client/src/v_video.c @@ -0,0 +1,980 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Gamma correction LUT stuff. +// Functions to draw patches (by post) directly to screen. +// Functions to blit a block to the screen. +// + +#include +#include +#include +#include +#include +#include + +#include "doomtype.h" +#include "i_input.h" +#include "i_swap.h" +#include "i_system.h" +#include "i_video.h" +#include "m_bbox.h" +#include "m_misc.h" +#include "pngconf.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +// Blending table used for fuzzpatch, etc. +// Only used in Heretic/Hexen + +byte *tinttable = NULL; + +// villsa [STRIFE] Blending table used for Strife +byte *xlatab = NULL; + +// The screen buffer that the v_video.c code draws to. + +static pixel_t *dest_screen = NULL; + +int dirtybox[4]; + +// haleyjd 08/28/10: clipping callback function for patches. +// This is needed for Chocolate Strife, which clips patches to the screen. +static vpatchclipfunc_t patchclip_callback = NULL; + +// +// V_MarkRect +// +void V_MarkRect(int x, int y, int width, int height) +{ + // If we are temporarily using an alternate screen, do not + // affect the update box. + + if (dest_screen == I_VideoBuffer) + { + M_AddToBox (dirtybox, x, y); + M_AddToBox (dirtybox, x + width-1, y + height-1); + } +} + + +// +// V_CopyRect +// +void V_CopyRect(int srcx, int srcy, pixel_t *source, + int width, int height, + int destx, int desty) +{ + pixel_t *src; + pixel_t *dest; + + if (srcx < 0 + || srcx + width > SCREENWIDTH + || srcy < 0 + || srcy + height > SCREENHEIGHT + || destx < 0 + || destx + width > SCREENWIDTH + || desty < 0 + || desty + height > SCREENHEIGHT) + { + I_Error ("Bad V_CopyRect"); + } + + V_MarkRect(destx, desty, width, height); + + src = source + SCREENWIDTH * srcy + srcx; + dest = dest_screen + SCREENWIDTH * desty + destx; + + for ( ; height>0 ; height--) + { + memcpy(dest, src, width * sizeof(*dest)); + src += SCREENWIDTH; + dest += SCREENWIDTH; + } +} + +// +// V_SetPatchClipCallback +// +// haleyjd 08/28/10: Added for Strife support. +// By calling this function, you can setup runtime error checking for patch +// clipping. Strife never caused errors by drawing patches partway off-screen. +// Some versions of vanilla DOOM also behaved differently than the default +// implementation, so this could possibly be extended to those as well for +// accurate emulation. +// +void V_SetPatchClipCallback(vpatchclipfunc_t func) +{ + patchclip_callback = func; +} + +// +// V_DrawPatch +// Masks a column based masked pic to the screen. +// + +void V_DrawPatch(int x, int y, patch_t *patch) +{ + int count; + int col; + column_t *column; + pixel_t *desttop; + pixel_t *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + // haleyjd 08/28/10: Strife needs silent error checking here. + if(patchclip_callback) + { + if(!patchclip_callback(patch, x, y)) + return; + } + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawPatch"); + } + + V_MarkRect(x, y, SHORT(patch->width), SHORT(patch->height)); + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + + for ( ; colcolumnofs[col])); + + // step through the posts in a column + while (column->topdelta != 0xff) + { + source = (byte *)column + 3; + dest = desttop + column->topdelta*SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = *source++; + dest += SCREENWIDTH; + } + column = (column_t *)((byte *)column + column->length + 4); + } + } +} + +// +// V_DrawPatchFlipped +// Masks a column based masked pic to the screen. +// Flips horizontally, e.g. to mirror face. +// + +void V_DrawPatchFlipped(int x, int y, patch_t *patch) +{ + int count; + int col; + column_t *column; + pixel_t *desttop; + pixel_t *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + // haleyjd 08/28/10: Strife needs silent error checking here. + if(patchclip_callback) + { + if(!patchclip_callback(patch, x, y)) + return; + } + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawPatchFlipped"); + } + + V_MarkRect (x, y, SHORT(patch->width), SHORT(patch->height)); + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + + for ( ; colcolumnofs[w-1-col])); + + // step through the posts in a column + while (column->topdelta != 0xff ) + { + source = (byte *)column + 3; + dest = desttop + column->topdelta*SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = *source++; + dest += SCREENWIDTH; + } + column = (column_t *)((byte *)column + column->length + 4); + } + } +} + + + +// +// V_DrawPatchDirect +// Draws directly to the screen on the pc. +// + +void V_DrawPatchDirect(int x, int y, patch_t *patch) +{ + V_DrawPatch(x, y, patch); +} + +// +// V_DrawTLPatch +// +// Masks a column based translucent masked pic to the screen. +// + +void V_DrawTLPatch(int x, int y, patch_t * patch) +{ + int count, col; + column_t *column; + pixel_t *desttop, *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawTLPatch"); + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + for (; col < w; x++, col++, desttop++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while (column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = tinttable[((*dest) << 8) + *source++]; + dest += SCREENWIDTH; + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// V_DrawXlaPatch +// +// villsa [STRIFE] Masks a column based translucent masked pic to the screen. +// + +void V_DrawXlaPatch(int x, int y, patch_t * patch) +{ + int count, col; + column_t *column; + pixel_t *desttop, *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if(patchclip_callback) + { + if(!patchclip_callback(patch, x, y)) + return; + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + for(; col < w; x++, col++, desttop++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while(column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + count = column->length; + + while(count--) + { + *dest = xlatab[*dest + ((*source) << 8)]; + source++; + dest += SCREENWIDTH; + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// V_DrawAltTLPatch +// +// Masks a column based translucent masked pic to the screen. +// + +void V_DrawAltTLPatch(int x, int y, patch_t * patch) +{ + int count, col; + column_t *column; + pixel_t *desttop, *dest; + byte *source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawAltTLPatch"); + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + + w = SHORT(patch->width); + for (; col < w; x++, col++, desttop++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while (column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest = tinttable[((*dest) << 8) + *source++]; + dest += SCREENWIDTH; + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// V_DrawShadowedPatch +// +// Masks a column based masked pic to the screen. +// + +void V_DrawShadowedPatch(int x, int y, patch_t *patch) +{ + int count, col; + column_t *column; + pixel_t *desttop, *dest; + byte *source; + pixel_t *desttop2, *dest2; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + + if (x < 0 + || x + SHORT(patch->width) > SCREENWIDTH + || y < 0 + || y + SHORT(patch->height) > SCREENHEIGHT) + { + I_Error("Bad V_DrawShadowedPatch"); + } + + col = 0; + desttop = dest_screen + y * SCREENWIDTH + x; + desttop2 = dest_screen + (y + 2) * SCREENWIDTH + x + 2; + + w = SHORT(patch->width); + for (; col < w; x++, col++, desttop++, desttop2++) + { + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + + // step through the posts in a column + + while (column->topdelta != 0xff) + { + source = (byte *) column + 3; + dest = desttop + column->topdelta * SCREENWIDTH; + dest2 = desttop2 + column->topdelta * SCREENWIDTH; + count = column->length; + + while (count--) + { + *dest2 = tinttable[((*dest2) << 8)]; + dest2 += SCREENWIDTH; + *dest = *source++; + dest += SCREENWIDTH; + + } + column = (column_t *) ((byte *) column + column->length + 4); + } + } +} + +// +// Load tint table from TINTTAB lump. +// + +void V_LoadTintTable(void) +{ + tinttable = W_CacheLumpName("TINTTAB", PU_STATIC); +} + +// +// V_LoadXlaTable +// +// villsa [STRIFE] Load xla table from XLATAB lump. +// + +void V_LoadXlaTable(void) +{ + xlatab = W_CacheLumpName("XLATAB", PU_STATIC); +} + +// +// V_DrawBlock +// Draw a linear block of pixels into the view buffer. +// + +void V_DrawBlock(int x, int y, int width, int height, pixel_t *src) +{ + pixel_t *dest; + + if (x < 0 + || x + width >SCREENWIDTH + || y < 0 + || y + height > SCREENHEIGHT) + { + I_Error ("Bad V_DrawBlock"); + } + + V_MarkRect (x, y, width, height); + + dest = dest_screen + y * SCREENWIDTH + x; + + while (height--) + { + memcpy (dest, src, width * sizeof(*dest)); + src += width; + dest += SCREENWIDTH; + } +} + +void V_DrawFilledBox(int x, int y, int w, int h, int c) +{ + uint8_t *buf, *buf1; + int x1, y1; + + buf = I_VideoBuffer + SCREENWIDTH * y + x; + + for (y1 = 0; y1 < h; ++y1) + { + buf1 = buf; + + for (x1 = 0; x1 < w; ++x1) + { + *buf1++ = c; + } + + buf += SCREENWIDTH; + } +} + +void V_DrawHorizLine(int x, int y, int w, int c) +{ + uint8_t *buf; + int x1; + + buf = I_VideoBuffer + SCREENWIDTH * y + x; + + for (x1 = 0; x1 < w; ++x1) + { + *buf++ = c; + } +} + +void V_DrawVertLine(int x, int y, int h, int c) +{ + uint8_t *buf; + int y1; + + buf = I_VideoBuffer + SCREENWIDTH * y + x; + + for (y1 = 0; y1 < h; ++y1) + { + *buf = c; + buf += SCREENWIDTH; + } +} + +void V_DrawBox(int x, int y, int w, int h, int c) +{ + V_DrawHorizLine(x, y, w, c); + V_DrawHorizLine(x, y+h-1, w, c); + V_DrawVertLine(x, y, h, c); + V_DrawVertLine(x+w-1, y, h, c); +} + +// +// Draw a "raw" screen (lump containing raw data to blit directly +// to the screen) +// + +void V_DrawRawScreen(byte *raw) +{ + memcpy(dest_screen, raw, SCREENWIDTH * SCREENHEIGHT); +} + +// +// V_Init +// +void V_Init (void) +{ + // no-op! + // There used to be separate screens that could be drawn to; these are + // now handled in the upper layers. +} + +// Set the buffer that the code draws to. + +void V_UseBuffer(pixel_t *buffer) +{ + dest_screen = buffer; +} + +// Restore screen buffer to the i_video screen buffer. + +void V_RestoreBuffer(void) +{ + dest_screen = I_VideoBuffer; +} + +// +// SCREEN SHOTS +// + +typedef PACKED_STRUCT ( +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + + unsigned short xmin; + unsigned short ymin; + unsigned short xmax; + unsigned short ymax; + + unsigned short hres; + unsigned short vres; + + unsigned char palette[48]; + + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + + char filler[58]; + unsigned char data; // unbounded +}) pcx_t; + + +// +// WritePCXfile +// + +void WritePCXfile(char *filename, byte *data, + int width, int height, + byte *palette) +{ + int i; + int length; + pcx_t* pcx; + byte* pack; + + pcx = Z_Malloc (width*height*2+1000, PU_STATIC, NULL); + + pcx->manufacturer = 0x0a; // PCX id + pcx->version = 5; // 256 color + pcx->encoding = 1; // uncompressed + pcx->bits_per_pixel = 8; // 256 color + pcx->xmin = 0; + pcx->ymin = 0; + pcx->xmax = SHORT(width-1); + pcx->ymax = SHORT(height-1); + pcx->hres = SHORT(width); + pcx->vres = SHORT(height); + memset (pcx->palette,0,sizeof(pcx->palette)); + pcx->reserved = 0; // PCX spec: reserved byte must be zero + pcx->color_planes = 1; // chunky image + pcx->bytes_per_line = SHORT(width); + pcx->palette_type = SHORT(2); // not a grey scale + memset (pcx->filler,0,sizeof(pcx->filler)); + + // pack the image + pack = &pcx->data; + + for (i=0 ; i 0 && fabs(mouse_acceleration - 1) > 0.01) + { + draw_acceleration = true; + } + + // Calculate box position + + box_x = SCREENWIDTH - MOUSE_SPEED_BOX_WIDTH - 10; + box_y = 15; + + V_DrawFilledBox(box_x, box_y, + MOUSE_SPEED_BOX_WIDTH, MOUSE_SPEED_BOX_HEIGHT, bgcolor); + V_DrawBox(box_x, box_y, + MOUSE_SPEED_BOX_WIDTH, MOUSE_SPEED_BOX_HEIGHT, bordercolor); + + // Calculate the position of the red threshold line when calibrating + // acceleration. This is 1/3 of the way along the box. + + redline_x = MOUSE_SPEED_BOX_WIDTH / 3; + + // Calculate line length + + if (draw_acceleration && speed >= mouse_threshold) + { + // Undo acceleration and get back the original mouse speed + original_speed = speed - mouse_threshold; + original_speed = (int) (original_speed / mouse_acceleration); + original_speed += mouse_threshold; + + linelen = (original_speed * redline_x) / mouse_threshold; + } + else + { + linelen = speed / linelen_multiplier; + } + + // Draw horizontal "thermometer" + + if (linelen > MOUSE_SPEED_BOX_WIDTH - 1) + { + linelen = MOUSE_SPEED_BOX_WIDTH - 1; + if (!draw_acceleration) + { + linelen_multiplier++; + } + } + + V_DrawHorizLine(box_x + 1, box_y + 4, MOUSE_SPEED_BOX_WIDTH - 2, black); + + if (!draw_acceleration || linelen < redline_x) + { + V_DrawHorizLine(box_x + 1, box_y + MOUSE_SPEED_BOX_HEIGHT / 2, + linelen, white); + } + else + { + V_DrawHorizLine(box_x + 1, box_y + MOUSE_SPEED_BOX_HEIGHT / 2, + redline_x, white); + V_DrawHorizLine(box_x + redline_x, box_y + MOUSE_SPEED_BOX_HEIGHT / 2, + linelen - redline_x, yellow); + } + + if (draw_acceleration) + { + // Draw acceleration threshold line + V_DrawVertLine(box_x + redline_x, box_y + 1, + MOUSE_SPEED_BOX_HEIGHT - 2, red); + } + else + { + // Draw multiplier lines to indicate current resolution + for (i = 1; i < linelen_multiplier; i++) + { + V_DrawVertLine( + box_x + (i * MOUSE_SPEED_BOX_WIDTH / linelen_multiplier), + box_y + 1, MOUSE_SPEED_BOX_HEIGHT - 2, yellow); + } + } +} + diff --git a/client/src/v_video.h b/client/src/v_video.h new file mode 100644 index 0000000..7b98c35 --- /dev/null +++ b/client/src/v_video.h @@ -0,0 +1,108 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Gamma correction LUT. +// Functions to draw patches (by post) directly to screen. +// Functions to blit a block to the screen. +// + + +#ifndef __V_VIDEO__ +#define __V_VIDEO__ + +#include "doomtype.h" +#include "i_video.h" +// Needed because we are refering to patches. +#include "v_patch.h" + +// +// VIDEO +// + +#define CENTERY (SCREENHEIGHT/2) + + +extern int dirtybox[4]; + +extern byte *tinttable; + +// haleyjd 08/28/10: implemented for Strife support +// haleyjd 08/28/10: Patch clipping callback, implemented to support Choco +// Strife. +typedef boolean (*vpatchclipfunc_t)(patch_t *, int, int); +void V_SetPatchClipCallback(vpatchclipfunc_t func); + + +// Allocates buffer screens, call before R_Init. +void V_Init (void); + +// Draw a block from the specified source screen to the screen. + +void V_CopyRect(int srcx, int srcy, pixel_t *source, + int width, int height, + int destx, int desty); + +void V_DrawPatch(int x, int y, patch_t *patch); +void V_DrawPatchFlipped(int x, int y, patch_t *patch); +void V_DrawTLPatch(int x, int y, patch_t *patch); +void V_DrawAltTLPatch(int x, int y, patch_t * patch); +void V_DrawShadowedPatch(int x, int y, patch_t *patch); +void V_DrawXlaPatch(int x, int y, patch_t * patch); // villsa [STRIFE] +void V_DrawPatchDirect(int x, int y, patch_t *patch); + +// Draw a linear block of pixels into the view buffer. + +void V_DrawBlock(int x, int y, int width, int height, pixel_t *src); + +void V_MarkRect(int x, int y, int width, int height); + +void V_DrawFilledBox(int x, int y, int w, int h, int c); +void V_DrawHorizLine(int x, int y, int w, int c); +void V_DrawVertLine(int x, int y, int h, int c); +void V_DrawBox(int x, int y, int w, int h, int c); + +// Draw a raw screen lump + +void V_DrawRawScreen(byte *raw); + +// Temporarily switch to using a different buffer to draw graphics, etc. + +void V_UseBuffer(pixel_t *buffer); + +// Return to using the normal screen buffer to draw graphics. + +void V_RestoreBuffer(void); + +// Save a screenshot of the current screen to a file, named in the +// format described in the string passed to the function, eg. +// "DOOM%02i.pcx" + +void V_ScreenShot(char *format); + +// Load the lookup table for translucency calculations from the TINTTAB +// lump. + +void V_LoadTintTable(void); + +// villsa [STRIFE] +// Load the lookup table for translucency calculations from the XLATAB +// lump. + +void V_LoadXlaTable(void); + +void V_DrawMouseSpeedBox(int speed); + +#endif + diff --git a/client/src/w_checksum.c b/client/src/w_checksum.c new file mode 100644 index 0000000..141a779 --- /dev/null +++ b/client/src/w_checksum.c @@ -0,0 +1,87 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Generate a checksum of the WAD directory. +// + +#include +#include + +#include "m_misc.h" +#include "sha1.h" +#include "w_checksum.h" +#include "w_file.h" +#include "w_wad.h" + +static wad_file_t **open_wadfiles = NULL; +static int num_open_wadfiles = 0; + +static int GetFileNumber(wad_file_t *handle) +{ + int i; + int result; + + for (i = 0; i < num_open_wadfiles; ++i) + { + if (open_wadfiles[i] == handle) + { + return i; + } + } + + // Not found in list. This is a new file we haven't seen yet. + // Allocate another slot for this file. + + open_wadfiles = realloc(open_wadfiles, + sizeof(wad_file_t *) * (num_open_wadfiles + 1)); + open_wadfiles[num_open_wadfiles] = handle; + + result = num_open_wadfiles; + ++num_open_wadfiles; + + return result; +} + +static void ChecksumAddLump(sha1_context_t *sha1_context, lumpinfo_t *lump) +{ + char buf[9]; + + M_StringCopy(buf, lump->name, sizeof(buf)); + SHA1_UpdateString(sha1_context, buf); + SHA1_UpdateInt32(sha1_context, GetFileNumber(lump->wad_file)); + SHA1_UpdateInt32(sha1_context, lump->position); + SHA1_UpdateInt32(sha1_context, lump->size); +} + +void W_Checksum(sha1_digest_t digest) +{ + sha1_context_t sha1_context; + unsigned int i; + + SHA1_Init(&sha1_context); + + num_open_wadfiles = 0; + + // Go through each entry in the WAD directory, adding information + // about each entry to the SHA1 hash. + + for (i = 0; i < numlumps; ++i) + { + ChecksumAddLump(&sha1_context, lumpinfo[i]); + } + + SHA1_Final(digest, &sha1_context); +} + diff --git a/client/src/w_checksum.h b/client/src/w_checksum.h new file mode 100644 index 0000000..13acc18 --- /dev/null +++ b/client/src/w_checksum.h @@ -0,0 +1,27 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Generate a checksum of the WAD directory. +// + +#ifndef W_CHECKSUM_H +#define W_CHECKSUM_H + +#include "sha1.h" + +extern void W_Checksum(sha1_digest_t digest); + +#endif /* #ifndef W_CHECKSUM_H */ + diff --git a/client/src/w_file.c b/client/src/w_file.c new file mode 100644 index 0000000..b91ee01 --- /dev/null +++ b/client/src/w_file.c @@ -0,0 +1,78 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// WAD I/O functions. +// + +#include + +#include "doomtype.h" +#include "m_argv.h" + +#include "w_file.h" + +extern wad_file_class_t stdc_wad_file; + +extern wad_file_class_t posix_wad_file; + +static wad_file_class_t *wad_file_classes[] = +{ + &posix_wad_file, + &stdc_wad_file, +}; + +wad_file_t *W_OpenFile(char *path) +{ + wad_file_t *result; + int i; + + //! + // Use the OS's virtual memory subsystem to map WAD files + // directly into memory. + // + + if (!M_CheckParm("-mmap")) + { + return stdc_wad_file.OpenFile(path); + } + + // Try all classes in order until we find one that works + + result = NULL; + + for (i=0; iOpenFile(path); + + if (result != NULL) + { + break; + } + } + + return result; +} + +void W_CloseFile(wad_file_t *wad) +{ + wad->file_class->CloseFile(wad); +} + +size_t W_Read(wad_file_t *wad, unsigned int offset, + void *buffer, size_t buffer_len) +{ + return wad->file_class->Read(wad, offset, buffer, buffer_len); +} + diff --git a/client/src/w_file.h b/client/src/w_file.h new file mode 100644 index 0000000..8ffebe6 --- /dev/null +++ b/client/src/w_file.h @@ -0,0 +1,77 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// WAD I/O functions. +// + + +#ifndef __W_FILE__ +#define __W_FILE__ + +#include + +#include "doomtype.h" + +struct _wad_file_s; + +typedef struct _wad_file_s wad_file_t; + +typedef struct +{ + // Open a file for reading. + wad_file_t *(*OpenFile)(char *path); + + // Close the specified file. + void (*CloseFile)(wad_file_t *file); + + // Read data from the specified position in the file into the + // provided buffer. Returns the number of bytes read. + size_t (*Read)(wad_file_t *file, unsigned int offset, + void *buffer, size_t buffer_len); +} wad_file_class_t; + +struct _wad_file_s +{ + // Class of this file. + wad_file_class_t *file_class; + + // If this is NULL, the file cannot be mapped into memory. If this + // is non-NULL, it is a pointer to the mapped file. + byte *mapped; + + // Length of the file, in bytes. + unsigned int length; + + // File's location on disk. + const char *path; +}; + +// Open the specified file. Returns a pointer to a new wad_file_t +// handle for the WAD file, or NULL if it could not be opened. + +wad_file_t *W_OpenFile(char *path); + +// Close the specified WAD file. + +void W_CloseFile(wad_file_t *wad); + +// Read data from the specified file into the provided buffer. The +// data is read from the specified offset from the start of the file. +// Returns the number of bytes read. + +size_t W_Read(wad_file_t *wad, unsigned int offset, + void *buffer, size_t buffer_len); + +#endif /* #ifndef __W_FILE__ */ diff --git a/client/src/w_file_posix.c b/client/src/w_file_posix.c new file mode 100644 index 0000000..ac283f9 --- /dev/null +++ b/client/src/w_file_posix.c @@ -0,0 +1,165 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// WAD I/O functions. +// + +#include +#include +#include +#include +#include +#include + +#include "doomtype.h" +#include "m_misc.h" +#include "w_file.h" +#include "z_zone.h" + +typedef struct +{ + wad_file_t wad; + int handle; +} posix_wad_file_t; + +extern wad_file_class_t posix_wad_file; + +static void MapFile(posix_wad_file_t *wad, char *filename) +{ + void *result; + int protection; + int flags; + + // Mapped area can be read and written to. Ideally + // this should be read-only, as none of the Doom code should + // change the WAD files after being read. However, there may + // be code lurking in the source that does. + + protection = PROT_READ|PROT_WRITE; + + // Writes to the mapped area result in private changes that are + // *not* written to disk. + + flags = MAP_PRIVATE; + + result = mmap(NULL, wad->wad.length, + protection, flags, + wad->handle, 0); + + wad->wad.mapped = result; + + if (result == NULL) + { + fprintf(stderr, "W_POSIX_OpenFile: Unable to mmap() %s - %s\n", + filename, strerror(errno)); + } +} + +unsigned int GetFileLength(int handle) +{ + return lseek(handle, 0, SEEK_END); +} + +static wad_file_t *W_POSIX_OpenFile(char *path) +{ + posix_wad_file_t *result; + int handle; + + handle = open(path, 0); + + if (handle < 0) + { + return NULL; + } + + // Create a new posix_wad_file_t to hold the file handle. + + result = Z_Malloc(sizeof(posix_wad_file_t), PU_STATIC, 0); + result->wad.file_class = &posix_wad_file; + result->wad.length = GetFileLength(handle); + result->wad.path = M_StringDuplicate(path); + result->handle = handle; + + // Try to map the file into memory with mmap: + + MapFile(result, path); + + return &result->wad; +} + +static void W_POSIX_CloseFile(wad_file_t *wad) +{ + posix_wad_file_t *posix_wad; + + posix_wad = (posix_wad_file_t *) wad; + + // If mapped, unmap it. + + // Close the file + + close(posix_wad->handle); + Z_Free(posix_wad); +} + +// Read data from the specified position in the file into the +// provided buffer. Returns the number of bytes read. + +size_t W_POSIX_Read(wad_file_t *wad, unsigned int offset, + void *buffer, size_t buffer_len) +{ + posix_wad_file_t *posix_wad; + byte *byte_buffer; + size_t bytes_read; + int result; + + posix_wad = (posix_wad_file_t *) wad; + + // Jump to the specified position in the file. + + lseek(posix_wad->handle, offset, SEEK_SET); + + // Read into the buffer. + + bytes_read = 0; + byte_buffer = buffer; + + while (buffer_len > 0) { + result = read(posix_wad->handle, byte_buffer, buffer_len); + + if (result < 0) { + perror("W_POSIX_Read"); + break; + } else if (result == 0) { + break; + } + + // Successfully read some bytes + + byte_buffer += result; + buffer_len -= result; + bytes_read += result; + } + + return bytes_read; +} + + +wad_file_class_t posix_wad_file = +{ + W_POSIX_OpenFile, + W_POSIX_CloseFile, + W_POSIX_Read, +}; + diff --git a/client/src/w_file_stdc.c b/client/src/w_file_stdc.c new file mode 100644 index 0000000..c41692f --- /dev/null +++ b/client/src/w_file_stdc.c @@ -0,0 +1,97 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// WAD I/O functions. +// + +#include + +#include "m_misc.h" +#include "w_file.h" +#include "z_zone.h" + +typedef struct +{ + wad_file_t wad; + FILE *fstream; +} stdc_wad_file_t; + +extern wad_file_class_t stdc_wad_file; + +static wad_file_t *W_StdC_OpenFile(char *path) +{ + stdc_wad_file_t *result; + FILE *fstream; + + fstream = fopen(path, "rb"); + + if (fstream == NULL) + { + return NULL; + } + + // Create a new stdc_wad_file_t to hold the file handle. + + result = Z_Malloc(sizeof(stdc_wad_file_t), PU_STATIC, 0); + result->wad.file_class = &stdc_wad_file; + result->wad.mapped = NULL; + result->wad.length = M_FileLength(fstream); + result->wad.path = M_StringDuplicate(path); + result->fstream = fstream; + + return &result->wad; +} + +static void W_StdC_CloseFile(wad_file_t *wad) +{ + stdc_wad_file_t *stdc_wad; + + stdc_wad = (stdc_wad_file_t *) wad; + + fclose(stdc_wad->fstream); + Z_Free(stdc_wad); +} + +// Read data from the specified position in the file into the +// provided buffer. Returns the number of bytes read. + +size_t W_StdC_Read(wad_file_t *wad, unsigned int offset, + void *buffer, size_t buffer_len) +{ + stdc_wad_file_t *stdc_wad; + size_t result; + + stdc_wad = (stdc_wad_file_t *) wad; + + // Jump to the specified position in the file. + + fseek(stdc_wad->fstream, offset, SEEK_SET); + + // Read into the buffer. + + result = fread(buffer, 1, buffer_len, stdc_wad->fstream); + + return result; +} + + +wad_file_class_t stdc_wad_file = +{ + W_StdC_OpenFile, + W_StdC_CloseFile, + W_StdC_Read, +}; + + diff --git a/client/src/w_main.c b/client/src/w_main.c new file mode 100644 index 0000000..6235976 --- /dev/null +++ b/client/src/w_main.c @@ -0,0 +1,235 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Common code to parse command line, identifying WAD files to load. +// + +#include + +#include "d_iwad.h" +#include "i_system.h" +#include "m_argv.h" +#include "w_main.h" +#include "w_merge.h" +#include "w_wad.h" + +// Parse the command line, merging WAD files that are sppecified. +// Returns true if at least one file was added. +boolean W_ParseCommandLine(void) +{ + boolean modifiedgame = false; + int p; + + // Merged PWADs are loaded first, because they are supposed to be + // modified IWADs. + + //! + // @arg + // @category mod + // + // Simulates the behavior of deutex's -merge option, merging a PWAD + // into the main IWAD. Multiple files may be specified. + // + + p = M_CheckParmWithArgs("-merge", 1); + + if (p > 0) + { + for (p = p + 1; p + // @category mod + // + // Simulates the behavior of NWT's -merge option. Multiple files + // may be specified. + + p = M_CheckParmWithArgs("-nwtmerge", 1); + + if (p > 0) + { + for (p = p + 1; p + // @category mod + // + // Simulates the behavior of NWT's -af option, merging flats into + // the main IWAD directory. Multiple files may be specified. + // + + p = M_CheckParmWithArgs("-af", 1); + + if (p > 0) + { + for (p = p + 1; p + // @category mod + // + // Simulates the behavior of NWT's -as option, merging sprites + // into the main IWAD directory. Multiple files may be specified. + // + + p = M_CheckParmWithArgs("-as", 1); + + if (p > 0) + { + for (p = p + 1; p + // @category mod + // + // Equivalent to "-af -as ". + // + + p = M_CheckParmWithArgs("-aa", 1); + + if (p > 0) + { + for (p = p + 1; p + // @vanilla + // + // Load the specified PWAD files. + // + + p = M_CheckParmWithArgs ("-file", 1); + if (p) + { + // the parms after p are wadfile/lump names, + // until end of parms or another - preceded parm + modifiedgame = true; // homebrew levels + while (++p != myargc && myargv[p][0] != '-') + { + char *filename; + + filename = D_TryFindWADByName(myargv[p]); + + printf(" adding %s\n", filename); + W_AddFile(filename); + } + } + +// W_PrintDirectory(); + + return modifiedgame; +} + +// Lump names that are unique to particular game types. This lets us check +// the user is not trying to play with the wrong executable, eg. +// chocolate-doom -iwad hexen.wad. +static const struct +{ + GameMission_t mission; + char *lumpname; +} unique_lumps[] = { + { doom, "POSSA1" }, + { heretic, "IMPXA1" }, + { hexen, "ETTNA1" }, + { strife, "AGRDA1" }, +}; + +void W_CheckCorrectIWAD(GameMission_t mission) +{ + int i; + lumpindex_t lumpnum; + + for (i = 0; i < arrlen(unique_lumps); ++i) + { + if (mission != unique_lumps[i].mission) + { + lumpnum = W_CheckNumForName(unique_lumps[i].lumpname); + + if (lumpnum >= 0) + { + I_Error("\nYou are trying to use a %s IWAD file with " + "the %s%s binary.\nThis isn't going to work.\n" + "You probably want to use the %s%s binary.", + D_SuggestGameName(unique_lumps[i].mission, + indetermined), + "chocolate", + D_GameMissionString(mission), + "chocolate", + D_GameMissionString(unique_lumps[i].mission)); + } + } + } +} + diff --git a/client/src/w_main.h b/client/src/w_main.h new file mode 100644 index 0000000..7a071c3 --- /dev/null +++ b/client/src/w_main.h @@ -0,0 +1,28 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Common code to parse command line, identifying WAD files to load. +// + +#ifndef W_MAIN_H +#define W_MAIN_H + +#include "d_mode.h" +#include "doomtype.h" + +boolean W_ParseCommandLine(void); +void W_CheckCorrectIWAD(GameMission_t mission); + +#endif /* #ifndef W_MAIN_H */ + diff --git a/client/src/w_merge.c b/client/src/w_merge.c new file mode 100644 index 0000000..899071c --- /dev/null +++ b/client/src/w_merge.c @@ -0,0 +1,724 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Handles merging of PWADs, similar to deutex's -merge option +// +// Ideally this should work exactly the same as in deutex, but trying to +// read the deutex source code made my brain hurt. +// + +#include +#include +#include +#include +#include + +#include "doomtype.h" +#include "i_system.h" +#include "m_misc.h" +#include "w_file.h" +#include "w_merge.h" +#include "w_wad.h" +#include "z_zone.h" + +typedef enum +{ + SECTION_NORMAL, + SECTION_FLATS, + SECTION_SPRITES, +} section_t; + +typedef struct +{ + lumpinfo_t **lumps; + int numlumps; +} searchlist_t; + +typedef struct +{ + char sprname[4]; + char frame; + lumpinfo_t *angle_lumps[8]; +} sprite_frame_t; + +static searchlist_t iwad; +static searchlist_t iwad_sprites; +static searchlist_t pwad; + +static searchlist_t iwad_flats; +static searchlist_t pwad_sprites; +static searchlist_t pwad_flats; + +// lumps with these sprites must be replaced in the IWAD +static sprite_frame_t *sprite_frames; +static int num_sprite_frames; +static int sprite_frames_alloced; + +// Search in a list to find a lump with a particular name +// Linear search (slow!) +// +// Returns -1 if not found + +static int FindInList(searchlist_t *list, char *name) +{ + int i; + + for (i=0; inumlumps; ++i) + { + if (!strncasecmp(list->lumps[i]->name, name, 8)) + return i; + } + + return -1; +} + +static boolean SetupList(searchlist_t *list, searchlist_t *src_list, + char *startname, char *endname, + char *startname2, char *endname2) +{ + int startlump, endlump; + + list->numlumps = 0; + startlump = FindInList(src_list, startname); + + if (startname2 != NULL && startlump < 0) + { + startlump = FindInList(src_list, startname2); + } + + if (startlump >= 0) + { + endlump = FindInList(src_list, endname); + + if (endname2 != NULL && endlump < 0) + { + endlump = FindInList(src_list, endname2); + } + + if (endlump > startlump) + { + list->lumps = src_list->lumps + startlump + 1; + list->numlumps = endlump - startlump - 1; + return true; + } + } + + return false; +} + +// Sets up the sprite/flat search lists + +static void SetupLists(void) +{ + // IWAD + + if (!SetupList(&iwad_flats, &iwad, "F_START", "F_END", NULL, NULL)) + { + I_Error("Flats section not found in IWAD"); + } + + if (!SetupList(&iwad_sprites, &iwad, "S_START", "S_END", NULL, NULL)) + + { + I_Error("Sprites section not found in IWAD"); + } + + // PWAD + + SetupList(&pwad_flats, &pwad, "F_START", "F_END", "FF_START", "FF_END"); + SetupList(&pwad_sprites, &pwad, "S_START", "S_END", "SS_START", "SS_END"); +} + +// Initialize the replace list + +static void InitSpriteList(void) +{ + if (sprite_frames == NULL) + { + sprite_frames_alloced = 128; + sprite_frames = Z_Malloc(sizeof(*sprite_frames) * sprite_frames_alloced, + PU_STATIC, NULL); + } + + num_sprite_frames = 0; +} + +static boolean ValidSpriteLumpName(char *name) +{ + if (name[0] == '\0' || name[1] == '\0' + || name[2] == '\0' || name[3] == '\0') + { + return false; + } + + // First frame: + + if (name[4] == '\0' || !isdigit(name[5])) + { + return false; + } + + // Second frame (optional): + + if (name[6] != '\0' && !isdigit(name[7])) + { + return false; + } + + return true; +} + +// Find a sprite frame + +static sprite_frame_t *FindSpriteFrame(char *name, int frame) +{ + sprite_frame_t *result; + int i; + + // Search the list and try to find the frame + + for (i=0; isprname, name, 4) && cur->frame == frame) + { + return cur; + } + } + + // Not found in list; Need to add to the list + + // Grow list? + + if (num_sprite_frames >= sprite_frames_alloced) + { + sprite_frame_t *newframes; + + newframes = Z_Malloc(sprite_frames_alloced * 2 * sizeof(*sprite_frames), + PU_STATIC, NULL); + memcpy(newframes, sprite_frames, + sprite_frames_alloced * sizeof(*sprite_frames)); + Z_Free(sprite_frames); + sprite_frames_alloced *= 2; + sprite_frames = newframes; + } + + // Add to end of list + + result = &sprite_frames[num_sprite_frames]; + strncpy(result->sprname, name, 4); + result->frame = frame; + + for (i=0; i<8; ++i) + result->angle_lumps[i] = NULL; + + ++num_sprite_frames; + + return result; +} + +// Check if sprite lump is needed in the new wad + +static boolean SpriteLumpNeeded(lumpinfo_t *lump) +{ + sprite_frame_t *sprite; + int angle_num; + int i; + + if (!ValidSpriteLumpName(lump->name)) + { + return true; + } + + // check the first frame + + sprite = FindSpriteFrame(lump->name, lump->name[4]); + angle_num = lump->name[5] - '0'; + + if (angle_num == 0) + { + // must check all frames + + for (i=0; i<8; ++i) + { + if (sprite->angle_lumps[i] == lump) + return true; + } + } + else + { + // check if this lump is being used for this frame + + if (sprite->angle_lumps[angle_num - 1] == lump) + return true; + } + + // second frame if any + + // no second frame? + if (lump->name[6] == '\0') + return false; + + sprite = FindSpriteFrame(lump->name, lump->name[6]); + angle_num = lump->name[7] - '0'; + + if (angle_num == 0) + { + // must check all frames + + for (i=0; i<8; ++i) + { + if (sprite->angle_lumps[i] == lump) + return true; + } + } + else + { + // check if this lump is being used for this frame + + if (sprite->angle_lumps[angle_num - 1] == lump) + return true; + } + + return false; +} + +static void AddSpriteLump(lumpinfo_t *lump) +{ + sprite_frame_t *sprite; + int angle_num; + int i; + + if (!ValidSpriteLumpName(lump->name)) + { + return; + } + + // first angle + + sprite = FindSpriteFrame(lump->name, lump->name[4]); + angle_num = lump->name[5] - '0'; + + if (angle_num == 0) + { + for (i=0; i<8; ++i) + sprite->angle_lumps[i] = lump; + } + else + { + sprite->angle_lumps[angle_num - 1] = lump; + } + + // second angle + + // no second angle? + + if (lump->name[6] == '\0') + return; + + sprite = FindSpriteFrame(lump->name, lump->name[6]); + angle_num = lump->name[7] - '0'; + + if (angle_num == 0) + { + for (i=0; i<8; ++i) + sprite->angle_lumps[i] = lump; + } + else + { + sprite->angle_lumps[angle_num - 1] = lump; + } +} + +// Generate the list. Run at the start, before merging + +static void GenerateSpriteList(void) +{ + int i; + + InitSpriteList(); + + // Add all sprites from the IWAD + + for (i=0; iname, "F_START", 8)) + { + current_section = SECTION_FLATS; + } + else if (!strncasecmp(lump->name, "S_START", 8)) + { + current_section = SECTION_SPRITES; + } + + newlumps[num_newlumps++] = lump; + + break; + + case SECTION_FLATS: + + // Have we reached the end of the section? + + if (!strncasecmp(lump->name, "F_END", 8)) + { + // Add all new flats from the PWAD to the end + // of the section + + for (n=0; nname); + + if (lumpindex < 0) + { + newlumps[num_newlumps++] = lump; + } + } + + break; + + case SECTION_SPRITES: + + // Have we reached the end of the section? + + if (!strncasecmp(lump->name, "S_END", 8)) + { + // add all the PWAD sprites + + for (n=0; nname, "F_START", 8) + || !strncasecmp(lump->name, "FF_START", 8)) + { + current_section = SECTION_FLATS; + } + else if (!strncasecmp(lump->name, "S_START", 8) + || !strncasecmp(lump->name, "SS_START", 8)) + { + current_section = SECTION_SPRITES; + } + else + { + // Don't include the headers of sections + + newlumps[num_newlumps++] = lump; + } + break; + + case SECTION_FLATS: + + // PWAD flats are ignored (already merged) + + if (!strncasecmp(lump->name, "FF_END", 8) + || !strncasecmp(lump->name, "F_END", 8)) + { + // end of section + current_section = SECTION_NORMAL; + } + break; + + case SECTION_SPRITES: + + // PWAD sprites are ignored (already merged) + + if (!strncasecmp(lump->name, "SS_END", 8) + || !strncasecmp(lump->name, "S_END", 8)) + { + // end of section + current_section = SECTION_NORMAL; + } + break; + } + } + + // Switch to the new lumpinfo, and free the old one + + free(lumpinfo); + lumpinfo = newlumps; + numlumps = num_newlumps; +} + +void W_PrintDirectory(void) +{ + unsigned int i, n; + + // debug + for (i=0; iname[n] != '\0'; ++n) + putchar(lumpinfo[i]->name[n]); + putchar('\n'); + } +} + +// Merge in a file by name + +void W_MergeFile(char *filename) +{ + int old_numlumps; + + old_numlumps = numlumps; + + // Load PWAD + + if (W_AddFile(filename) == NULL) + return; + + // IWAD is at the start, PWAD was appended to the end + + iwad.lumps = lumpinfo; + iwad.numlumps = old_numlumps; + + pwad.lumps = lumpinfo + old_numlumps; + pwad.numlumps = numlumps - old_numlumps; + + // Setup sprite/flat lists + + SetupLists(); + + // Generate list of sprites to be replaced by the PWAD + + GenerateSpriteList(); + + // Perform the merge + + DoMerge(); +} + +// Replace lumps in the given list with lumps from the PWAD + +static void W_NWTAddLumps(searchlist_t *list) +{ + int i; + + // Go through the IWAD list given, replacing lumps with lumps of + // the same name from the PWAD + for (i=0; inumlumps; ++i) + { + int index; + + index = FindInList(&pwad, list->lumps[i]->name); + + if (index > 0) + { + memcpy(list->lumps[i], pwad.lumps[index], + sizeof(lumpinfo_t)); + } + } +} + +// Merge sprites and flats in the way NWT does with its -af and -as +// command-line options. + +void W_NWTMergeFile(char *filename, int flags) +{ + int old_numlumps; + + old_numlumps = numlumps; + + // Load PWAD + + if (W_AddFile(filename) == NULL) + return; + + // IWAD is at the start, PWAD was appended to the end + + iwad.lumps = lumpinfo; + iwad.numlumps = old_numlumps; + + pwad.lumps = lumpinfo + old_numlumps; + pwad.numlumps = numlumps - old_numlumps; + + // Setup sprite/flat lists + + SetupLists(); + + // Merge in flats? + + if (flags & W_NWT_MERGE_FLATS) + { + W_NWTAddLumps(&iwad_flats); + } + + // Sprites? + + if (flags & W_NWT_MERGE_SPRITES) + { + W_NWTAddLumps(&iwad_sprites); + } + + // Discard the PWAD + + numlumps = old_numlumps; +} + +// Simulates the NWT -merge command line parameter. What this does is load +// a PWAD, then search the IWAD sprites, removing any sprite lumps that also +// exist in the PWAD. + +void W_NWTDashMerge(char *filename) +{ + wad_file_t *wad_file; + int old_numlumps; + int i; + + old_numlumps = numlumps; + + // Load PWAD + + wad_file = W_AddFile(filename); + + if (wad_file == NULL) + { + return; + } + + // IWAD is at the start, PWAD was appended to the end + + iwad.lumps = lumpinfo; + iwad.numlumps = old_numlumps; + + pwad.lumps = lumpinfo + old_numlumps; + pwad.numlumps = numlumps - old_numlumps; + + // Setup sprite/flat lists + + SetupLists(); + + // Search through the IWAD sprites list. + + for (i=0; iname) >= 0) + { + // Replace this entry with an empty string. This is what + // nwt -merge does. + + M_StringCopy(iwad_sprites.lumps[i]->name, "", 8); + } + } + + // Discard PWAD + // The PWAD must now be added in again with -file. + + numlumps = old_numlumps; + + W_CloseFile(wad_file); +} + diff --git a/client/src/w_merge.h b/client/src/w_merge.h new file mode 100644 index 0000000..c8ecc69 --- /dev/null +++ b/client/src/w_merge.h @@ -0,0 +1,44 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Handles merging of PWADs, similar to deutex's -merge option +// +// Ideally this should work exactly the same as in deutex, but trying to +// read the deutex source code made my brain hurt. +// + +#ifndef W_MERGE_H +#define W_MERGE_H + +#define W_NWT_MERGE_SPRITES 0x1 +#define W_NWT_MERGE_FLATS 0x2 + +// Add a new WAD and merge it into the main directory + +void W_MergeFile(char *filename); + +// NWT-style merging + +void W_NWTMergeFile(char *filename, int flags); + +// Acts the same as NWT's "-merge" option. + +void W_NWTDashMerge(char *filename); + +// Debug function that prints the WAD directory. + +void W_PrintDirectory(void); + +#endif /* #ifndef W_MERGE_H */ + diff --git a/client/src/w_wad.c b/client/src/w_wad.c new file mode 100644 index 0000000..225e113 --- /dev/null +++ b/client/src/w_wad.c @@ -0,0 +1,621 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Handles WAD file header, directory, lump I/O. +// + + + + +#include +#include +#include +#include +#include + +#include "doomtype.h" +#include "i_swap.h" +#include "i_system.h" +#include "m_misc.h" +#include "w_wad.h" +#include "z_zone.h" + +typedef PACKED_STRUCT ( +{ + // Should be "IWAD" or "PWAD". + char identification[4]; + int numlumps; + int infotableofs; +}) wadinfo_t; + + +typedef PACKED_STRUCT ( +{ + int filepos; + int size; + char name[8]; +}) filelump_t; + +// +// GLOBALS +// + +// Location of each lump on disk. +lumpinfo_t **lumpinfo; +unsigned int numlumps = 0; + +// Hash table for fast lookups +static lumpindex_t *lumphash; + +// Variables for the reload hack: filename of the PWAD to reload, and the +// lumps from WADs before the reload file, so we can resent numlumps and +// load the file again. +static wad_file_t *reloadhandle = NULL; +static lumpinfo_t *reloadlumps = NULL; +static char *reloadname = NULL; +static int reloadlump = -1; + +// Hash function used for lump names. +unsigned int W_LumpNameHash(const char *s) +{ + // This is the djb2 string hash function, modded to work on strings + // that have a maximum length of 8. + + unsigned int result = 5381; + unsigned int i; + + for (i=0; i < 8 && s[i] != '\0'; ++i) + { + result = ((result << 5) ^ result ) ^ toupper(s[i]); + } + + return result; +} + +// +// LUMP BASED ROUTINES. +// + +// +// W_AddFile +// All files are optional, but at least one file must be +// found (PWAD, if all required lumps are present). +// Files with a .wad extension are wadlink files +// with multiple lumps. +// Other files are single lumps with the base filename +// for the lump name. + +wad_file_t *W_AddFile (char *filename) +{ + wadinfo_t header; + lumpindex_t i; + wad_file_t *wad_file; + int length; + int startlump; + filelump_t *fileinfo; + filelump_t *filerover; + lumpinfo_t *filelumps; + int numfilelumps; + + // If the filename begins with a ~, it indicates that we should use the + // reload hack. + if (filename[0] == '~') + { + if (reloadname != NULL) + { + I_Error("Prefixing a WAD filename with '~' indicates that the " + "WAD should be reloaded\n" + "on each level restart, for use by level authors for " + "rapid development. You\n" + "can only reload one WAD file, and it must be the last " + "file in the -file list."); + } + + reloadname = strdup(filename); + reloadlump = numlumps; + ++filename; + } + + // Open the file and add to directory + wad_file = W_OpenFile(filename); + + if (wad_file == NULL) + { + printf (" couldn't open %s\n", filename); + return NULL; + } + + if (strcasecmp(filename+strlen(filename)-3 , "wad" ) ) + { + // single lump file + + // fraggle: Swap the filepos and size here. The WAD directory + // parsing code expects a little-endian directory, so will swap + // them back. Effectively we're constructing a "fake WAD directory" + // here, as it would appear on disk. + + fileinfo = Z_Malloc(sizeof(filelump_t), PU_STATIC, 0); + fileinfo->filepos = LONG(0); + fileinfo->size = LONG(wad_file->length); + + // Name the lump after the base of the filename (without the + // extension). + + M_ExtractFileBase (filename, fileinfo->name); + numfilelumps = 1; + } + else + { + // WAD file + W_Read(wad_file, 0, &header, sizeof(header)); + + if (strncmp(header.identification,"IWAD",4)) + { + // Homebrew levels? + if (strncmp(header.identification,"PWAD",4)) + { + W_CloseFile(wad_file); + I_Error ("Wad file %s doesn't have IWAD " + "or PWAD id\n", filename); + } + + // ???modifiedgame = true; + } + + header.numlumps = LONG(header.numlumps); + + // Vanilla Doom doesn't like WADs with more than 4046 lumps + // https://www.doomworld.com/vb/post/1010985 + if (!strncmp(header.identification,"PWAD",4) && header.numlumps > 4046) + { + W_CloseFile(wad_file); + I_Error ("Error: Vanilla limit for lumps in a WAD is 4046, " + "PWAD %s has %d", filename, header.numlumps); + } + + header.infotableofs = LONG(header.infotableofs); + length = header.numlumps*sizeof(filelump_t); + fileinfo = Z_Malloc(length, PU_STATIC, 0); + + W_Read(wad_file, header.infotableofs, fileinfo, length); + numfilelumps = header.numlumps; + } + + // Increase size of numlumps array to accomodate the new file. + filelumps = calloc(numfilelumps, sizeof(lumpinfo_t)); + if (filelumps == NULL) + { + W_CloseFile(wad_file); + I_Error("Failed to allocate array for lumps from new file."); + } + + startlump = numlumps; + numlumps += numfilelumps; + lumpinfo = realloc(lumpinfo, numlumps * sizeof(lumpinfo_t *)); + if (lumpinfo == NULL) + { + W_CloseFile(wad_file); + I_Error("Failed to increase lumpinfo[] array size."); + } + + filerover = fileinfo; + + for (i = startlump; i < numlumps; ++i) + { + lumpinfo_t *lump_p = &filelumps[i - startlump]; + lump_p->wad_file = wad_file; + lump_p->position = LONG(filerover->filepos); + lump_p->size = LONG(filerover->size); + lump_p->cache = NULL; + strncpy(lump_p->name, filerover->name, 8); + lumpinfo[i] = lump_p; + + ++filerover; + } + + Z_Free(fileinfo); + + if (lumphash != NULL) + { + Z_Free(lumphash); + lumphash = NULL; + } + + // If this is the reload file, we need to save some details about the + // file so that we can close it later on when we do a reload. + if (reloadname) + { + reloadhandle = wad_file; + reloadlumps = filelumps; + } + + return wad_file; +} + + + +// +// W_NumLumps +// +int W_NumLumps (void) +{ + return numlumps; +} + + + +// +// W_CheckNumForName +// Returns -1 if name not found. +// + +lumpindex_t W_CheckNumForName(char* name) +{ + lumpindex_t i; + + // Do we have a hash table yet? + + if (lumphash != NULL) + { + int hash; + + // We do! Excellent. + + hash = W_LumpNameHash(name) % numlumps; + + for (i = lumphash[hash]; i != -1; i = lumpinfo[i]->next) + { + if (!strncasecmp(lumpinfo[i]->name, name, 8)) + { + return i; + } + } + } + else + { + // We don't have a hash table generate yet. Linear search :-( + // + // scan backwards so patch lump files take precedence + + for (i = numlumps - 1; i >= 0; --i) + { + if (!strncasecmp(lumpinfo[i]->name, name, 8)) + { + return i; + } + } + } + + // TFB. Not found. + + return -1; +} + + + + +// +// W_GetNumForName +// Calls W_CheckNumForName, but bombs out if not found. +// +lumpindex_t W_GetNumForName(char* name) +{ + lumpindex_t i; + + i = W_CheckNumForName (name); + + if (i < 0) + { + I_Error ("W_GetNumForName: %s not found!", name); + } + + return i; +} + + +// +// W_LumpLength +// Returns the buffer size needed to load the given lump. +// +int W_LumpLength(lumpindex_t lump) +{ + if (lump >= numlumps) + { + I_Error ("W_LumpLength: %i >= numlumps", lump); + } + + return lumpinfo[lump]->size; +} + + + +// +// W_ReadLump +// Loads the lump into the given buffer, +// which must be >= W_LumpLength(). +// +void W_ReadLump(lumpindex_t lump, void *dest) +{ + int c; + lumpinfo_t *l; + + if (lump >= numlumps) + { + I_Error ("W_ReadLump: %i >= numlumps", lump); + } + + l = lumpinfo[lump]; + + c = W_Read(l->wad_file, l->position, dest, l->size); + + if (c < l->size) + { + I_Error("W_ReadLump: only read %i of %i on lump %i", + c, l->size, lump); + } +} + + + + +// +// W_CacheLumpNum +// +// Load a lump into memory and return a pointer to a buffer containing +// the lump data. +// +// 'tag' is the type of zone memory buffer to allocate for the lump +// (usually PU_STATIC or PU_CACHE). If the lump is loaded as +// PU_STATIC, it should be released back using W_ReleaseLumpNum +// when no longer needed (do not use Z_ChangeTag). +// + +void *W_CacheLumpNum(lumpindex_t lumpnum, int tag) +{ + byte *result; + lumpinfo_t *lump; + + if ((unsigned)lumpnum >= numlumps) + { + I_Error ("W_CacheLumpNum: %i >= numlumps", lumpnum); + } + + lump = lumpinfo[lumpnum]; + + // Get the pointer to return. If the lump is in a memory-mapped + // file, we can just return a pointer to within the memory-mapped + // region. If the lump is in an ordinary file, we may already + // have it cached; otherwise, load it into memory. + + if (lump->wad_file->mapped != NULL) + { + // Memory mapped file, return from the mmapped region. + + result = lump->wad_file->mapped + lump->position; + } + else if (lump->cache != NULL) + { + // Already cached, so just switch the zone tag. + + result = lump->cache; + Z_ChangeTag(lump->cache, tag); + } + else + { + // Not yet loaded, so load it now + + lump->cache = Z_Malloc(W_LumpLength(lumpnum), tag, &lump->cache); + W_ReadLump (lumpnum, lump->cache); + result = lump->cache; + } + + return result; +} + + + +// +// W_CacheLumpName +// +void *W_CacheLumpName(char *name, int tag) +{ + return W_CacheLumpNum(W_GetNumForName(name), tag); +} + +// +// Release a lump back to the cache, so that it can be reused later +// without having to read from disk again, or alternatively, discarded +// if we run out of memory. +// +// Back in Vanilla Doom, this was just done using Z_ChangeTag +// directly, but now that we have WAD mmap, things are a bit more +// complicated ... +// + +void W_ReleaseLumpNum(lumpindex_t lumpnum) +{ + lumpinfo_t *lump; + + if ((unsigned)lumpnum >= numlumps) + { + I_Error ("W_ReleaseLumpNum: %i >= numlumps", lumpnum); + } + + lump = lumpinfo[lumpnum]; + + if (lump->wad_file->mapped != NULL) + { + // Memory-mapped file, so nothing needs to be done here. + } + else + { + Z_ChangeTag(lump->cache, PU_CACHE); + } +} + +void W_ReleaseLumpName(char *name) +{ + W_ReleaseLumpNum(W_GetNumForName(name)); +} + +#if 0 + +// +// W_Profile +// +int info[2500][10]; +int profilecount; + +void W_Profile (void) +{ + int i; + memblock_t* block; + void* ptr; + char ch; + FILE* f; + int j; + char name[9]; + + + for (i=0 ; itag < PU_PURGELEVEL) + ch = 'S'; + else + ch = 'P'; + } + info[i][profilecount] = ch; + } + profilecount++; + + f = fopen ("waddump.txt","w"); + name[8] = 0; + + for (i=0 ; i 0) + { + lumphash = Z_Malloc(sizeof(lumpindex_t) * numlumps, PU_STATIC, NULL); + + for (i = 0; i < numlumps; ++i) + { + lumphash[i] = -1; + } + + for (i = 0; i < numlumps; ++i) + { + unsigned int hash; + + hash = W_LumpNameHash(lumpinfo[i]->name) % numlumps; + + // Hook into the hash table + + lumpinfo[i]->next = lumphash[hash]; + lumphash[hash] = i; + } + } + + // All done! +} + +// The Doom reload hack. The idea here is that if you give a WAD file to -file +// prefixed with the ~ hack, that WAD file will be reloaded each time a new +// level is loaded. This lets you use a level editor in parallel and make +// incremental changes to the level you're working on without having to restart +// the game after every change. +// But: the reload feature is a fragile hack... +void W_Reload(void) +{ + char *filename; + lumpindex_t i; + + if (reloadname == NULL) + { + return; + } + + // We must free any lumps being cached from the PWAD we're about to reload: + for (i = reloadlump; i < numlumps; ++i) + { + if (lumpinfo[i]->cache != NULL) + { + Z_Free(lumpinfo[i]->cache); + } + } + + // Reset numlumps to remove the reload WAD file: + numlumps = reloadlump; + + // Now reload the WAD file. + filename = reloadname; + + W_CloseFile(reloadhandle); + free(reloadlumps); + + reloadname = NULL; + reloadlump = -1; + reloadhandle = NULL; + W_AddFile(filename); + free(filename); + + // The WAD directory has changed, so we have to regenerate the + // fast lookup hashtable: + W_GenerateHashTable(); +} + diff --git a/client/src/w_wad.h b/client/src/w_wad.h new file mode 100644 index 0000000..c836f4f --- /dev/null +++ b/client/src/w_wad.h @@ -0,0 +1,74 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// WAD I/O functions. +// + + +#ifndef __W_WAD__ +#define __W_WAD__ + +#include "w_file.h" + +struct lumpinfo_s; + + +// +// TYPES +// + +// +// WADFILE I/O related stuff. +// + +typedef struct lumpinfo_s lumpinfo_t; +typedef int lumpindex_t; + +struct lumpinfo_s +{ + char name[8]; + wad_file_t *wad_file; + int position; + int size; + void *cache; + + // Used for hash table lookups + lumpindex_t next; +}; + + +extern lumpinfo_t **lumpinfo; +extern unsigned int numlumps; + +wad_file_t *W_AddFile(char *filename); +void W_Reload(void); + +lumpindex_t W_CheckNumForName(char *name); +lumpindex_t W_GetNumForName(char *name); + +int W_LumpLength(lumpindex_t lump); +void W_ReadLump(lumpindex_t lump, void *dest); + +void *W_CacheLumpNum(lumpindex_t lump, int tag); +void *W_CacheLumpName(char *name, int tag); + +void W_GenerateHashTable(void); + +extern unsigned int W_LumpNameHash(const char *s); + +void W_ReleaseLumpNum(lumpindex_t lump); +void W_ReleaseLumpName(char *name); + +#endif diff --git a/client/src/wi_stuff.c b/client/src/wi_stuff.c new file mode 100644 index 0000000..301886b --- /dev/null +++ b/client/src/wi_stuff.c @@ -0,0 +1,1534 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Intermission screens. +// + +#include + +#include "d_event.h" +#include "d_mode.h" +#include "d_ticcmd.h" +#include "doomdef.h" +#include "doomstat.h" +#include "doomtype.h" +#include "g_game.h" +#include "i_swap.h" +#include "i_timer.h" +#include "i_video.h" +#include "m_misc.h" +#include "m_random.h" +#include "s_sound.h" +// Data. +#include "sounds.h" +#include "v_patch.h" +// Needs access to LFB. +#include "v_video.h" +#include "w_wad.h" +#include "wi_stuff.h" +#include "z_zone.h" + +// +// Data needed to add patches to full screen intermission pics. +// Patches are statistics messages, and animations. +// Loads of by-pixel layout and placement, offsets etc. +// + +// +// Different vetween registered DOOM (1994) and +// Ultimate DOOM - Final edition (retail, 1995?). +// This is supposedly ignored for commercial +// release (aka DOOM II), which had 34 maps +// in one episode. So there. +#define NUMEPISODES 4 +#define NUMMAPS 9 + +// in tics +// U #define PAUSELEN (TICRATE*2) +// U #define SCORESTEP 100 +// U #define ANIMPERIOD 32 +// pixel distance from "(YOU)" to "PLAYER N" +// U #define STARDIST 10 +// U #define WK 1 + +// GLOBAL LOCATIONS +#define WI_TITLEY 2 +#define WI_SPACINGY 33 + +// SINGPLE-PLAYER STUFF +#define SP_STATSX 50 +#define SP_STATSY 50 + +#define SP_TIMEX 16 +#define SP_TIMEY (SCREENHEIGHT - 32) + +// NET GAME STUFF +#define NG_STATSY 50 +#define NG_STATSX (32 + SHORT(star->width) / 2 + 32 * !dofrags) + +#define NG_SPACINGX 64 + +// DEATHMATCH STUFF +#define DM_MATRIXX 42 +#define DM_MATRIXY 68 + +#define DM_SPACINGX 40 + +#define DM_TOTALSX 269 + +#define DM_KILLERSX 10 +#define DM_KILLERSY 100 +#define DM_VICTIMSX 5 +#define DM_VICTIMSY 50 + +typedef enum { + ANIM_ALWAYS, + ANIM_RANDOM, + ANIM_LEVEL + +} animenum_t; + +typedef struct { + int x; + int y; + +} point_t; + +// +// Animation. +// There is another anim_t used in p_spec. +// +typedef struct { + animenum_t type; + + // period in tics between animations + int period; + + // number of animation frames + int nanims; + + // location of animation + point_t loc; + + // ALWAYS: n/a, + // RANDOM: period deviation (<256), + // LEVEL: level + int data1; + + // ALWAYS: n/a, + // RANDOM: random base period, + // LEVEL: n/a + int data2; + + // actual graphics for frames of animations + patch_t *p[3]; + + // following must be initialized to zero before use! + + // next value of bcnt (used in conjunction with period) + int nexttic; + + // last drawn animation frame + int lastdrawn; + + // next frame number to animate + int ctr; + + // used by RANDOM and LEVEL when animating + int state; + +} anim_t; + +static point_t lnodes[NUMEPISODES][NUMMAPS] = { + // Episode 0 World Map + { + {185, 164}, // location of level 0 (CJ) + {148, 143}, // location of level 1 (CJ) + {69, 122}, // location of level 2 (CJ) + {209, 102}, // location of level 3 (CJ) + {116, 89}, // location of level 4 (CJ) + {166, 55}, // location of level 5 (CJ) + {71, 56}, // location of level 6 (CJ) + {135, 29}, // location of level 7 (CJ) + {71, 24} // location of level 8 (CJ) + }, + + // Episode 1 World Map should go here + { + {254, 25}, // location of level 0 (CJ) + {97, 50}, // location of level 1 (CJ) + {188, 64}, // location of level 2 (CJ) + {128, 78}, // location of level 3 (CJ) + {214, 92}, // location of level 4 (CJ) + {133, 130}, // location of level 5 (CJ) + {208, 136}, // location of level 6 (CJ) + {148, 140}, // location of level 7 (CJ) + {235, 158} // location of level 8 (CJ) + }, + + // Episode 2 World Map should go here + { + {156, 168}, // location of level 0 (CJ) + {48, 154}, // location of level 1 (CJ) + {174, 95}, // location of level 2 (CJ) + {265, 75}, // location of level 3 (CJ) + {130, 48}, // location of level 4 (CJ) + {279, 23}, // location of level 5 (CJ) + {198, 48}, // location of level 6 (CJ) + {140, 25}, // location of level 7 (CJ) + {281, 136} // location of level 8 (CJ) + } + +}; + +// +// Animation locations for episode 0 (1). +// Using patches saves a lot of space, +// as they replace 320x200 full screen frames. +// + +#define ANIM(type, period, nanims, x, y, nexttic) \ + { \ + (type), (period), (nanims), {(x), (y)}, (nexttic), 0, {NULL, NULL, NULL}, \ + 0, 0, 0, 0 \ + } + +static anim_t epsd0animinfo[] = { + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 224, 104, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 184, 160, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 112, 136, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 72, 112, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 88, 96, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 64, 48, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 192, 40, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 136, 16, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 80, 16, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 64, 24, 0), +}; + +static anim_t epsd1animinfo[] = { + ANIM(ANIM_LEVEL, TICRATE / 3, 1, 128, 136, 1), + ANIM(ANIM_LEVEL, TICRATE / 3, 1, 128, 136, 2), + ANIM(ANIM_LEVEL, TICRATE / 3, 1, 128, 136, 3), + ANIM(ANIM_LEVEL, TICRATE / 3, 1, 128, 136, 4), + ANIM(ANIM_LEVEL, TICRATE / 3, 1, 128, 136, 5), + ANIM(ANIM_LEVEL, TICRATE / 3, 1, 128, 136, 6), + ANIM(ANIM_LEVEL, TICRATE / 3, 1, 128, 136, 7), + ANIM(ANIM_LEVEL, TICRATE / 3, 3, 192, 144, 8), + ANIM(ANIM_LEVEL, TICRATE / 3, 1, 128, 136, 8), +}; + +static anim_t epsd2animinfo[] = { + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 104, 168, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 40, 136, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 160, 96, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 104, 80, 0), + ANIM(ANIM_ALWAYS, TICRATE / 3, 3, 120, 32, 0), + ANIM(ANIM_ALWAYS, TICRATE / 4, 3, 40, 0, 0), +}; + +static int NUMANIMS[NUMEPISODES] = { + arrlen(epsd0animinfo), arrlen(epsd1animinfo), arrlen(epsd2animinfo), +}; + +static anim_t *anims[NUMEPISODES] = {epsd0animinfo, epsd1animinfo, + epsd2animinfo}; + +// +// GENERAL DATA +// + +// +// Locally used stuff. +// + +// States for single-player +#define SP_KILLS 0 +#define SP_ITEMS 2 +#define SP_SECRET 4 +#define SP_FRAGS 6 +#define SP_TIME 8 +#define SP_PAR ST_TIME + +#define SP_PAUSE 1 + +// in seconds +#define SHOWNEXTLOCDELAY 4 +//#define SHOWLASTLOCDELAY SHOWNEXTLOCDELAY + +// used to accelerate or skip a stage +static int acceleratestage; + +// wbs->pnum +static int me; + +// specifies current state +static stateenum_t state; + +// contains information passed into intermission +static wbstartstruct_t *wbs; + +static wbplayerstruct_t *plrs; // wbs->plyr[] + +// used for general timing +static int cnt; + +// used for timing of background animation +static int bcnt; + +// signals to refresh everything for one frame +static int firstrefresh; + +static int cnt_kills[MAXPLAYERS]; +static int cnt_items[MAXPLAYERS]; +static int cnt_secret[MAXPLAYERS]; +static int cnt_time; +static int cnt_par; +static int cnt_pause; + +// # of commercial levels +static int NUMCMAPS; + +// +// GRAPHICS +// + +// You Are Here graphic +static patch_t *yah[3] = {NULL, NULL, NULL}; + +// splat +static patch_t *splat[2] = {NULL, NULL}; + +// %, : graphics +static patch_t *percent; +static patch_t *colon; + +// 0-9 graphic +static patch_t *num[10]; + +// minus sign +static patch_t *wiminus; + +// "Finished!" graphics +static patch_t *finished; + +// "Entering" graphic +static patch_t *entering; + +// "secret" +static patch_t *sp_secret; + +// "Kills", "Scrt", "Items", "Frags" +static patch_t *kills; +static patch_t *secret; +static patch_t *items; +static patch_t *frags; + +// Time sucks. +static patch_t *timepatch; +static patch_t *par; +static patch_t *sucks; + +// "killers", "victims" +static patch_t *killers; +static patch_t *victims; + +// "Total", your face, your dead face +static patch_t *total; +static patch_t *star; +static patch_t *bstar; + +// "red P[1..MAXPLAYERS]" +static patch_t *p[MAXPLAYERS]; + +// "gray P[1..MAXPLAYERS]" +static patch_t *bp[MAXPLAYERS]; + +// Name graphics of each level (centered) +static patch_t **lnames; + +// Buffer storing the backdrop +static patch_t *background; + +// +// CODE +// + +// slam background +void WI_slamBackground(void) { V_DrawPatch(0, 0, background); } + +// The ticker is used to detect keys +// because of timing issues in netgames. +boolean WI_Responder(event_t *ev) { return false; } + +// Draws " Finished!" +void WI_drawLF(void) { + int y = WI_TITLEY; + + if (gamemode != commercial || wbs->last < NUMCMAPS) { + // draw + V_DrawPatch((SCREENWIDTH - SHORT(lnames[wbs->last]->width)) / 2, y, + lnames[wbs->last]); + + // draw "Finished!" + y += (5 * SHORT(lnames[wbs->last]->height)) / 4; + + V_DrawPatch((SCREENWIDTH - SHORT(finished->width)) / 2, y, finished); + } else if (wbs->last == NUMCMAPS) { + // MAP33 - nothing is displayed! + } else if (wbs->last > NUMCMAPS) { + // > MAP33. Doom bombs out here with a Bad V_DrawPatch error. + // I'm pretty sure that doom2.exe is just reading into random + // bits of memory at this point, but let's try to be accurate + // anyway. This deliberately triggers a V_DrawPatch error. + + patch_t tmp = {SCREENWIDTH, SCREENHEIGHT, 1, 1, {0, 0, 0, 0, 0, 0, 0, 0}}; + + V_DrawPatch(0, y, &tmp); + } +} + +// Draws "Entering " +void WI_drawEL(void) { + int y = WI_TITLEY; + + // draw "Entering" + V_DrawPatch((SCREENWIDTH - SHORT(entering->width)) / 2, y, entering); + + // draw level + y += (5 * SHORT(lnames[wbs->next]->height)) / 4; + + V_DrawPatch((SCREENWIDTH - SHORT(lnames[wbs->next]->width)) / 2, y, + lnames[wbs->next]); +} + +void WI_drawOnLnode(int n, patch_t *c[]) { + + int i; + int left; + int top; + int right; + int bottom; + boolean fits = false; + + i = 0; + do { + left = lnodes[wbs->epsd][n].x - SHORT(c[i]->leftoffset); + top = lnodes[wbs->epsd][n].y - SHORT(c[i]->topoffset); + right = left + SHORT(c[i]->width); + bottom = top + SHORT(c[i]->height); + + if (left >= 0 && right < SCREENWIDTH && top >= 0 && bottom < SCREENHEIGHT) { + fits = true; + } else { + i++; + } + } while (!fits && i != 2 && c[i] != NULL); + + if (fits && i < 2) { + V_DrawPatch(lnodes[wbs->epsd][n].x, lnodes[wbs->epsd][n].y, c[i]); + } else { + // DEBUG + printf("Could not place patch on level %d", n + 1); + } +} + +void WI_initAnimatedBack(void) { + int i; + anim_t *a; + + if (gamemode == commercial) + return; + + if (wbs->epsd > 2) + return; + + for (i = 0; i < NUMANIMS[wbs->epsd]; i++) { + a = &anims[wbs->epsd][i]; + + // init variables + a->ctr = -1; + + // specify the next time to draw it + if (a->type == ANIM_ALWAYS) + a->nexttic = bcnt + 1 + (M_Random() % a->period); + else if (a->type == ANIM_RANDOM) + a->nexttic = bcnt + 1 + a->data2 + (M_Random() % a->data1); + else if (a->type == ANIM_LEVEL) + a->nexttic = bcnt + 1; + } +} + +void WI_updateAnimatedBack(void) { + int i; + anim_t *a; + + if (gamemode == commercial) + return; + + if (wbs->epsd > 2) + return; + + for (i = 0; i < NUMANIMS[wbs->epsd]; i++) { + a = &anims[wbs->epsd][i]; + + if (bcnt == a->nexttic) { + switch (a->type) { + case ANIM_ALWAYS: + if (++a->ctr >= a->nanims) + a->ctr = 0; + a->nexttic = bcnt + a->period; + break; + + case ANIM_RANDOM: + a->ctr++; + if (a->ctr == a->nanims) { + a->ctr = -1; + a->nexttic = bcnt + a->data2 + (M_Random() % a->data1); + } else + a->nexttic = bcnt + a->period; + break; + + case ANIM_LEVEL: + // gawd-awful hack for level anims + if (!(state == StatCount && i == 7) && wbs->next == a->data1) { + a->ctr++; + if (a->ctr == a->nanims) + a->ctr--; + a->nexttic = bcnt + a->period; + } + break; + } + } + } +} + +void WI_drawAnimatedBack(void) { + int i; + anim_t *a; + + if (gamemode == commercial) + return; + + if (wbs->epsd > 2) + return; + + for (i = 0; i < NUMANIMS[wbs->epsd]; i++) { + a = &anims[wbs->epsd][i]; + + if (a->ctr >= 0) + V_DrawPatch(a->loc.x, a->loc.y, a->p[a->ctr]); + } +} + +// +// Draws a number. +// If digits > 0, then use that many digits minimum, +// otherwise only use as many as necessary. +// Returns new x position. +// + +int WI_drawNum(int x, int y, int n, int digits) { + + int fontwidth = SHORT(num[0]->width); + int neg; + int temp; + + if (digits < 0) { + if (!n) { + // make variable-length zeros 1 digit long + digits = 1; + } else { + // figure out # of digits in # + digits = 0; + temp = n; + + while (temp) { + temp /= 10; + digits++; + } + } + } + + neg = n < 0; + if (neg) + n = -n; + + // if non-number, do not draw it + if (n == 1994) + return 0; + + // draw the new number + while (digits--) { + x -= fontwidth; + V_DrawPatch(x, y, num[n % 10]); + n /= 10; + } + + // draw a minus sign if necessary + if (neg) + V_DrawPatch(x -= 8, y, wiminus); + + return x; +} + +void WI_drawPercent(int x, int y, int p) { + if (p < 0) + return; + + V_DrawPatch(x, y, percent); + WI_drawNum(x, y, p, -1); +} + +// +// Display level completion time and par, +// or "sucks" message if overflow. +// +void WI_drawTime(int x, int y, int t) { + + int div; + int n; + + if (t < 0) + return; + + if (t <= 61 * 59) { + div = 1; + + do { + n = (t / div) % 60; + x = WI_drawNum(x, y, n, 2) - SHORT(colon->width); + div *= 60; + + // draw + if (div == 60 || t / div) + V_DrawPatch(x, y, colon); + + } while (t / div); + } else { + // "sucks" + V_DrawPatch(x - SHORT(sucks->width), y, sucks); + } +} + +void WI_End(void) { + void WI_unloadData(void); + WI_unloadData(); +} + +void WI_initNoState(void) { + state = NoState; + acceleratestage = 0; + cnt = 10; +} + +void WI_updateNoState(void) { + + WI_updateAnimatedBack(); + + if (!--cnt) { + // Don't call WI_End yet. G_WorldDone doesnt immediately + // change gamestate, so WI_Drawer is still going to get + // run until that happens. If we do that after WI_End + // (which unloads all the graphics), we're in trouble. + // WI_End(); + G_WorldDone(); + } +} + +static boolean snl_pointeron = false; + +void WI_initShowNextLoc(void) { + state = ShowNextLoc; + acceleratestage = 0; + cnt = SHOWNEXTLOCDELAY * TICRATE; + + WI_initAnimatedBack(); +} + +void WI_updateShowNextLoc(void) { + WI_updateAnimatedBack(); + + if (!--cnt || acceleratestage) + WI_initNoState(); + else + snl_pointeron = (cnt & 31) < 20; +} + +void WI_drawShowNextLoc(void) { + + int i; + int last; + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + + if (gamemode != commercial) { + if (wbs->epsd > 2) { + WI_drawEL(); + return; + } + + last = (wbs->last == 8) ? wbs->next - 1 : wbs->last; + + // draw a splat on taken cities. + for (i = 0; i <= last; i++) + WI_drawOnLnode(i, splat); + + // splat the secret level? + if (wbs->didsecret) + WI_drawOnLnode(8, splat); + + // draw flashing ptr + if (snl_pointeron) + WI_drawOnLnode(wbs->next, yah); + } + + // draws which level you are entering.. + if ((gamemode != commercial) || wbs->next != 30) + WI_drawEL(); +} + +void WI_drawNoState(void) { + snl_pointeron = true; + WI_drawShowNextLoc(); +} + +int WI_fragSum(int playernum) { + int i; + int frags = 0; + + for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i] && i != playernum) { + frags += plrs[playernum].frags[i]; + } + } + + // JDC hack - negative frags. + frags -= plrs[playernum].frags[playernum]; + // UNUSED if (frags < 0) + // frags = 0; + + return frags; +} + +static int dm_state; +static int dm_frags[MAXPLAYERS][MAXPLAYERS]; +static int dm_totals[MAXPLAYERS]; + +void WI_initDeathmatchStats(void) { + + int i; + int j; + + state = StatCount; + acceleratestage = 0; + dm_state = 1; + + cnt_pause = TICRATE; + + for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + for (j = 0; j < MAXPLAYERS; j++) + if (playeringame[j]) + dm_frags[i][j] = 0; + + dm_totals[i] = 0; + } + } + + WI_initAnimatedBack(); +} + +void WI_updateDeathmatchStats(void) { + + int i; + int j; + + boolean stillticking; + + WI_updateAnimatedBack(); + + if (acceleratestage && dm_state != 4) { + acceleratestage = 0; + + for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + for (j = 0; j < MAXPLAYERS; j++) + if (playeringame[j]) + dm_frags[i][j] = plrs[i].frags[j]; + + dm_totals[i] = WI_fragSum(i); + } + } + + S_StartSound(0, sfx_barexp); + dm_state = 4; + } + + if (dm_state == 2) { + if (!(bcnt & 3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + for (j = 0; j < MAXPLAYERS; j++) { + if (playeringame[j] && dm_frags[i][j] != plrs[i].frags[j]) { + if (plrs[i].frags[j] < 0) + dm_frags[i][j]--; + else + dm_frags[i][j]++; + + if (dm_frags[i][j] > 99) + dm_frags[i][j] = 99; + + if (dm_frags[i][j] < -99) + dm_frags[i][j] = -99; + + stillticking = true; + } + } + dm_totals[i] = WI_fragSum(i); + + if (dm_totals[i] > 99) + dm_totals[i] = 99; + + if (dm_totals[i] < -99) + dm_totals[i] = -99; + } + } + if (!stillticking) { + S_StartSound(0, sfx_barexp); + dm_state++; + } + + } else if (dm_state == 4) { + if (acceleratestage) { + S_StartSound(0, sfx_slop); + + if (gamemode == commercial) + WI_initNoState(); + else + WI_initShowNextLoc(); + } + } else if (dm_state & 1) { + if (!--cnt_pause) { + dm_state++; + cnt_pause = TICRATE; + } + } +} + +void WI_drawDeathmatchStats(void) { + + int i; + int j; + int x; + int y; + int w; + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + WI_drawLF(); + + // draw stat titles (top line) + V_DrawPatch(DM_TOTALSX - SHORT(total->width) / 2, + DM_MATRIXY - WI_SPACINGY + 10, total); + + V_DrawPatch(DM_KILLERSX, DM_KILLERSY, killers); + V_DrawPatch(DM_VICTIMSX, DM_VICTIMSY, victims); + + // draw P? + x = DM_MATRIXX + DM_SPACINGX; + y = DM_MATRIXY; + + for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + V_DrawPatch(x - SHORT(p[i]->width) / 2, DM_MATRIXY - WI_SPACINGY, p[i]); + + V_DrawPatch(DM_MATRIXX - SHORT(p[i]->width) / 2, y, p[i]); + + if (i == me) { + V_DrawPatch(x - SHORT(p[i]->width) / 2, DM_MATRIXY - WI_SPACINGY, + bstar); + + V_DrawPatch(DM_MATRIXX - SHORT(p[i]->width) / 2, y, star); + } + } else { + // V_DrawPatch(x-SHORT(bp[i]->width)/2, + // DM_MATRIXY - WI_SPACINGY, bp[i]); + // V_DrawPatch(DM_MATRIXX-SHORT(bp[i]->width)/2, + // y, bp[i]); + } + x += DM_SPACINGX; + y += WI_SPACINGY; + } + + // draw stats + y = DM_MATRIXY + 10; + w = SHORT(num[0]->width); + + for (i = 0; i < MAXPLAYERS; i++) { + x = DM_MATRIXX + DM_SPACINGX; + + if (playeringame[i]) { + for (j = 0; j < MAXPLAYERS; j++) { + if (playeringame[j]) + WI_drawNum(x + w, y, dm_frags[i][j], 2); + + x += DM_SPACINGX; + } + WI_drawNum(DM_TOTALSX + w, y, dm_totals[i], 2); + } + y += WI_SPACINGY; + } +} + +static int cnt_frags[MAXPLAYERS]; +static int dofrags; +static int ng_state; + +void WI_initNetgameStats(void) { + + int i; + + state = StatCount; + acceleratestage = 0; + ng_state = 1; + + cnt_pause = TICRATE; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + + cnt_kills[i] = cnt_items[i] = cnt_secret[i] = cnt_frags[i] = 0; + + dofrags += WI_fragSum(i); + } + + dofrags = !!dofrags; + + WI_initAnimatedBack(); +} + +void WI_updateNetgameStats(void) { + + int i; + int fsum; + + boolean stillticking; + + WI_updateAnimatedBack(); + + if (acceleratestage && ng_state != 10) { + acceleratestage = 0; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + + cnt_kills[i] = (plrs[i].skills * 100) / wbs->maxkills; + cnt_items[i] = (plrs[i].sitems * 100) / wbs->maxitems; + cnt_secret[i] = (plrs[i].ssecret * 100) / wbs->maxsecret; + + if (dofrags) + cnt_frags[i] = WI_fragSum(i); + } + S_StartSound(0, sfx_barexp); + ng_state = 10; + } + + if (ng_state == 2) { + if (!(bcnt & 3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + + cnt_kills[i] += 2; + + if (cnt_kills[i] >= (plrs[i].skills * 100) / wbs->maxkills) + cnt_kills[i] = (plrs[i].skills * 100) / wbs->maxkills; + else + stillticking = true; + } + + if (!stillticking) { + S_StartSound(0, sfx_barexp); + ng_state++; + } + } else if (ng_state == 4) { + if (!(bcnt & 3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + + cnt_items[i] += 2; + if (cnt_items[i] >= (plrs[i].sitems * 100) / wbs->maxitems) + cnt_items[i] = (plrs[i].sitems * 100) / wbs->maxitems; + else + stillticking = true; + } + if (!stillticking) { + S_StartSound(0, sfx_barexp); + ng_state++; + } + } else if (ng_state == 6) { + if (!(bcnt & 3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + + cnt_secret[i] += 2; + + if (cnt_secret[i] >= (plrs[i].ssecret * 100) / wbs->maxsecret) + cnt_secret[i] = (plrs[i].ssecret * 100) / wbs->maxsecret; + else + stillticking = true; + } + + if (!stillticking) { + S_StartSound(0, sfx_barexp); + ng_state += 1 + 2 * !dofrags; + } + } else if (ng_state == 8) { + if (!(bcnt & 3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + + cnt_frags[i] += 1; + + if (cnt_frags[i] >= (fsum = WI_fragSum(i))) + cnt_frags[i] = fsum; + else + stillticking = true; + } + + if (!stillticking) { + S_StartSound(0, sfx_pldeth); + ng_state++; + } + } else if (ng_state == 10) { + if (acceleratestage) { + S_StartSound(0, sfx_sgcock); + if (gamemode == commercial) + WI_initNoState(); + else + WI_initShowNextLoc(); + } + } else if (ng_state & 1) { + if (!--cnt_pause) { + ng_state++; + cnt_pause = TICRATE; + } + } +} + +void WI_drawNetgameStats(void) { + int i; + int x; + int y; + int pwidth = SHORT(percent->width); + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + + WI_drawLF(); + + // draw stat titles (top line) + V_DrawPatch(NG_STATSX + NG_SPACINGX - SHORT(kills->width), NG_STATSY, kills); + + V_DrawPatch(NG_STATSX + 2 * NG_SPACINGX - SHORT(items->width), NG_STATSY, + items); + + V_DrawPatch(NG_STATSX + 3 * NG_SPACINGX - SHORT(secret->width), NG_STATSY, + secret); + + if (dofrags) + V_DrawPatch(NG_STATSX + 4 * NG_SPACINGX - SHORT(frags->width), NG_STATSY, + frags); + + // draw stats + y = NG_STATSY + SHORT(kills->height); + + for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i]) + continue; + + x = NG_STATSX; + V_DrawPatch(x - SHORT(p[i]->width), y, p[i]); + + if (i == me) + V_DrawPatch(x - SHORT(p[i]->width), y, star); + + x += NG_SPACINGX; + WI_drawPercent(x - pwidth, y + 10, cnt_kills[i]); + x += NG_SPACINGX; + WI_drawPercent(x - pwidth, y + 10, cnt_items[i]); + x += NG_SPACINGX; + WI_drawPercent(x - pwidth, y + 10, cnt_secret[i]); + x += NG_SPACINGX; + + if (dofrags) + WI_drawNum(x, y + 10, cnt_frags[i], -1); + + y += WI_SPACINGY; + } +} + +static int sp_state; + +void WI_initStats(void) { + state = StatCount; + acceleratestage = 0; + sp_state = 1; + cnt_kills[0] = cnt_items[0] = cnt_secret[0] = -1; + cnt_time = cnt_par = -1; + cnt_pause = TICRATE; + + WI_initAnimatedBack(); +} + +void WI_updateStats(void) { + + WI_updateAnimatedBack(); + + if (acceleratestage && sp_state != 10) { + acceleratestage = 0; + cnt_kills[0] = (plrs[me].skills * 100) / wbs->maxkills; + cnt_items[0] = (plrs[me].sitems * 100) / wbs->maxitems; + cnt_secret[0] = (plrs[me].ssecret * 100) / wbs->maxsecret; + cnt_time = plrs[me].stime / TICRATE; + cnt_par = wbs->partime / TICRATE; + S_StartSound(0, sfx_barexp); + sp_state = 10; + } + + if (sp_state == 2) { + cnt_kills[0] += 2; + + if (!(bcnt & 3)) + S_StartSound(0, sfx_pistol); + + if (cnt_kills[0] >= (plrs[me].skills * 100) / wbs->maxkills) { + cnt_kills[0] = (plrs[me].skills * 100) / wbs->maxkills; + S_StartSound(0, sfx_barexp); + sp_state++; + } + } else if (sp_state == 4) { + cnt_items[0] += 2; + + if (!(bcnt & 3)) + S_StartSound(0, sfx_pistol); + + if (cnt_items[0] >= (plrs[me].sitems * 100) / wbs->maxitems) { + cnt_items[0] = (plrs[me].sitems * 100) / wbs->maxitems; + S_StartSound(0, sfx_barexp); + sp_state++; + } + } else if (sp_state == 6) { + cnt_secret[0] += 2; + + if (!(bcnt & 3)) + S_StartSound(0, sfx_pistol); + + if (cnt_secret[0] >= (plrs[me].ssecret * 100) / wbs->maxsecret) { + cnt_secret[0] = (plrs[me].ssecret * 100) / wbs->maxsecret; + S_StartSound(0, sfx_barexp); + sp_state++; + } + } + + else if (sp_state == 8) { + if (!(bcnt & 3)) + S_StartSound(0, sfx_pistol); + + cnt_time += 3; + + if (cnt_time >= plrs[me].stime / TICRATE) + cnt_time = plrs[me].stime / TICRATE; + + cnt_par += 3; + + if (cnt_par >= wbs->partime / TICRATE) { + cnt_par = wbs->partime / TICRATE; + + if (cnt_time >= plrs[me].stime / TICRATE) { + S_StartSound(0, sfx_barexp); + sp_state++; + } + } + } else if (sp_state == 10) { + if (acceleratestage) { + S_StartSound(0, sfx_sgcock); + + if (gamemode == commercial) + WI_initNoState(); + else + WI_initShowNextLoc(); + } + } else if (sp_state & 1) { + if (!--cnt_pause) { + sp_state++; + cnt_pause = TICRATE; + } + } +} + +void WI_drawStats(void) { + // line height + int lh; + + lh = (3 * SHORT(num[0]->height)) / 2; + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + + WI_drawLF(); + + V_DrawPatch(SP_STATSX, SP_STATSY, kills); + WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY, cnt_kills[0]); + + V_DrawPatch(SP_STATSX, SP_STATSY + lh, items); + WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY + lh, cnt_items[0]); + + V_DrawPatch(SP_STATSX, SP_STATSY + 2 * lh, sp_secret); + WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY + 2 * lh, cnt_secret[0]); + + V_DrawPatch(SP_TIMEX, SP_TIMEY, timepatch); + WI_drawTime(SCREENWIDTH / 2 - SP_TIMEX, SP_TIMEY, cnt_time); + + if (wbs->epsd < 3) { + V_DrawPatch(SCREENWIDTH / 2 + SP_TIMEX, SP_TIMEY, par); + WI_drawTime(SCREENWIDTH - SP_TIMEX, SP_TIMEY, cnt_par); + } +} + +void WI_checkForAccelerate(void) { + int i; + player_t *player; + + // check for button presses to skip delays + for (i = 0, player = players; i < MAXPLAYERS; i++, player++) { + if (playeringame[i]) { + if (player->cmd.buttons & BT_ATTACK) { + if (!player->attackdown) + acceleratestage = 1; + player->attackdown = true; + } else + player->attackdown = false; + if (player->cmd.buttons & BT_USE) { + if (!player->usedown) + acceleratestage = 1; + player->usedown = true; + } else + player->usedown = false; + } + } +} + +// Updates stuff each tick +void WI_Ticker(void) { + // counter for general background animation + bcnt++; + + if (bcnt == 1) { + // intermission music + if (gamemode == commercial) + S_ChangeMusic(mus_dm2int, true); + else + S_ChangeMusic(mus_inter, true); + } + + WI_checkForAccelerate(); + + switch (state) { + case StatCount: + if (deathmatch) + WI_updateDeathmatchStats(); + else if (netgame) + WI_updateNetgameStats(); + else + WI_updateStats(); + break; + + case ShowNextLoc: + WI_updateShowNextLoc(); + break; + + case NoState: + WI_updateNoState(); + break; + } +} + +typedef void (*load_callback_t)(char *lumpname, patch_t **variable); + +// Common load/unload function. Iterates over all the graphics +// lumps to be loaded/unloaded into memory. + +static void WI_loadUnloadData(load_callback_t callback) { + int i, j; + char name[9]; + anim_t *a; + + if (gamemode == commercial) { + for (i = 0; i < NUMCMAPS; i++) { + M_snprintf(name, 9, "CWILV%2.2d", i); + callback(name, &lnames[i]); + } + } else { + for (i = 0; i < NUMMAPS; i++) { + M_snprintf(name, 9, "WILV%d%d", wbs->epsd, i); + callback(name, &lnames[i]); + } + + // you are here + callback(("WIURH0"), &yah[0]); + + // you are here (alt.) + callback(("WIURH1"), &yah[1]); + + // splat + callback(("WISPLAT"), &splat[0]); + + if (wbs->epsd < 3) { + for (j = 0; j < NUMANIMS[wbs->epsd]; j++) { + a = &anims[wbs->epsd][j]; + for (i = 0; i < a->nanims; i++) { + // MONDO HACK! + if (wbs->epsd != 1 || j != 8) { + // animations + M_snprintf(name, 9, "WIA%d%.2d%.2d", wbs->epsd, j, i); + callback(name, &a->p[i]); + } else { + // HACK ALERT! + a->p[i] = anims[1][4].p[i]; + } + } + } + } + } + + // More hacks on minus sign. + callback(("WIMINUS"), &wiminus); + + for (i = 0; i < 10; i++) { + // numbers 0-9 + M_snprintf(name, 9, "WINUM%d", i); + callback(name, &num[i]); + } + + // percent sign + callback(("WIPCNT"), &percent); + + // "finished" + callback(("WIF"), &finished); + + // "entering" + callback(("WIENTER"), &entering); + + // "kills" + callback(("WIOSTK"), &kills); + + // "scrt" + callback(("WIOSTS"), &secret); + + // "secret" + callback(("WISCRT2"), &sp_secret); + + // french wad uses WIOBJ (?) + if (W_CheckNumForName(("WIOBJ")) >= 0) { + // "items" + if (netgame && !deathmatch) + callback(("WIOBJ"), &items); + else + callback(("WIOSTI"), &items); + } else { + callback(("WIOSTI"), &items); + } + + // "frgs" + callback(("WIFRGS"), &frags); + + // ":" + callback(("WICOLON"), &colon); + + // "time" + callback(("WITIME"), &timepatch); + + // "sucks" + callback(("WISUCKS"), &sucks); + + // "par" + callback(("WIPAR"), &par); + + // "killers" (vertical) + callback(("WIKILRS"), &killers); + + // "victims" (horiz) + callback(("WIVCTMS"), &victims); + + // "total" + callback(("WIMSTT"), &total); + + for (i = 0; i < MAXPLAYERS; i++) { + // "1,2,3,4" + M_snprintf(name, 9, "STPB%d", i); + callback(name, &p[i]); + + // "1,2,3,4" + M_snprintf(name, 9, "WIBP%d", i + 1); + callback(name, &bp[i]); + } + + // Background image + + if (gamemode == commercial) { + M_StringCopy(name, ("INTERPIC"), sizeof(name)); + } else if (gameversion >= exe_ultimate && wbs->epsd == 3) { + M_StringCopy(name, ("INTERPIC"), sizeof(name)); + } else { + M_snprintf(name, sizeof(name), "WIMAP%d", wbs->epsd); + } + + // Draw backdrop and save to a temporary buffer + + callback(name, &background); +} + +static void WI_loadCallback(char *name, patch_t **variable) { + *variable = W_CacheLumpName(name, PU_STATIC); +} + +void WI_loadData(void) { + if (gamemode == commercial) { + NUMCMAPS = 32; + lnames = + (patch_t **)Z_Malloc(sizeof(patch_t *) * NUMCMAPS, PU_STATIC, NULL); + } else { + lnames = (patch_t **)Z_Malloc(sizeof(patch_t *) * NUMMAPS, PU_STATIC, NULL); + } + + WI_loadUnloadData(WI_loadCallback); + + // These two graphics are special cased because we're sharing + // them with the status bar code + + // your face + star = W_CacheLumpName(("STFST01"), PU_STATIC); + + // dead face + bstar = W_CacheLumpName(("STFDEAD0"), PU_STATIC); +} + +static void WI_unloadCallback(char *name, patch_t **variable) { + W_ReleaseLumpName(name); + *variable = NULL; +} + +void WI_unloadData(void) { + WI_loadUnloadData(WI_unloadCallback); + + // We do not free these lumps as they are shared with the status + // bar code. + + // W_ReleaseLumpName("STFST01"); + // W_ReleaseLumpName("STFDEAD0"); +} + +void WI_Drawer(void) { + switch (state) { + case StatCount: + if (deathmatch) + WI_drawDeathmatchStats(); + else if (netgame) + WI_drawNetgameStats(); + else + WI_drawStats(); + break; + + case ShowNextLoc: + WI_drawShowNextLoc(); + break; + + case NoState: + WI_drawNoState(); + break; + } +} + +void WI_initVariables(wbstartstruct_t *wbstartstruct) { + + wbs = wbstartstruct; + + acceleratestage = 0; + cnt = bcnt = 0; + firstrefresh = 1; + me = wbs->pnum; + plrs = wbs->plyr; + + if (!wbs->maxkills) + wbs->maxkills = 1; + + if (!wbs->maxitems) + wbs->maxitems = 1; + + if (!wbs->maxsecret) + wbs->maxsecret = 1; + + if (gameversion < exe_ultimate) + if (wbs->epsd > 2) + wbs->epsd -= 3; +} + +void WI_Start(wbstartstruct_t *wbstartstruct) { + WI_initVariables(wbstartstruct); + WI_loadData(); + + if (deathmatch) + WI_initDeathmatchStats(); + else if (netgame) + WI_initNetgameStats(); + else + WI_initStats(); +} diff --git a/client/src/wi_stuff.h b/client/src/wi_stuff.h new file mode 100644 index 0000000..7a551d7 --- /dev/null +++ b/client/src/wi_stuff.h @@ -0,0 +1,47 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Intermission. +// + +#ifndef __WI_STUFF__ +#define __WI_STUFF__ + +//#include "v_video.h" + +#include "d_player.h" + +// States for the intermission + +typedef enum { + NoState = -1, + StatCount, + ShowNextLoc, +} stateenum_t; + +// Called by main loop, animate the intermission. +void WI_Ticker(void); + +// Called by main loop, +// draws the intermission directly into the screen buffer. +void WI_Drawer(void); + +// Setup for an intermission screen. +void WI_Start(wbstartstruct_t *wbstartstruct); + +// Shut down the intermission screen +void WI_End(void); + +#endif diff --git a/client/src/z_zone.c b/client/src/z_zone.c new file mode 100644 index 0000000..828fd7e --- /dev/null +++ b/client/src/z_zone.c @@ -0,0 +1,552 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Zone Memory Allocation. Neat. +// + +#include + +#include "doomtype.h" +#include "i_system.h" +#include "m_argv.h" + +#include "z_zone.h" + + +// +// ZONE MEMORY ALLOCATION +// +// There is never any space between memblocks, +// and there will never be two contiguous free memblocks. +// The rover can be left pointing at a non-empty block. +// +// It is of no value to free a cachable block, +// because it will get overwritten automatically if needed. +// + +#define MEM_ALIGN sizeof(void *) +#define ZONEID 0x1d4a11 + +typedef struct memblock_s +{ + int size; // including the header and possibly tiny fragments + void** user; + int tag; // PU_FREE if this is free + int id; // should be ZONEID + struct memblock_s* next; + struct memblock_s* prev; +} memblock_t; + + +typedef struct +{ + // total bytes malloced, including header + int size; + + // start / end cap for linked list + memblock_t blocklist; + + memblock_t* rover; + +} memzone_t; + + + +static memzone_t *mainzone; +static boolean zero_on_free; +static boolean scan_on_free; + + +// +// Z_ClearZone +// +void Z_ClearZone (memzone_t* zone) +{ + memblock_t* block; + + // set the entire zone to one free block + zone->blocklist.next = + zone->blocklist.prev = + block = (memblock_t *)( (byte *)zone + sizeof(memzone_t) ); + + zone->blocklist.user = (void *)zone; + zone->blocklist.tag = PU_STATIC; + zone->rover = block; + + block->prev = block->next = &zone->blocklist; + + // a free block. + block->tag = PU_FREE; + + block->size = zone->size - sizeof(memzone_t); +} + + + +// +// Z_Init +// +void Z_Init (void) +{ + memblock_t* block; + int size; + + mainzone = (memzone_t *)I_ZoneBase (&size); + mainzone->size = size; + + // set the entire zone to one free block + mainzone->blocklist.next = + mainzone->blocklist.prev = + block = (memblock_t *)( (byte *)mainzone + sizeof(memzone_t) ); + + mainzone->blocklist.user = (void *)mainzone; + mainzone->blocklist.tag = PU_STATIC; + mainzone->rover = block; + + block->prev = block->next = &mainzone->blocklist; + + // free block + block->tag = PU_FREE; + + block->size = mainzone->size - sizeof(memzone_t); + + // [Deliberately undocumented] + // Zone memory debugging flag. If set, memory is zeroed after it is freed + // to deliberately break any code that attempts to use it after free. + // + zero_on_free = M_ParmExists("-zonezero"); + + // [Deliberately undocumented] + // Zone memory debugging flag. If set, each time memory is freed, the zone + // heap is scanned to look for remaining pointers to the freed block. + // + scan_on_free = M_ParmExists("-zonescan"); +} + +// Scan the zone heap for pointers within the specified range, and warn about +// any remaining pointers. +static void ScanForBlock(void *start, void *end) +{ + memblock_t *block; + void **mem; + int i, len, tag; + + block = mainzone->blocklist.next; + + while (block->next != &mainzone->blocklist) + { + tag = block->tag; + + if (tag == PU_STATIC || tag == PU_LEVEL || tag == PU_LEVSPEC) + { + // Scan for pointers on the assumption that pointers are aligned + // on word boundaries (word size depending on pointer size): + mem = (void **) ((byte *) block + sizeof(memblock_t)); + len = (block->size - sizeof(memblock_t)) / sizeof(void *); + + for (i = 0; i < len; ++i) + { + if (start <= mem[i] && mem[i] <= end) + { + fprintf(stderr, + "%p has dangling pointer into freed block " + "%p (%p -> %p)\n", + mem, start, &mem[i], mem[i]); + } + } + } + + block = block->next; + } +} + +// +// Z_Free +// +void Z_Free (void* ptr) +{ + memblock_t* block; + memblock_t* other; + + block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); + + if (block->id != ZONEID) + I_Error ("Z_Free: freed a pointer without ZONEID"); + + if (block->tag != PU_FREE && block->user != NULL) + { + // clear the user's mark + *block->user = 0; + } + + // mark as free + block->tag = PU_FREE; + block->user = NULL; + block->id = 0; + + // If the -zonezero flag is provided, we zero out the block on free + // to break code that depends on reading freed memory. + if (zero_on_free) + { + memset(ptr, 0, block->size - sizeof(memblock_t)); + } + if (scan_on_free) + { + ScanForBlock(ptr, + (byte *) ptr + block->size - sizeof(memblock_t)); + } + + other = block->prev; + + if (other->tag == PU_FREE) + { + // merge with previous free block + other->size += block->size; + other->next = block->next; + other->next->prev = other; + + if (block == mainzone->rover) + mainzone->rover = other; + + block = other; + } + + other = block->next; + if (other->tag == PU_FREE) + { + // merge the next free block onto the end + block->size += other->size; + block->next = other->next; + block->next->prev = block; + + if (other == mainzone->rover) + mainzone->rover = block; + } +} + + + +// +// Z_Malloc +// You can pass a NULL user if the tag is < PU_PURGELEVEL. +// +#define MINFRAGMENT 64 + + +void* +Z_Malloc +( int size, + int tag, + void* user ) +{ + int extra; + memblock_t* start; + memblock_t* rover; + memblock_t* newblock; + memblock_t* base; + void *result; + + size = (size + MEM_ALIGN - 1) & ~(MEM_ALIGN - 1); + + // scan through the block list, + // looking for the first free block + // of sufficient size, + // throwing out any purgable blocks along the way. + + // account for size of block header + size += sizeof(memblock_t); + + // if there is a free block behind the rover, + // back up over them + base = mainzone->rover; + + if (base->prev->tag == PU_FREE) + base = base->prev; + + rover = base; + start = base->prev; + + do + { + if (rover == start) + { + // scanned all the way around the list + I_Error ("Z_Malloc: failed on allocation of %i bytes", size); + } + + if (rover->tag != PU_FREE) + { + if (rover->tag < PU_PURGELEVEL) + { + // hit a block that can't be purged, + // so move base past it + base = rover = rover->next; + } + else + { + // free the rover block (adding the size to base) + + // the rover can be the base block + base = base->prev; + Z_Free ((byte *)rover+sizeof(memblock_t)); + base = base->next; + rover = base->next; + } + } + else + { + rover = rover->next; + } + + } while (base->tag != PU_FREE || base->size < size); + + + // found a block big enough + extra = base->size - size; + + if (extra > MINFRAGMENT) + { + // there will be a free fragment after the allocated block + newblock = (memblock_t *) ((byte *)base + size ); + newblock->size = extra; + + newblock->tag = PU_FREE; + newblock->user = NULL; + newblock->prev = base; + newblock->next = base->next; + newblock->next->prev = newblock; + + base->next = newblock; + base->size = size; + } + + if (user == NULL && tag >= PU_PURGELEVEL) + I_Error ("Z_Malloc: an owner is required for purgable blocks"); + + base->user = user; + base->tag = tag; + + result = (void *) ((byte *)base + sizeof(memblock_t)); + + if (base->user) + { + *base->user = result; + } + + // next allocation will start looking here + mainzone->rover = base->next; + + base->id = ZONEID; + + return result; +} + + + +// +// Z_FreeTags +// +void +Z_FreeTags +( int lowtag, + int hightag ) +{ + memblock_t* block; + memblock_t* next; + + for (block = mainzone->blocklist.next ; + block != &mainzone->blocklist ; + block = next) + { + // get link before freeing + next = block->next; + + // free block? + if (block->tag == PU_FREE) + continue; + + if (block->tag >= lowtag && block->tag <= hightag) + Z_Free ( (byte *)block+sizeof(memblock_t)); + } +} + + + +// +// Z_DumpHeap +// Note: TFileDumpHeap( stdout ) ? +// +void +Z_DumpHeap +( int lowtag, + int hightag ) +{ + memblock_t* block; + + printf ("zone size: %i location: %p\n", + mainzone->size,mainzone); + + printf ("tag range: %i to %i\n", + lowtag, hightag); + + for (block = mainzone->blocklist.next ; ; block = block->next) + { + if (block->tag >= lowtag && block->tag <= hightag) + printf ("block:%p size:%7i user:%p tag:%3i\n", + block, block->size, block->user, block->tag); + + if (block->next == &mainzone->blocklist) + { + // all blocks have been hit + break; + } + + if ( (byte *)block + block->size != (byte *)block->next) + printf ("ERROR: block size does not touch the next block\n"); + + if ( block->next->prev != block) + printf ("ERROR: next block doesn't have proper back link\n"); + + if (block->tag == PU_FREE && block->next->tag == PU_FREE) + printf ("ERROR: two consecutive free blocks\n"); + } +} + + +// +// Z_FileDumpHeap +// +void Z_FileDumpHeap (FILE* f) +{ + memblock_t* block; + + fprintf (f,"zone size: %i location: %p\n",mainzone->size,mainzone); + + for (block = mainzone->blocklist.next ; ; block = block->next) + { + fprintf (f,"block:%p size:%7i user:%p tag:%3i\n", + block, block->size, block->user, block->tag); + + if (block->next == &mainzone->blocklist) + { + // all blocks have been hit + break; + } + + if ( (byte *)block + block->size != (byte *)block->next) + fprintf (f,"ERROR: block size does not touch the next block\n"); + + if ( block->next->prev != block) + fprintf (f,"ERROR: next block doesn't have proper back link\n"); + + if (block->tag == PU_FREE && block->next->tag == PU_FREE) + fprintf (f,"ERROR: two consecutive free blocks\n"); + } +} + + + +// +// Z_CheckHeap +// +void Z_CheckHeap (void) +{ + memblock_t* block; + + for (block = mainzone->blocklist.next ; ; block = block->next) + { + if (block->next == &mainzone->blocklist) + { + // all blocks have been hit + break; + } + + if ( (byte *)block + block->size != (byte *)block->next) + I_Error ("Z_CheckHeap: block size does not touch the next block\n"); + + if ( block->next->prev != block) + I_Error ("Z_CheckHeap: next block doesn't have proper back link\n"); + + if (block->tag == PU_FREE && block->next->tag == PU_FREE) + I_Error ("Z_CheckHeap: two consecutive free blocks\n"); + } +} + + + + +// +// Z_ChangeTag +// +void Z_ChangeTag2(void *ptr, int tag, char *file, int line) +{ + memblock_t* block; + + block = (memblock_t *) ((byte *)ptr - sizeof(memblock_t)); + + if (block->id != ZONEID) + I_Error("%s:%i: Z_ChangeTag: block without a ZONEID!", + file, line); + + if (tag >= PU_PURGELEVEL && block->user == NULL) + I_Error("%s:%i: Z_ChangeTag: an owner is required " + "for purgable blocks", file, line); + + block->tag = tag; +} + +void Z_ChangeUser(void *ptr, void **user) +{ + memblock_t* block; + + block = (memblock_t *) ((byte *)ptr - sizeof(memblock_t)); + + if (block->id != ZONEID) + { + I_Error("Z_ChangeUser: Tried to change user for invalid block!"); + } + + block->user = user; + *user = ptr; +} + + + +// +// Z_FreeMemory +// +int Z_FreeMemory (void) +{ + memblock_t* block; + int free; + + free = 0; + + for (block = mainzone->blocklist.next ; + block != &mainzone->blocklist; + block = block->next) + { + if (block->tag == PU_FREE || block->tag >= PU_PURGELEVEL) + free += block->size; + } + + return free; +} + +unsigned int Z_ZoneSize(void) +{ + return mainzone->size; +} + diff --git a/client/src/z_zone.h b/client/src/z_zone.h new file mode 100644 index 0000000..526f30d --- /dev/null +++ b/client/src/z_zone.h @@ -0,0 +1,73 @@ +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005-2014 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Zone Memory Allocation, perhaps NeXT ObjectiveC inspired. +// Remark: this was the only stuff that, according +// to John Carmack, might have been useful for +// Quake. +// + + + +#ifndef __Z_ZONE__ +#define __Z_ZONE__ + +#include + +// +// ZONE MEMORY +// PU - purge tags. + +enum +{ + PU_STATIC = 1, // static entire execution time + PU_SOUND, // static while playing + PU_MUSIC, // static while playing + PU_FREE, // a free block + PU_LEVEL, // static until level exited + PU_LEVSPEC, // a special thinker in a level + + // Tags >= PU_PURGELEVEL are purgable whenever needed. + + PU_PURGELEVEL, + PU_CACHE, + + // Total number of different tag types + + PU_NUM_TAGS +}; + + +void Z_Init (void); +void* Z_Malloc (int size, int tag, void *ptr); +void Z_Free (void *ptr); +void Z_FreeTags (int lowtag, int hightag); +void Z_DumpHeap (int lowtag, int hightag); +void Z_FileDumpHeap (FILE *f); +void Z_CheckHeap (void); +void Z_ChangeTag2 (void *ptr, int tag, char *file, int line); +void Z_ChangeUser(void *ptr, void **user); +int Z_FreeMemory (void); +unsigned int Z_ZoneSize(void); + +// +// This is used to get the local FILE:LINE info from CPP +// prior to really call the function in question. +// +#define Z_ChangeTag(p,t) \ + Z_ChangeTag2((p), (t), __FILE__, __LINE__) + + +#endif diff --git a/common/streaming.h b/common/streaming.h new file mode 100644 index 0000000..752e9ba --- /dev/null +++ b/common/streaming.h @@ -0,0 +1,22 @@ +#ifndef __COMMON_STREAMING__ +#define __COMMON_STREAMING__ + + +#define STREAMPORT (6666) +#define STREAMWIDTH (320) +#define STREAMLINESPERMESSAGE (2) + +#define STREAMSCANSIZE (STREAMLINESPERMESSAGE*STREAMWIDTH) + +typedef struct msg_ { + unsigned char type; + unsigned char idx; + unsigned char data[STREAMSCANSIZE]; +} StreamMsg; + + +#define MSGT_HELLO (0) +#define MSGT_SCANLINE (1) + + +#endif // __COMMON_STREAMING__ \ No newline at end of file diff --git a/src/d_main.c b/src/d_main.c index 4cb0dfe..c5a3569 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -70,6 +70,7 @@ #include "ipu_host.h" #include "ipu/ipu_interface.h" +#include "streaming.h" // // D-DoomLoop() @@ -826,7 +827,8 @@ void D_DoomMain(void) { char demolumpname[9]; // int numiwadlumps; - // print banner + // JOSEF + wait_for_client_connect(); printf("Z_Init: Init zone memory allocation daemon. \n"); Z_Init(); diff --git a/src/i_video.c b/src/i_video.c index fcfe8cb..a451d88 100644 --- a/src/i_video.c +++ b/src/i_video.c @@ -57,6 +57,8 @@ #include "w_wad.h" #include "z_zone.h" +#include "streaming.h" + // These are (1) the window (or the full screen) that our game is rendered to // and (2) the renderer that scales the texture (see below) into this window. @@ -787,6 +789,11 @@ void I_FinishUpdate (void) } } + // JOSEF: Streaming + for (int i = 0; i < SCREENHEIGHT; i += STREAMLINESPERMESSAGE) { + send_scanline(I_VideoBuffer, i); + } + if (palette_to_set) { SDL_SetPaletteColors(screenbuffer->format->palette, palette, 0, 256); diff --git a/src/streaming.c b/src/streaming.c new file mode 100644 index 0000000..181a097 --- /dev/null +++ b/src/streaming.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "streaming.h" + + +static int sockfd; +static struct sockaddr_in cliaddr; +static unsigned int clientaddr_len; + + +// Driver code +void wait_for_client_connect() { + + StreamMsg msg; + struct sockaddr_in servaddr; + + // Creating socket file descriptor + if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) { + perror("socket creation failed"); + exit(EXIT_FAILURE); + } + + memset(&servaddr, 0, sizeof(servaddr)); + memset(&cliaddr, 0, sizeof(cliaddr)); + + // Filling server information + servaddr.sin_family = AF_INET; // IPv4 + servaddr.sin_addr.s_addr = INADDR_ANY; + servaddr.sin_port = htons(STREAMPORT); + + // Bind the socket with the server address + if (bind(sockfd, (const struct sockaddr *)&servaddr, + sizeof(servaddr)) < 0) { + perror("bind failed"); + exit(EXIT_FAILURE); + } + + clientaddr_len = sizeof(cliaddr); + recvfrom( + sockfd, + (char *)(&msg), sizeof(msg), + MSG_WAITALL, + (struct sockaddr *) &cliaddr, &clientaddr_len + ); + if (msg.type != MSGT_HELLO) { + perror("Client said something other than hello?"); + exit(EXIT_FAILURE); + } + printf("Client says hello\n"); + + msg.type = MSGT_HELLO; + sendto( + sockfd, + (char *)(&msg), sizeof(msg), + MSG_CONFIRM, + (const struct sockaddr *) &cliaddr, clientaddr_len + ); + + return; +} + + +void send_scanline(unsigned char *data, unsigned char linenum) { + StreamMsg msg; + msg.type = MSGT_SCANLINE; + msg.idx = linenum; + memcpy(msg.data, &data[linenum * STREAMWIDTH], STREAMSCANSIZE); + ssize_t sent = sendto( + sockfd, + (char *)(&msg), sizeof(msg), + MSG_CONFIRM, + (const struct sockaddr *) &cliaddr, clientaddr_len + ); + if (sent == -1) { + perror("Failed to send scanline"); + exit(EXIT_FAILURE); + } + return; +} diff --git a/src/streaming.h b/src/streaming.h new file mode 100644 index 0000000..22b1282 --- /dev/null +++ b/src/streaming.h @@ -0,0 +1,10 @@ +#ifndef __STREAMING__ +#define __STREAMING__ + +#include "../common/streaming.h" + +void wait_for_client_connect(); +void send_scanline(unsigned char *data, unsigned char linenum); + + +#endif // __STREAMING__ \ No newline at end of file