diff options
38 files changed, 1630 insertions, 316 deletions
diff --git a/Makefile.am b/Makefile.am index 97a18507..09fc5868 100644 --- a/Makefile.am +++ b/Makefile.am @@ -53,6 +53,7 @@ EXTRA_DIST= \ README.OPL \ TODO \ BUGS \ + NOT-BUGS \ rpm.spec MAINTAINERCLEANFILES = $(AUX_DIST_GEN) @@ -4,8 +4,19 @@ * The DOSbox OPL emulator (DBOPL) has been imported to replace the older FMOPL code. The quality of OPL emulation is now therefore much better. + * The game can now run in screen modes at any color depth (not + just 8-bit modes). This is mainly to work around problems with + Windows Vista/7, where 8-bit color modes don't always work + properly. + * Multiplayer servers now register themselves with an Internet + master server. Use the -search command line parameter to + find servers on the Internet to play on. You can also use + DoomSeeker (http://skulltag.net/doomseeker/) which supports + this functionality. * When running in windowed mode, it is now possible to dynamically resize the window by dragging the window borders. + * Names can be specified for servers with the -servername command + line parameter. * There are now keyboard, mouse and joystick bindings to cycle through available weapons, making play with joypads or mobile devices (ie. without a proper keyboard) much more practical. @@ -15,19 +26,50 @@ port, for cards that don't use port 0x388. * Up to 8 mouse buttons are now supported (including the mousewheel). + * The Python scripts used for building Chocolate Doom now work + with Python 3 (but also continue to work with Python 2) + (thanks arin). + * The font used for the textscreen library can be forced by + setting the TEXTSCREEN_FONT environment variable to "small" or + "normal". + * There is now a NOT-BUGS file included that lists some common + Vanilla Doom bugs/limitations that you might encounter. + + Compatibility: + * The -timer and -avg options now work the same as Vanilla when + playing back demos (thanks xttl) + * A texture lookup bug was fixed that caused the wrong sky to be + displayed in Spooky01.wad (thanks Porsche Monty). + * The HacX v1.2 IWAD file is now supported, and can be used + standalone without the need for the Doom II IWAD (thanks + atyth). Bugs fixed: + * A workaround has been a bug in old versions of SDL_mixer + (v1.2.8 and earlier) that could cause the game to lock up. + Please upgrade to a newer version if you haven't already. * It is now possible to use OPL emulation at 11025Hz sound - sampling rate (thanks to the new OPL emulator). + sampling rate, due to the new OPL emulator (thanks Porsche + Monty). * The span renderer function (used for drawing floors and ceilings) now behaves the same as Vanilla Doom, so screenshots are pixel-perfect identical to Vanilla Doom (thanks Porsche Monty). * The zone memory system now aligns allocated memory to 8-byte boundaries on 64-bit systems, which may fix crashes on systems - such as sparc64. + such as sparc64 (thanks Ryan Freeman and Edd Barrett). * The configure script now checks for libm, fixing compile - problems on Fedora Linux. + problems on Fedora Linux (thanks Sander van Dijk). + * Sound distortion with certain music files when played back + using OPL (eg. Heretic title screen). + * Error in Windows when reading response files (thanks Porsche + Monty, xttl, Janizdreg). + * Windows Vista/7 8-bit color mode issues (the default is now to + run in 32-bit color depth on these versions) (thanks to + everybody who reported this and helped test the fix). + * Screen borders no longer flash when running on widescreen + monitors, if you choose a true-color screen mode (thanks + exp(x)). 1.4.0 (2010-07-10): diff --git a/NOT-BUGS b/NOT-BUGS new file mode 100644 index 00000000..8dbf7262 --- /dev/null +++ b/NOT-BUGS @@ -0,0 +1,112 @@ + +The aim of Chocolate Doom is to behave as closely to Vanilla Doom as +possible. As a result, you may experience problems that you would +also experience when using Vanilla Doom. These are not "bugs" as +Chocolate Doom is behaving as intended. + +This is not intended to be a comprehensive list of Vanilla Doom bugs. +For more information, consult the "engine bugs" page of the Doom Wiki. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +== Game exits after title screen with message about game version == + +The game may exit after the title screen is shown, with a message like +the following: + + Demo is from a different game version! + (read 106, should be 109) + + *** You may need to upgrade your version of Doom to v1.9. *** + See: http://doomworld.com/files/patches.shtml + This appears to be v1.6/v1.666. + +This usually indicates that your IWAD file that you are using to play +the game (usually named doom.wad or doom2.wad) is out of date. +Chocolate Doom only supports the v1.9 IWAD file. + +To fix the problem, you must upgrade to the v1.9 IWAD file. The URL +in the message has downloadable upgrade patches that you can use to +upgrade. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +== Game exits when accessing the options menu == + +The game may exit with the message "Bad V_DrawPatch" when accessing +the options menu, if you have your mouse sensitivity set high. + +The Doom options menu has a slider that allows the mouse sensitivity +to be controlled; however, it has only a very limited range. It is +common for experienced players to set a mouse sensitivity that is much +higher than what can be set via the options menu. The setup program +allows a larger range of values to be set. + +However, setting very high sensitivity values causes the game to exit +when accessing the options menu under Vanilla Doom. Because Chocolate +Doom aims to emulate Vanilla Doom as closely as possible, it does the +same thing. + +One solution to the problem is to set a lower mouse sensitivity. +Alternatively, all of the settings in the options menu can be +controlled through Doom's key bindings anyway: + + End game: F7 + Messages on/off: F8 + Graphic detail high/low: F5 + Screen size smaller/larger: -/+ + Sound volume menu: F4 + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +== Game exits with "Savegame buffer overrun" when saving the game == + +If you are playing on a particularly large level, it is possible that +when you save the game, the game will quit with the message "Savegame +buffer overrun". + +Vanilla Doom has a limited size memory bufferthat it uses for saving +games. If you are playing on a large level, the buffer may be too +small for the entire savegame to fit. Chocolate Doom allows the limit +to be disabled: in the setup tool, go to the "compatibility" menu and +disable the "Vanilla savegame limit" option. + +If this error happens to you, your game has not been lost! A file +named temp.dsg is saved; rename this to doomsav0.dsg to make it appear +in the first slot in the "load game" menu. (On Unix systems, you will +need to look in the .chocolate-doom/savegames directory in your home +directory) + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +== Game ends suddenly when recording a demo == + +If you are recording a very long demo, the game may exit suddenly. +Vanilla Doom has a limited size memory buffer that it uses to save the +demo into. When the buffer is full, the game exits. You can tell if +this happens, as the demo file will be around 131,072 bytes in size. + +You can work around this by using the -maxdemo command line parameter +to specify a larger buffer size. Alternatively, the limit can be +disabled: in the setup tool, go to the compatibility menu and disable +the "Vanilla demo limit" option. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +== Game exits with a message about "visplanes" == + +The game may exit with one of these messages: + + R_FindPlane: no more visplanes + R_DrawPlanes: visplane overflow (129) + +This is known as the "visplane overflow" limit and is one of the most +well-known Vanilla Doom engine limits. You should only ever experience +this when trying to play an add-on level. The level you are trying to +play is too complex; it was most likely designed to work with a limit +removing source port. + +More information can be found here: + + http://rome.ro/lee_killough/editing/visplane.shtml + @@ -65,9 +65,13 @@ Here are some examples: You are encouraged to sign up and contribute any useful information you may have regarding the port! - * Chocolate Doom is not perfect. See the BUGS file for a list of - known issues. New bug reports can be submitted to the Chocolate - Doom bug tracker on Sourceforge. See: + * Chocolate Doom is not perfect. See the BUGS file for a list of + known issues. Because of the nature of the project, you may also + encounter Vanilla Doom bugs; these are intentionally present; see + the NOT-BUGS file for more information. + + New bug reports can be submitted to the Chocolate Doom bug tracker + on Sourceforge. See: http://sourceforge.net/projects/chocolate-doom diff --git a/data/convert-icon b/data/convert-icon index c651bc7f..30fade20 100755 --- a/data/convert-icon +++ b/data/convert-icon @@ -1,6 +1,4 @@ -#!/usr/bin/python -# -# $Id: convert-icon 704 2006-10-18 00:51:11Z fraggle $ +#!/usr/bin/env python # # Copyright(C) 2005 Simon Howard # @@ -29,7 +27,7 @@ import re try: import Image except ImportError: - print "WARNING: Could not update %s. Please install the Python Imaging library." % sys.argv[2] + print("WARNING: Could not update %s. Please install the Python Imaging library." % sys.argv[2]) sys.exit(0) @@ -71,4 +69,3 @@ def convert_image(filename, output_filename): convert_image(sys.argv[1], sys.argv[2]) - @@ -150,11 +150,8 @@ config_files = {} show_vanilla_options = True class Parameter: - def __cmp__(self, other): - if self.name < other.name: - return -1 - else: - return 1 + def __lt__(self, other): + return self.name < other.name def __init__(self): self.text = "" @@ -389,7 +386,7 @@ def print_template(template_file, content): try: for line in f: line = line.replace("@content", content) - print line.rstrip() + print(line.rstrip()) finally: f.close() @@ -407,7 +404,7 @@ def wiki_output(targets, template): read_wikipages() for t in targets: - print t.wiki_output() + print(t.wiki_output()) def plaintext_output(targets, template_file): @@ -419,13 +416,13 @@ def plaintext_output(targets, template_file): print_template(template_file, content) def usage(): - print "Usage: %s [-V] [-c filename ]( -m | -w | -p ) <directory>" \ - % sys.argv[0] - print " -c : Provide documentation for the specified configuration file" - print " -m : Manpage output" - print " -w : Wikitext output" - print " -p : Plaintext output" - print " -V : Don't show Vanilla Doom options" + print("Usage: %s [-V] [-c filename ]( -m | -w | -p ) <directory>" \ + % sys.argv[0]) + print(" -c : Provide documentation for the specified configuration file") + print(" -m : Manpage output") + print(" -w : Wikitext output") + print(" -p : Plaintext output") + print(" -V : Don't show Vanilla Doom options") sys.exit(0) # Parse command line diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index f6a3b229..5059fb5e 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -176,8 +176,8 @@ static void FillBuffer(int16_t *buffer, unsigned int nsamples) for (i=0; i<nsamples; ++i) { - buffer[i * 2] = (int16_t) (mix_buffer[i] * 2); - buffer[i * 2 + 1] = (int16_t) (mix_buffer[i] * 2); + buffer[i * 2] = (int16_t) mix_buffer[i]; + buffer[i * 2 + 1] = (int16_t) mix_buffer[i]; } } diff --git a/pkg/config.make.in b/pkg/config.make.in index dc2d2888..27e44bd8 100644 --- a/pkg/config.make.in +++ b/pkg/config.make.in @@ -25,6 +25,7 @@ DOC_FILES = README \ INSTALL \ NEWS \ BUGS \ + NOT-BUGS \ CMDLINE \ TODO diff --git a/pkg/wince/wince-cabgen b/pkg/wince/wince-cabgen index 97cba132..76845cf9 100755 --- a/pkg/wince/wince-cabgen +++ b/pkg/wince/wince-cabgen @@ -3,10 +3,11 @@ import os import re import shutil +import struct import sys import tempfile -CAB_HEADER = "MSCE" +CAB_HEADER = "MSCE".encode("ascii") ARCHITECTURES = { "shx-sh3": 103, @@ -58,16 +59,10 @@ DIR_VARIABLES = { } def write_int16(f, value): - b1 = value & 0xff - b2 = (value >> 8) & 0xff - f.write("%c%c" % (b1, b2)) + f.write(struct.pack("<H", value)) def write_int32(f, value): - b1 = value & 0xff - b2 = (value >> 8) & 0xff - b3 = (value >> 16) & 0xff - b4 = (value >> 24) & 0xff - f.write("%c%c%c%c" % (b1, b2, b3, b4)) + f.write(struct.pack("<I", value)) # Pad a string with NUL characters so that it has a length that is # a multiple of 4. At least one NUL is always added. @@ -208,7 +203,7 @@ class StringDictionary: for i, s in self.string_list: write_int16(stream, i) write_int16(stream, len(s)) - stream.write(s) + stream.write(s.encode("ascii")) class DirectoryList: def __init__(self, cab_header): @@ -252,7 +247,7 @@ class DirectoryList: # dir_path = dir_path[1:] dir_path = [ dir ] - dir_path = map(lambda x: dictionary.get(x), dir_path) + dir_path = list(map(lambda x: dictionary.get(x), dir_path)) self.directories[key] = self.index self.directories_list.append((self.index, dir_path)) @@ -334,7 +329,7 @@ class FileList: write_int16(stream, file_no) write_int32(stream, flags) write_int16(stream, len(filename)) - stream.write(filename) + stream.write(filename.encode("ascii")) # TODO? @@ -412,7 +407,7 @@ class LinkList: # Map dirs that make up the path to strings. dictionary = self.cab_header.dictionary - dest_path = map(lambda x: dictionary.get(x), dest_path) + dest_path = list(map(lambda x: dictionary.get(x), dest_path)) self.links.append((self.index, target_type, target_id, base_dir, dest_path)) @@ -492,6 +487,7 @@ class CabHeaderFile: section.write(stream) pos = stream.tell() if pos != old_pos + len(section): + print(section) raise Exception("Section is %i bytes long, but %i written" % \ (len(section), pos - old_pos)) old_pos = pos @@ -574,7 +570,7 @@ class CabFile: basename = self.__shorten_name(self.files[0], 0) filename = os.path.join(dir, basename) - stream = file(filename, "w") + stream = open(filename, "wb") self.cab_header.write(stream) stream.close() @@ -625,17 +621,17 @@ def expand_path(filename): # Expand $(xyz) path variables to their Windows equivalents: def replace_var(match): - var_name = match.group(1) + var_name = match.group(1) - if not var_name in DIR_VARIABLES: - raise Exception("Unknown variable '%s'" % var_name) - else: - return DIR_VARIABLES[var_name] + if not var_name in DIR_VARIABLES: + raise Exception("Unknown variable '%s'" % var_name) + else: + return DIR_VARIABLES[var_name] return re.sub(r"\$\((.*?)\)", replace_var, filename) def read_config_file(filename): - f = file(filename) + f = open(filename) data = f.readlines() data = "".join(data) @@ -656,10 +652,10 @@ def print_dependencies(filename): files_list = config["files"] for dest, source_file in files_list.items(): - print source_file + print(source_file) if len(sys.argv) < 3: - print "Usage: %s <config file> <output file>" % sys.argv[0] + print("Usage: %s <config file> <output file>" % sys.argv[0]) sys.exit(0) if sys.argv[1] == "-d": diff --git a/src/Makefile.am b/src/Makefile.am index 55901885..6c0adfae 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -33,6 +33,7 @@ net_dedicated.c net_dedicated.h \ net_io.c net_io.h \ net_packet.c net_packet.h \ net_sdl.c net_sdl.h \ +net_query.c net_query.h \ net_server.c net_server.h \ net_structrw.c net_structrw.h \ z_native.c z_zone.h diff --git a/src/d_iwad.c b/src/d_iwad.c index ab1dd823..9bf53b9a 100644 --- a/src/d_iwad.c +++ b/src/d_iwad.c @@ -47,6 +47,7 @@ static iwad_t iwads[] = { "doom.wad", doom, retail, "Doom" }, { "doom1.wad", doom, shareware, "Doom Shareware" }, { "chex.wad", doom, shareware, "Chex Quest" }, + { "hacx.wad", doom2, commercial, "Hacx" }, { "heretic.wad", heretic, retail, "Heretic" }, { "heretic1.wad", heretic, shareware, "Heretic Shareware" }, { "hexen.wad", hexen, commercial, "Hexen" }, diff --git a/src/d_mode.h b/src/d_mode.h index f7e93562..29f61c10 100644 --- a/src/d_mode.h +++ b/src/d_mode.h @@ -62,6 +62,7 @@ typedef enum typedef enum { exe_doom_1_9, // Doom 1.9: used for shareware, registered and commercial + exe_hacx, // Hacx exe_ultimate, // Ultimate Doom (retail) exe_final, // Final Doom exe_chex, // Chex Quest executable (based on Final Doom) diff --git a/src/deh_io.c b/src/deh_io.c index 3386a6fa..92c81632 100644 --- a/src/deh_io.c +++ b/src/deh_io.c @@ -30,21 +30,63 @@ #include <string.h> #include "i_system.h" +#include "w_wad.h" #include "z_zone.h" #include "deh_defs.h" #include "deh_io.h" +typedef enum +{ + DEH_INPUT_FILE, + DEH_INPUT_LUMP +} deh_input_type_t; + struct deh_context_s { - FILE *stream; + deh_input_type_t type; char *filename; + + // If the input comes from a memory buffer, pointer to the memory + // buffer. + + unsigned char *input_buffer; + size_t input_buffer_len; + unsigned int input_buffer_pos; + int lumpnum; + + // If the input comes from a file, the file stream for reading + // data. + + FILE *stream; + + // Current line number that we have reached: + int linenum; + + // Used by DEH_ReadLine: + boolean last_was_newline; char *readbuffer; int readbuffer_size; }; +static deh_context_t *DEH_NewContext(void) +{ + deh_context_t *context; + + context = Z_Malloc(sizeof(*context), PU_STATIC, NULL); + + // Initial read buffer size of 128 bytes + + context->readbuffer_size = 128; + context->readbuffer = Z_Malloc(context->readbuffer_size, PU_STATIC, NULL); + context->linenum = 0; + context->last_was_newline = true; + + return context; +} + // Open a dehacked file for reading // Returns NULL if open failed @@ -52,22 +94,41 @@ deh_context_t *DEH_OpenFile(char *filename) { FILE *fstream; deh_context_t *context; - + fstream = fopen(filename, "r"); if (fstream == NULL) return NULL; - context = Z_Malloc(sizeof(*context), PU_STATIC, NULL); + context = DEH_NewContext(); + + context->type = DEH_INPUT_FILE; context->stream = fstream; - - // Initial read buffer size of 128 bytes + context->filename = strdup(filename); - context->readbuffer_size = 128; - context->readbuffer = Z_Malloc(context->readbuffer_size, PU_STATIC, NULL); - context->filename = filename; - context->linenum = 0; - context->last_was_newline = true; + return context; +} + +// Open a WAD lump for reading. + +deh_context_t *DEH_OpenLump(int lumpnum) +{ + deh_context_t *context; + void *lump; + + lump = W_CacheLumpNum(lumpnum, PU_STATIC); + + context = DEH_NewContext(); + + context->type = DEH_INPUT_LUMP; + context->lumpnum = lumpnum; + context->input_buffer = lump; + context->input_buffer_len = W_LumpLength(lumpnum); + context->input_buffer_pos = 0; + + context->filename = malloc(9); + strncpy(context->filename, lumpinfo[lumpnum].name, 8); + context->filename[8] = '\0'; return context; } @@ -76,33 +137,67 @@ deh_context_t *DEH_OpenFile(char *filename) void DEH_CloseFile(deh_context_t *context) { - fclose(context->stream); + if (context->type == DEH_INPUT_FILE) + { + fclose(context->stream); + } + else if (context->type == DEH_INPUT_LUMP) + { + W_ReleaseLumpNum(context->lumpnum); + } + Z_Free(context->readbuffer); Z_Free(context); } +int DEH_GetCharFile(deh_context_t *context) +{ + if (feof(context->stream)) + { + // end of file + + return -1; + } + + return fgetc(context->stream); +} + +int DEH_GetCharLump(deh_context_t *context) +{ + int result; + + if (context->input_buffer_pos >= context->input_buffer_len) + { + return -1; + } + + result = context->input_buffer[context->input_buffer_pos]; + ++context->input_buffer_pos; + + return result; +} + // Reads a single character from a dehacked file int DEH_GetChar(deh_context_t *context) { int result; - + // Read characters, but ignore carriage returns // Essentially this is a DOS->Unix conversion - do + do { - if (feof(context->stream)) + switch (context->type) { - // end of file + case DEH_INPUT_FILE: + result = DEH_GetCharFile(context); + break; - result = -1; + case DEH_INPUT_LUMP: + result = DEH_GetCharLump(context); + break; } - else - { - result = fgetc(context->stream); - } - } while (result == '\r'); // Track the current line number @@ -111,9 +206,9 @@ int DEH_GetChar(deh_context_t *context) { ++context->linenum; } - + context->last_was_newline = result == '\n'; - + return result; } diff --git a/src/deh_io.h b/src/deh_io.h index 061a5a0e..9d22b360 100644 --- a/src/deh_io.h +++ b/src/deh_io.h @@ -30,6 +30,7 @@ #include "deh_defs.h" deh_context_t *DEH_OpenFile(char *filename); +deh_context_t *DEH_OpenLump(int lumpnum); void DEH_CloseFile(deh_context_t *context); int DEH_GetChar(deh_context_t *context); char *DEH_ReadLine(deh_context_t *context); diff --git a/src/deh_main.c b/src/deh_main.c index 75934087..39d59e8c 100644 --- a/src/deh_main.c +++ b/src/deh_main.c @@ -32,6 +32,7 @@ #include "doomtype.h" #include "d_iwad.h" #include "m_argv.h" +#include "w_wad.h" #include "deh_defs.h" #include "deh_io.h" @@ -246,9 +247,6 @@ static void DEH_ParseContext(deh_context_t *context) DEH_Error(context, "This is not a valid dehacked patch file!"); } - deh_allow_long_strings = false; - deh_allow_long_cheats = false; - // Read the file for (;;) @@ -260,7 +258,9 @@ static void DEH_ParseContext(deh_context_t *context) // end of file? if (line == NULL) + { return; + } while (line[0] != '\0' && isspace(line[0])) ++line; @@ -347,6 +347,48 @@ int DEH_LoadFile(char *filename) return 1; } +// Load dehacked file from WAD lump. + +int DEH_LoadLump(int lumpnum) +{ + deh_context_t *context; + + // If it's in a lump, it's probably designed for a modern source port, + // so allow it to do long string and cheat replacements. + + deh_allow_long_strings = true; + deh_allow_long_cheats = true; + + context = DEH_OpenLump(lumpnum); + + if (context == NULL) + { + fprintf(stderr, "DEH_LoadFile: Unable to open lump %i\n", lumpnum); + return 0; + } + + DEH_ParseContext(context); + + DEH_CloseFile(context); + + return 1; +} + +int DEH_LoadLumpByName(char *name) +{ + int lumpnum; + + lumpnum = W_CheckNumForName(name); + + if (lumpnum == -1) + { + fprintf(stderr, "DEH_LoadLumpByName: '%s' lump not found\n", name); + return 0; + } + + return DEH_LoadLump(lumpnum); +} + // Checks the command line for -deh argument void DEH_Init(void) @@ -387,4 +429,3 @@ void DEH_Init(void) } } - diff --git a/src/deh_main.h b/src/deh_main.h index f9cb44ca..9ac2c6c7 100644 --- a/src/deh_main.h +++ b/src/deh_main.h @@ -41,6 +41,8 @@ void DEH_Init(void); int DEH_LoadFile(char *filename); +int DEH_LoadLump(int lumpnum); +int DEH_LoadLumpByName(char *name); boolean DEH_ParseAssignment(char *line, char **variable_name, char **value); diff --git a/src/doom/d_main.c b/src/doom/d_main.c index fed6a1b4..69dd6e9f 100644 --- a/src/doom/d_main.c +++ b/src/doom/d_main.c @@ -122,8 +122,6 @@ int startmap; boolean autostart; int startloadgame; -FILE* debugfile; - boolean advancedemo; // Store demo, do not accept any inputs @@ -424,14 +422,6 @@ void D_DoomLoop (void) if (demorecording) G_BeginRecording (); - if (M_CheckParm ("-debugfile")) - { - char filename[20]; - sprintf (filename,"debug%i.txt",consoleplayer); - printf ("debug output to: %s\n",filename); - debugfile = fopen (filename,"w"); - } - TryRunTics(); I_SetWindowTitle(gamedescription); @@ -611,8 +601,12 @@ void D_StartTitle (void) // These are from the original source: some of them are perhaps // not used in any dehacked patches -static char *banners[] = +static char *banners[] = { + // doom2.wad + " " + "DOOM 2: Hell on Earth v%i.%i" + " ", // doom1.wad " " "DOOM Shareware Startup v%i.%i" @@ -629,10 +623,6 @@ static char *banners[] = " " "The Ultimate DOOM Startup v%i.%i" " ", - // doom2.wad - " " - "DOOM 2: Hell on Earth v%i.%i" - " ", // tnt.wad " " "DOOM 2: TNT - Evilution v%i.%i" @@ -833,6 +823,18 @@ static boolean CheckChex(char *iwadname) chex_iwadname)); } +// Check if the IWAD file is the Hacx IWAD. +// Returns true if this is hacx.wad. + +static boolean CheckHacx(char *iwadname) +{ + char *hacx_iwadname = "hacx.wad"; + + return (strlen(iwadname) > strlen(hacx_iwadname) + && !strcasecmp(iwadname + strlen(iwadname) - strlen(hacx_iwadname), + hacx_iwadname)); +} + // print title for every printed line char title[128]; @@ -903,6 +905,7 @@ static struct GameVersion_t version; } gameversions[] = { {"Doom 1.9", "1.9", exe_doom_1_9}, + {"Hacx", "hacx", exe_hacx}, {"Ultimate Doom", "ultimate", exe_ultimate}, {"Final Doom", "final", exe_final}, {"Chex Quest", "chex", exe_chex}, @@ -960,6 +963,12 @@ static void InitGameVersion(void) gameversion = exe_chex; } + else if (CheckHacx(iwadfile)) + { + // hacx exe: identified by iwad filename + + gameversion = exe_hacx; + } else if (gamemode == shareware || gamemode == registered) { // original @@ -1060,6 +1069,20 @@ static void D_Endoom(void) I_Endoom(endoom); } +static void LoadHacxDeh(void) +{ + // If this is the HACX IWAD, we need to load the DEHACKED lump. + + if (gameversion == exe_hacx) + { + if (!DEH_LoadLumpByName("DEHACKED")) + { + I_Error("DEHACKED lump not found. Please check that this is the " + "Hacx v1.2 IWAD."); + } + } +} + // // D_DoomMain // @@ -1097,6 +1120,21 @@ void D_DoomMain (void) } //! + // @category net + // + // Query the Internet master server for a global list of active + // servers. + // + + if (M_CheckParm("-search")) + { + printf("\nSearching for servers on Internet ...\n"); + p = NET_MasterQuery(NET_QueryPrintCallback, NULL); + printf("\n%i server(s) found.\n", p); + exit(0); + } + + //! // @arg <address> // @category net // @@ -1109,6 +1147,7 @@ void D_DoomMain (void) if (p > 0) { NET_QueryAddress(myargv[p+1]); + exit(0); } //! @@ -1117,8 +1156,13 @@ void D_DoomMain (void) // Search the local LAN for running servers. // - if (M_CheckParm("-search")) - NET_LANQuery(); + if (M_CheckParm("-localsearch")) + { + printf("\nSearching for servers on local LAN ...\n"); + p = NET_LANQuery(NET_QueryPrintCallback, NULL); + printf("\n%i server(s) found.\n", p); + exit(0); + } #endif @@ -1365,6 +1409,7 @@ void D_DoomMain (void) D_IdentifyVersion(); InitGameVersion(); LoadChexDeh(); + LoadHacxDeh(); D_SetGameDescription(); SetSaveGameDir(iwadfile); @@ -1443,10 +1488,9 @@ void D_DoomMain (void) p = M_CheckParm ("-timer"); - if (p && p < myargc-1 && deathmatch) + if (p && p < myargc-1) { timelimit = atoi(myargv[p+1]); - printf("timer: %i\n", timelimit); } //! @@ -1458,10 +1502,8 @@ void D_DoomMain (void) p = M_CheckParm ("-avg"); - if (p && p < myargc-1 && deathmatch) + if (p && p < myargc-1) { - DEH_printf("Austin Virtual Gaming: Levels will end " - "after 20 minutes\n"); timelimit = 20; } diff --git a/src/doom/d_net.c b/src/doom/d_net.c index 05d1875f..b307d97f 100644 --- a/src/doom/d_net.c +++ b/src/doom/d_net.c @@ -499,6 +499,7 @@ boolean D_InitNetGame(net_connect_data_t *connect_data, if (i > 0) { addr = NET_FindLANServer(); + NET_SV_RegisterWithMaster(); if (addr == NULL) { @@ -617,12 +618,22 @@ void D_CheckNetGame (void) // Show players here; the server might have specified a time limit - if (timelimit > 0) + if (timelimit > 0 && deathmatch) { - DEH_printf("Levels will end after %d minute", timelimit); - if (timelimit > 1) - printf("s"); - printf(".\n"); + // Gross hack to work like Vanilla: + + if (timelimit == 20 && M_CheckParm("-avg")) + { + DEH_printf("Austin Virtual Gaming: Levels will end " + "after 20 minutes\n"); + } + else + { + DEH_printf("Levels will end after %d minute", timelimit); + if (timelimit > 1) + printf("s"); + printf(".\n"); + } } } @@ -634,9 +645,6 @@ void D_CheckNetGame (void) // void D_QuitNetGame (void) { - if (debugfile) - fclose (debugfile); - #ifdef FEATURE_MULTIPLAYER NET_SV_Shutdown(); diff --git a/src/doom/doomstat.h b/src/doom/doomstat.h index 15a8d743..237234d8 100644 --- a/src/doom/doomstat.h +++ b/src/doom/doomstat.h @@ -255,7 +255,6 @@ extern int maxammo[NUMAMMO]; // File handling stuff. extern char * savegamedir; extern char basedefault[1024]; -extern FILE* debugfile; // if true, load all graphics at level load extern boolean precache; diff --git a/src/doom/m_menu.c b/src/doom/m_menu.c index a6f7bbfb..3bb4baa3 100644 --- a/src/doom/m_menu.c +++ b/src/doom/m_menu.c @@ -762,6 +762,8 @@ void M_DrawReadThis1(void) switch (gameversion) { case exe_doom_1_9: + case exe_hacx: + if (gamemode == commercial) { // Doom 2 diff --git a/src/doom/p_setup.c b/src/doom/p_setup.c index 7d9d4318..3fc95cab 100644 --- a/src/doom/p_setup.c +++ b/src/doom/p_setup.c @@ -755,17 +755,7 @@ P_SetupLevel // Make sure all sounds are stopped before Z_FreeTags. S_Start (); - -#if 0 // UNUSED - if (debugfile) - { - Z_FreeTags (PU_LEVEL, INT_MAX); - Z_FileDumpHeap (debugfile); - } - else -#endif - Z_FreeTags (PU_LEVEL, PU_PURGELEVEL-1); - + Z_FreeTags (PU_LEVEL, PU_PURGELEVEL-1); // UNUSED W_Profile (); P_InitThinkers (); diff --git a/src/doom/p_spec.c b/src/doom/p_spec.c index 37beb850..fa3ec335 100644 --- a/src/doom/p_spec.c +++ b/src/doom/p_spec.c @@ -1389,10 +1389,9 @@ void P_SpawnSpecials (void) if (W_CheckNumForName(DEH_String("texture2")) >= 0) episode = 2; - // See if -TIMER was specified. - if (timelimit > 0) + if (timelimit > 0 && deathmatch) { levelTimer = true; levelTimeCount = timelimit * 60 * TICRATE; @@ -1401,7 +1400,7 @@ void P_SpawnSpecials (void) { levelTimer = false; } - + // Init special SECTORs. sector = sectors; for (i=0 ; i<numsectors ; i++, sector++) diff --git a/src/doom/r_data.c b/src/doom/r_data.c index b999e916..505f1ff7 100644 --- a/src/doom/r_data.c +++ b/src/doom/r_data.c @@ -413,6 +413,7 @@ R_GetColumn static void GenerateTextureHashTable(void) { + texture_t **rover; int i; int key; @@ -429,12 +430,25 @@ static void GenerateTextureHashTable(void) textures[i]->index = i; - // Hook into hash table + // 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; - textures[i]->next = textures_hashtable[key]; - textures_hashtable[key] = textures[i]; + rover = &textures_hashtable[key]; + + while (*rover != NULL) + { + rover = &(*rover)->next; + } + + // Hook into hash table + + textures[i]->next = NULL; + *rover = textures[i]; } } diff --git a/src/i_sdlsound.c b/src/i_sdlsound.c index 7deb683d..1cfafa6f 100644 --- a/src/i_sdlsound.c +++ b/src/i_sdlsound.c @@ -53,6 +53,8 @@ #define MAX_SOUND_SLICE_TIME 70 /* ms */ #define NUM_CHANNELS 16 +static boolean setpanning_workaround = false; + static boolean sound_initialized = false; static sfxinfo_t *channels_playing[NUM_CHANNELS]; @@ -632,10 +634,19 @@ static void I_SDL_UpdateSoundParams(int handle, int vol, int sep) if (right < 0) right = 0; else if (right > 255) right = 255; + // SDL_mixer version 1.2.8 and earlier has a bug in the Mix_SetPanning + // function. A workaround is to call Mix_UnregisterAllEffects for + // the channel before calling it. This is undesirable as it may lead + // to the channel volumes resetting briefly. + + if (setpanning_workaround) + { + Mix_UnregisterAllEffects(handle); + } + Mix_SetPanning(handle, left, right); } - // // Starting a sound means adding it // to the current list of active sounds @@ -822,8 +833,34 @@ static boolean I_SDL_InitSound(boolean _use_sfx_prefix) } #endif + // SDL_mixer version 1.2.8 and earlier has a bug in the Mix_SetPanning + // function that can cause the game to lock up. If we're using an old + // version, we need to apply a workaround. But the workaround has its + // own drawbacks ... + + { + const SDL_version *mixer_version; + int v; + + mixer_version = Mix_Linked_Version(); + v = SDL_VERSIONNUM(mixer_version->major, + mixer_version->minor, + mixer_version->patch); + + if (v <= SDL_VERSIONNUM(1, 2, 8)) + { + setpanning_workaround = true; + fprintf(stderr, "\n" + "ATTENTION: You are using an old version of SDL_mixer!\n" + " This version has a bug that may cause " + "your sound to stutter.\n" + " Please upgrade to a newer version!\n" + "\n"); + } + } + Mix_AllocateChannels(NUM_CHANNELS); - + SDL_PauseAudio(0); sound_initialized = true; diff --git a/src/i_video.c b/src/i_video.c index 733af899..aa9640bf 100644 --- a/src/i_video.c +++ b/src/i_video.c @@ -139,6 +139,12 @@ static SDL_Surface *screen; static char *window_title = ""; +// Intermediate 8-bit buffer that we draw to instead of 'screen'. +// This is used when we are rendering in 32-bit screen mode. +// When in a real 8-bit screen mode, screenbuffer == screen. + +static SDL_Surface *screenbuffer; + // palette static SDL_Color palette[256]; @@ -172,6 +178,10 @@ static boolean native_surface; static int screen_width = SCREENWIDTH; static int screen_height = SCREENHEIGHT; +// Color depth. + +int screen_bpp = 8; + // Automatically adjust video settings if the selected mode is // not a valid video mode. @@ -911,17 +921,18 @@ static boolean BlitArea(int x1, int y1, int x2, int y2) return true; } - x_offset = (screen->w - screen_mode->width) / 2; - y_offset = (screen->h - screen_mode->height) / 2; + x_offset = (screenbuffer->w - screen_mode->width) / 2; + y_offset = (screenbuffer->h - screen_mode->height) / 2; - if (SDL_LockSurface(screen) >= 0) + if (SDL_LockSurface(screenbuffer) >= 0) { I_InitScale(I_VideoBuffer, - (byte *) screen->pixels + (y_offset * screen->pitch) - + x_offset, + (byte *) screenbuffer->pixels + + (y_offset * screen->pitch) + + x_offset, screen->pitch); result = screen_mode->DrawScreen(x1, y1, x2, y2); - SDL_UnlockSurface(screen); + SDL_UnlockSurface(screenbuffer); } else { @@ -1055,19 +1066,37 @@ void I_FinishUpdate (void) // draw to screen BlitArea(0, 0, SCREENWIDTH, SCREENHEIGHT); - - // If we have a palette to set, the act of setting the palette - // updates the screen if (palette_to_set) { - SDL_SetColors(screen, palette, 0, 256); + SDL_SetColors(screenbuffer, palette, 0, 256); palette_to_set = false; + + // In native 8-bit mode, if we have a palette to set, the act + // of setting the palette updates the screen + + if (screenbuffer == screen) + { + return; + } } - else + + // In 8in32 mode, we must blit from the fake 8-bit screen buffer + // to the real screen before doing a screen flip. + + if (screenbuffer != screen) { - SDL_Flip(screen); + SDL_Rect dst_rect; + + // Center the buffer within the full screen space. + + dst_rect.x = (screen->w - screenbuffer->w) / 2; + dst_rect.y = (screen->h - screenbuffer->h) / 2; + + SDL_BlitSurface(screenbuffer, NULL, screen, &dst_rect); } + + SDL_Flip(screen); } @@ -1348,15 +1377,61 @@ static void AutoAdjustWindowed(void) } } +// Auto-adjust to a valid color depth. + +static void AutoAdjustColorDepth(void) +{ + SDL_Rect **modes; + SDL_PixelFormat format; + const SDL_VideoInfo *info; + int flags; + + if (fullscreen) + { + flags = SDL_FULLSCREEN; + } + else + { + flags = 0; + } + + format.BitsPerPixel = screen_bpp; + format.BytesPerPixel = (screen_bpp + 7) / 8; + + // Are any screen modes supported at the configured color depth? + + modes = SDL_ListModes(&format, flags); + + // If not, we must autoadjust to something sensible. + + if (modes == NULL) + { + printf("I_InitGraphics: %ibpp color depth not supported.\n", + screen_bpp); + + info = SDL_GetVideoInfo(); + + if (info != NULL && info->vfmt != NULL) + { + screen_bpp = info->vfmt->BitsPerPixel; + } + } +} + // If the video mode set in the configuration file is not available, // try to choose a different mode. static void I_AutoAdjustSettings(void) { - int old_screen_w, old_screen_h; + int old_screen_w, old_screen_h, old_screen_bpp; old_screen_w = screen_width; old_screen_h = screen_height; + old_screen_bpp = screen_bpp; + + // Possibly adjust color depth. + + AutoAdjustColorDepth(); // If we are running fullscreen, try to autoadjust to a valid fullscreen // mode. If this is impossible, switch to windowed. @@ -1375,10 +1450,11 @@ static void I_AutoAdjustSettings(void) // Have the settings changed? Show a message. - if (screen_width != old_screen_w || screen_height != old_screen_h) + if (screen_width != old_screen_w || screen_height != old_screen_h + || screen_bpp != old_screen_bpp) { - printf("I_InitGraphics: Auto-adjusted to %ix%i.\n", - screen_width, screen_height); + printf("I_InitGraphics: Auto-adjusted to %ix%ix%ibpp.\n", + screen_width, screen_height, screen_bpp); printf("NOTE: Your video settings have been adjusted. " "To disable this behavior,\n" @@ -1567,6 +1643,33 @@ static void CheckCommandLine(void) //! // @category video + // @arg <bpp> + // + // Specify the color depth of the screen, in bits per pixel. + // + + i = M_CheckParm("-bpp"); + + if (i > 0) + { + screen_bpp = atoi(myargv[i + 1]); + } + + // Because we love Eternity: + + //! + // @category video + // + // Set the color depth of the screen to 32 bits per pixel. + // + + if (M_CheckParm("-8in32")) + { + screen_bpp = 32; + } + + //! + // @category video // @arg <WxY> // // Specify the screen mode (when running fullscreen) or the window @@ -1752,7 +1855,12 @@ static void SetVideoMode(screen_mode_t *mode, int w, int h) // Set the video mode. - flags |= SDL_SWSURFACE | SDL_HWPALETTE | SDL_DOUBLEBUF; + flags |= SDL_SWSURFACE | SDL_DOUBLEBUF; + + if (screen_bpp == 8) + { + flags |= SDL_HWPALETTE; + } if (fullscreen) { @@ -1763,13 +1871,19 @@ static void SetVideoMode(screen_mode_t *mode, int w, int h) flags |= SDL_RESIZABLE; } - screen = SDL_SetVideoMode(w, h, 8, flags); + screen = SDL_SetVideoMode(w, h, screen_bpp, flags); if (screen == NULL) { - I_Error("Error setting video mode: %s\n", SDL_GetError()); + I_Error("Error setting video mode %ix%ix%ibpp: %s\n", + w, h, screen_bpp, SDL_GetError()); } + // Blank out the full screen area in case there is any junk in + // the borders that won't otherwise be overwritten. + + SDL_FillRect(screen, NULL, 0); + // If mode was not set, it must be set now that we know the // screen size. @@ -1791,6 +1905,22 @@ static void SetVideoMode(screen_mode_t *mode, int w, int h) } } + // Create the screenbuffer surface; if we have a real 8-bit palettized + // screen, then we can use the screen as the screenbuffer. + + if (screen->format->BitsPerPixel == 8) + { + screenbuffer = screen; + } + else + { + screenbuffer = SDL_CreateRGBSurface(SDL_SWSURFACE, + mode->width, mode->height, 8, + 0, 0, 0, 0); + + SDL_FillRect(screenbuffer, NULL, 0); + } + // Save screen mode. screen_mode = mode; @@ -1908,24 +2038,13 @@ void I_InitGraphics(void) // Start with a clear black screen // (screen will be flipped after we set the palette) - if (SDL_LockSurface(screen) >= 0) - { - byte *screenpixels; - int y; - - screenpixels = (byte *) screen->pixels; - - for (y=0; y<screen->h; ++y) - memset(screenpixels + screen->pitch * y, 0, screen->w); - - SDL_UnlockSurface(screen); - } + SDL_FillRect(screenbuffer, NULL, 0); // Set the palette doompal = W_CacheLumpName(DEH_String("PLAYPAL"), PU_CACHE); I_SetPalette(doompal); - SDL_SetColors(screen, palette, 0, 256); + SDL_SetColors(screenbuffer, palette, 0, 256); CreateCursors(); @@ -1947,7 +2066,8 @@ void I_InitGraphics(void) // Likewise if the screen pitch is not the same as the width // If we have to multiply, drawing is done to a separate 320x200 buf - native_surface = !SDL_MUSTLOCK(screen) + native_surface = screen == screenbuffer + && !SDL_MUSTLOCK(screen) && screen_mode == &mode_scale_1x && screen->pitch == SCREENWIDTH && aspect_ratio_correct; @@ -2018,5 +2138,26 @@ void I_BindVideoVariables(void) M_BindVariable("usegamma", &usegamma); M_BindVariable("vanilla_keyboard_mapping", &vanilla_keyboard_mapping); M_BindVariable("novert", &novert); + + // Windows Vista or later? Set screen color depth to + // 32 bits per pixel, as 8-bit palettized screen modes + // don't work properly in recent versions. + +#if defined(_WIN32) && !defined(_WIN32_WCE) + { + OSVERSIONINFOEX version_info; + + ZeroMemory(&version_info, sizeof(OSVERSIONINFOEX)); + version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + GetVersionEx((OSVERSIONINFO *) &version_info); + + if (version_info.dwPlatformId == VER_PLATFORM_WIN32_NT + && version_info.dwMajorVersion >= 6) + { + screen_bpp = 32; + } + } +#endif } diff --git a/src/i_video.h b/src/i_video.h index 82368967..c3c50c4b 100644 --- a/src/i_video.h +++ b/src/i_video.h @@ -116,6 +116,7 @@ void I_EnableLoadingDisk(void); extern char *video_driver; extern boolean screenvisible; + extern float mouse_acceleration; extern int mouse_threshold; extern int vanilla_keyboard_mapping; diff --git a/src/m_argv.c b/src/m_argv.c index 79702e56..875829bd 100644 --- a/src/m_argv.c +++ b/src/m_argv.c @@ -88,7 +88,7 @@ static void LoadResponseFile(int argv_index) response_filename = myargv[argv_index] + 1; // Read the response file into memory - handle = fopen(response_filename, "r"); + handle = fopen(response_filename, "rb"); if (handle == NULL) { diff --git a/src/m_config.c b/src/m_config.c index a277fc38..cb7d449c 100644 --- a/src/m_config.c +++ b/src/m_config.c @@ -555,6 +555,12 @@ static default_t extra_defaults_list[] = CONFIG_VARIABLE_INT(screen_height), //! + // Color depth of the screen, in bits. + // + + CONFIG_VARIABLE_INT(screen_bpp), + + //! // 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. diff --git a/src/net_dedicated.c b/src/net_dedicated.c index b47a7924..d8e50731 100644 --- a/src/net_dedicated.c +++ b/src/net_dedicated.c @@ -74,8 +74,8 @@ void NET_DedicatedServer(void) CheckForClientOptions(); NET_SV_Init(); - NET_SV_AddModule(&net_sdl_module); + NET_SV_RegisterWithMaster(); while (true) { diff --git a/src/net_defs.h b/src/net_defs.h index 72e0e6d2..1fa9a9af 100644 --- a/src/net_defs.h +++ b/src/net_defs.h @@ -128,6 +128,14 @@ typedef enum NET_PACKET_TYPE_QUERY_RESPONSE, } 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_t; + // Settings specified when the client connects to the server. typedef struct diff --git a/src/net_loop.c b/src/net_loop.c index 9f371bcb..acdc2cb6 100644 --- a/src/net_loop.c +++ b/src/net_loop.c @@ -137,9 +137,16 @@ static void NET_CL_FreeAddress(net_addr_t *addr) static net_addr_t *NET_CL_ResolveAddress(char *address) { - client_addr.module = &net_loop_client_module; + if (address == NULL) + { + client_addr.module = &net_loop_client_module; - return &client_addr; + return &client_addr; + } + else + { + return NULL; + } } net_module_t net_loop_client_module = @@ -206,8 +213,15 @@ static void NET_SV_FreeAddress(net_addr_t *addr) static net_addr_t *NET_SV_ResolveAddress(char *address) { - server_addr.module = &net_loop_server_module; - return &server_addr; + if (address == NULL) + { + server_addr.module = &net_loop_server_module; + return &server_addr; + } + else + { + return NULL; + } } net_module_t net_loop_server_module = diff --git a/src/net_query.c b/src/net_query.c index b50b4292..0c69c231 100644 --- a/src/net_query.c +++ b/src/net_query.c @@ -37,50 +37,166 @@ #include "net_structrw.h" #include "net_sdl.h" -typedef struct +// DNS address of the Internet master server. + +#define MASTER_SERVER_ADDRESS "master.chocolate-doom.org" + +// Time to wait for a response before declaring a timeout. + +#define QUERY_TIMEOUT_SECS 2 + +// 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; -} queryresponse_t; + unsigned int ping_time; + unsigned int query_time; + unsigned int query_attempts; + boolean printed; +} query_target_t; + +// Transmit a query packet + +static boolean registered_with_master = false; static net_context_t *query_context; -static queryresponse_t *responders; -static int num_responses; +static query_target_t *targets; +static int num_targets; + +static boolean query_loop_running = false; +static boolean printed_header = false; + +// 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; +} -// Add a new address to the list of hosts that has responded +// Send a registration packet to the master server to register +// ourselves with the global list. -static queryresponse_t *AddResponder(net_addr_t *addr, - net_querydata_t *data) +void NET_Query_AddToMaster(net_addr_t *master_addr) { - queryresponse_t *response; + 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. - responders = realloc(responders, - sizeof(queryresponse_t) * (num_responses + 1)); + printf("Failed to register with master server at %s\n", + MASTER_SERVER_ADDRESS); + } + } +} - response = &responders[num_responses]; - response->addr = addr; - response->data = *data; - ++num_responses; +// Send a query to the master server. + +static void NET_Query_SendMasterQuery(net_addr_t *addr) +{ + net_packet_t *packet; - return response; + packet = NET_NewPacket(10); + NET_WriteInt16(packet, NET_MASTER_PACKET_TYPE_QUERY); + NET_SendPacket(addr, packet); + NET_FreePacket(packet); } -// Returns true if the reply is from a host that has not previously -// responded. +// Given the specified address, find the target associated. If no +// target is found, and 'create' is true, a new target is created. -static boolean CheckResponder(net_addr_t *addr) +static query_target_t *GetTargetForAddr(net_addr_t *addr, boolean create) { + query_target_t *target; int i; - for (i=0; i<num_responses; ++i) + for (i=0; i<num_targets; ++i) { - if (responders[i].addr == addr) + if (targets[i].addr == addr) { - return false; + return &targets[i]; } } - return true; + if (!create) + { + return NULL; + } + + targets = realloc(targets, sizeof(query_target_t) * (num_targets + 1)); + + target = &targets[num_targets]; + target->type = 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 @@ -104,166 +220,254 @@ static void NET_Query_SendQuery(net_addr_t *addr) NET_FreePacket(request); } -static void formatted_printf(int wide, char *s, ...) +static void NET_Query_ParseResponse(net_addr_t *addr, net_packet_t *packet, + net_query_callback_t callback, + void *user_data) { - va_list args; - int i; - - va_start(args, s); - i = vprintf(s, args); - va_end(args); + unsigned int packet_type; + net_querydata_t querydata; + query_target_t *target; + + // Read the header - while (i < wide) + if (!NET_ReadInt16(packet, &packet_type) + || packet_type != NET_PACKET_TYPE_QUERY_RESPONSE) { - putchar(' '); - ++i; - } -} + return; + } -static char *GameDescription(GameMode_t mode, GameMission_t mission) -{ - switch (mode) + // Read query data + + if (!NET_ReadQueryData(packet, &querydata)) { - case shareware: - return "shareware"; - case registered: - return "registered"; - case retail: - return "ultimate"; - case commercial: - if (mission == doom2) - return "doom2"; - else if (mission == pack_tnt) - return "tnt"; - else if (mission == pack_plut) - return "plutonia"; - default: - return "unknown"; + return; } -} -static void PrintHeader(void) -{ - int i; + // Find the target that responded, or potentially add a new target + // if it was not already known (for LAN broadcast search) - formatted_printf(18, "Address"); - formatted_printf(8, "Players"); - puts("Description"); + target = GetTargetForAddr(addr, true); - for (i=0; i<70; ++i) - putchar('='); - putchar('\n'); + 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); + } } -static void PrintResponse(queryresponse_t *response) +// Parse a response packet from the master server. + +static void NET_Query_ParseMasterResponse(net_addr_t *master_addr, + net_packet_t *packet) { - formatted_printf(18, "%s: ", NET_AddrToString(response->addr)); - formatted_printf(8, "%i/%i", response->data.num_players, - response->data.max_players); + unsigned int packet_type; + query_target_t *target; + char *addr_str; + net_addr_t *addr; - if (response->data.gamemode != indetermined) + // Read the header. We are only interested in query responses. + + if (!NET_ReadInt16(packet, &packet_type) + || packet_type != NET_MASTER_PACKET_TYPE_QUERY_RESPONSE) { - printf("(%s) ", GameDescription(response->data.gamemode, - response->data.gamemission)); + return; } - if (response->data.server_state) + // Read a list of strings containing the addresses of servers + // that the master knows about. + + for (;;) { - printf("(game running) "); + 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); + } } - NET_SafePuts(response->data.description); + // 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) +static void NET_Query_ParsePacket(net_addr_t *addr, net_packet_t *packet, + net_query_callback_t callback, + void *user_data) { - unsigned int packet_type; - net_querydata_t querydata; - queryresponse_t *response; + query_target_t *target; + + // This might be the master server responding. - // Have we already received a packet from this host? + target = GetTargetForAddr(addr, false); - if (!CheckResponder(addr)) + if (target != NULL && target->type == QUERY_TARGET_MASTER) { - return; + NET_Query_ParseMasterResponse(addr, packet); } + else + { + NET_Query_ParseResponse(addr, packet, callback, user_data); + } +} - // Read the header +static void NET_Query_GetResponse(net_query_callback_t callback, + void *user_data) +{ + net_addr_t *addr; + net_packet_t *packet; - if (!NET_ReadInt16(packet, &packet_type) - || packet_type != NET_PACKET_TYPE_QUERY_RESPONSE) + if (NET_RecvPacket(query_context, &addr, &packet)) { - return; + NET_Query_ParsePacket(addr, packet, callback, user_data); + NET_FreePacket(packet); } +} - // Read query data +// Find a target we have not yet queried and send a query. - if (!NET_ReadQueryData(packet, &querydata)) +static void SendOneQuery(void) +{ + unsigned int now; + unsigned int i; + + now = I_GetTimeMS(); + + 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; } - if (num_responses <= 0) + // Found a target to query. Send a query; how to do this depends on + // the target type. + + switch (targets[i].type) { - // If this is the first response, print the table header + case QUERY_TARGET_SERVER: + NET_Query_SendQuery(targets[i].addr); + break; - PrintHeader(); + case QUERY_TARGET_BROADCAST: + NET_Query_SendQuery(NULL); + break; + + case QUERY_TARGET_MASTER: + NET_Query_SendMasterQuery(targets[i].addr); + break; } - response = AddResponder(addr, &querydata); + //printf("Queried %s\n", NET_AddrToString(targets[i].addr)); + targets[i].state = QUERY_TARGET_QUERIED; + targets[i].query_time = I_GetTimeMS(); + ++targets[i].query_attempts; +} + +// Time out servers that have been queried and not responded. - PrintResponse(response); +static void CheckTargetTimeouts(void) +{ + unsigned int i; + unsigned int now; + + now = I_GetTimeMS(); + + for (i = 0; i < num_targets; ++i) + { + // 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; + } + } } -static void NET_Query_GetResponse(void) +// If all targets have responded or timed out, returns true. + +static boolean AllTargetsDone(void) { - net_addr_t *addr; - net_packet_t *packet; + unsigned int i; - if (NET_RecvPacket(query_context, &addr, &packet)) + for (i = 0; i < num_targets; ++i) { - NET_Query_ParsePacket(addr, packet); - NET_FreePacket(packet); + if (targets[i].state != QUERY_TARGET_RESPONDED + && targets[i].state != QUERY_TARGET_NO_RESPONSE) + { + return false; + } } + + return true; } -static net_addr_t *NET_Query_QueryLoop(net_addr_t *addr, - boolean find_one) +// Stop the query loop + +static void NET_Query_ExitLoop(void) { - int start_time; - int last_send_time; + query_loop_running = false; +} + +// Loop waiting for responses. +// The specified callback is invoked when a new server responds. - last_send_time = -1; - start_time = I_GetTimeMS(); +static void NET_Query_QueryLoop(net_query_callback_t callback, + void *user_data) +{ + query_loop_running = true; - while (I_GetTimeMS() < start_time + 5000) + while (query_loop_running && !AllTargetsDone()) { - // Send a query once every second + // Send a query. This will only send a single query. + // Because of the delay below, this is therefore rate limited. - if (last_send_time < 0 || I_GetTimeMS() > last_send_time + 1000) - { - NET_Query_SendQuery(addr); - last_send_time = I_GetTimeMS(); - } + SendOneQuery(); // Check for a response - NET_Query_GetResponse(); + NET_Query_GetResponse(callback, user_data); - // Found a response? - - if (find_one && num_responses > 0) - break; - // Don't thrash the CPU - - I_Sleep(100); - } - if (num_responses > 0) - return responders[0].addr; - else - return NULL; + I_Sleep(50); + + CheckTargetTimeouts(); + } } void NET_Query_Init(void) @@ -272,51 +476,256 @@ void NET_Query_Init(void) NET_AddModule(query_context, &net_sdl_module); net_sdl_module.InitClient(); - responders = NULL; - num_responses = 0; + targets = NULL; + num_targets = 0; + + printed_header = false; } -void NET_QueryAddress(char *addr) +// 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_addr_t *net_addr; - - NET_Query_Init(); + NET_Query_ExitLoop(); +} - net_addr = NET_ResolveAddress(query_context, addr); +// 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; - if (net_addr == NULL) + for (i = 0; i < num_targets; ++i) { - I_Error("NET_QueryAddress: Host '%s' not found!", addr); + if (targets[i].type == QUERY_TARGET_SERVER + && targets[i].state == QUERY_TARGET_RESPONDED) + { + return &targets[i]; + } } - printf("\nQuerying '%s'...\n\n", addr); + return NULL; +} - if (!NET_Query_QueryLoop(net_addr, true)) +// 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) { - I_Error("No response from '%s'", addr); + if (targets[i].type == QUERY_TARGET_SERVER + && targets[i].state == QUERY_TARGET_RESPONDED) + { + ++result; + } } - exit(0); + return result; +} + +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; + } +} + +int NET_LANQuery(net_query_callback_t callback, void *user_data) +{ + query_target_t *target; + NET_Query_Init(); - return NET_Query_QueryLoop(NULL, true); + // Add a broadcast target to the list. + + target = GetTargetForAddr(NULL, true); + target->type = QUERY_TARGET_BROADCAST; + + NET_Query_QueryLoop(callback, user_data); + + return GetNumResponses(); } -void NET_LANQuery(void) +int NET_MasterQuery(net_query_callback_t callback, void *user_data) { + net_addr_t *master; + query_target_t *target; + NET_Query_Init(); - printf("\nSearching for servers on local LAN ...\n\n"); + // 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; + + NET_Query_QueryLoop(callback, user_data); + + // Check that we got a response from the master, and display + // a warning if we didn't. + + if (target->state == QUERY_TARGET_NO_RESPONSE) + { + fprintf(stderr, "NET_MasterQuery: no response from master server.\n"); + } + + return GetNumResponses(); +} + +static void formatted_printf(int wide, char *s, ...) +{ + va_list args; + int i; - if (!NET_Query_QueryLoop(NULL, false)) + va_start(args, s); + i = vprintf(s, args); + va_end(args); + + while (i < wide) { - I_Error("No servers found"); + putchar(' '); + ++i; + } +} + +static char *GameDescription(GameMode_t mode, GameMission_t mission) +{ + switch (mode) + { + case shareware: + return "shareware"; + case registered: + return "registered"; + case retail: + return "ultimate"; + case commercial: + if (mission == doom2) + return "doom2"; + else if (mission == pack_tnt) + return "tnt"; + else if (mission == pack_plut) + return "plutonia"; + default: + return "unknown"; + } +} + +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. + +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) "); } - exit(0); + NET_SafePuts(data->description); } diff --git a/src/net_query.h b/src/net_query.h index f682d320..01e059cb 100644 --- a/src/net_query.h +++ b/src/net_query.h @@ -27,9 +27,22 @@ #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_LANQuery(net_query_callback_t callback, void *user_data); +extern int NET_MasterQuery(net_query_callback_t callback, void *user_data); extern void NET_QueryAddress(char *addr); -extern void NET_LANQuery(void); extern net_addr_t *NET_FindLANServer(void); +extern void NET_QueryPrintCallback(net_addr_t *addr, net_querydata_t *data, + unsigned int ping_time, 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 void NET_Query_MasterResponse(net_packet_t *packet); + #endif /* #ifndef NET_QUERY_H */ diff --git a/src/net_server.c b/src/net_server.c index 058e61f7..3d5df135 100644 --- a/src/net_server.c +++ b/src/net_server.c @@ -41,10 +41,15 @@ #include "net_io.h" #include "net_loop.h" #include "net_packet.h" +#include "net_query.h" #include "net_server.h" #include "net_sdl.h" #include "net_structrw.h" +// How often to refresh our registration with the master server. + +#define MASTER_REFRESH_PERIOD 20 * 60 /* 20 minutes */ + typedef enum { // waiting for the game to start @@ -128,6 +133,11 @@ 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; + // receive window static unsigned int recvwindow_start; @@ -1067,6 +1077,7 @@ void NET_SV_SendQueryResponse(net_addr_t *addr) { net_packet_t *reply; net_querydata_t querydata; + int p; // Version @@ -1086,9 +1097,22 @@ void NET_SV_SendQueryResponse(net_addr_t *addr) querydata.gamemode = sv_gamemode; querydata.gamemission = sv_gamemission; - // Server description. This is currently hard-coded. + //! + // @arg <name> + // + // When starting a network server, specify a name for the server. + // - querydata.description = "Chocolate Doom server"; + p = M_CheckParm("-servername"); + + if (p > 0 && p + 1 < myargc) + { + querydata.description = myargv[p + 1]; + } + else + { + querydata.description = "Unnamed server"; + } // Send it and we're done. @@ -1106,6 +1130,14 @@ 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); @@ -1511,6 +1543,32 @@ void NET_SV_Init(void) server_initialized = true; } +void NET_SV_RegisterWithMaster(void) +{ + //! + // When running a server, don't register with the global master server. + // + // @category net + // + + 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(); + } +} + // Run server code to check for new packets/send packets as the server // requires @@ -1525,12 +1583,21 @@ void NET_SV_Run(void) return; } - while (NET_RecvPacket(server_context, &addr, &packet)) + while (NET_RecvPacket(server_context, &addr, &packet)) { NET_SV_Packet(packet, addr); NET_FreePacket(packet); } + // Possibly refresh our registration with the master server. + + if (master_server != NULL + && I_GetTimeMS() - master_refresh_time > MASTER_REFRESH_PERIOD * 1000) + { + NET_Query_AddToMaster(master_server); + master_refresh_time = I_GetTimeMS(); + } + // "Run" any clients that may have things to do, independent of responses // to received packets diff --git a/src/net_server.h b/src/net_server.h index 93b22fc3..1debbd79 100644 --- a/src/net_server.h +++ b/src/net_server.h @@ -41,5 +41,9 @@ void NET_SV_Shutdown(void); 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/src/setup/display.c b/src/setup/display.c index 9fd0963b..0def546f 100644 --- a/src/setup/display.c +++ b/src/setup/display.c @@ -32,6 +32,27 @@ #include "display.h" +typedef struct +{ + char *description; + int bpp; +} pixel_depth_t; + +// List of supported pixel depths. + +static pixel_depth_t pixel_depths[] = +{ + { "8-bit", 8 }, + { "16-bit", 16 }, + { "24-bit", 24 }, + { "32-bit", 32 }, +}; + +// List of strings containing supported pixel depths. + +static char **supported_bpps; +static int num_supported_bpps; + typedef struct { int w, h; @@ -78,6 +99,7 @@ static int aspect_ratio_correct = 1; static int fullscreen = 1; static int screen_width = 320; static int screen_height = 200; +static int screen_bpp = 8; static int startup_delay = 1000; static int graphical_startup = 1; static int show_endoom = 1; @@ -90,6 +112,10 @@ static int usegamma = 0; static int selected_screen_width = 0, selected_screen_height; +// Index into the supported_bpps of the selected pixel depth. + +static int selected_bpp = 0; + static int system_video_env_set; // Set the SDL_VIDEODRIVER environment variable @@ -133,6 +159,153 @@ void SetDisplayDriver(void) } } +// Query SDL as to whether any fullscreen modes are available for the +// specified pixel depth. + +static int PixelDepthSupported(int bpp) +{ + SDL_PixelFormat format; + SDL_Rect **modes; + + format.BitsPerPixel = bpp; + format.BytesPerPixel = (bpp + 7) / 8; + + modes = SDL_ListModes(&format, SDL_FULLSCREEN); + + return modes != NULL; +} + +// Query SDL and populate the supported_bpps array. + +static void IdentifyPixelDepths(void) +{ + unsigned int i; + unsigned int num_depths = sizeof(pixel_depths) / sizeof(*pixel_depths); + + if (supported_bpps != NULL) + { + free(supported_bpps); + } + + supported_bpps = malloc(sizeof(char *) * num_depths); + num_supported_bpps = 0; + + // Check each bit depth to determine if modes are available. + + for (i = 0; i < num_depths; ++i) + { + // If modes are available, add this bit depth to the list. + + if (PixelDepthSupported(pixel_depths[i].bpp)) + { + supported_bpps[num_supported_bpps] = pixel_depths[i].description; + ++num_supported_bpps; + } + } + + // No supported pixel depths? That's kind of a problem. Add 8bpp + // as a fallback. + + if (num_supported_bpps == 0) + { + supported_bpps[0] = pixel_depths[0].description; + ++num_supported_bpps; + } +} + +// Get the screen pixel depth corresponding to what selected_bpp is set to. + +static int GetSelectedBPP(void) +{ + unsigned int num_depths = sizeof(pixel_depths) / sizeof(*pixel_depths); + unsigned int i; + + // Find which pixel depth is selected, and set screen_bpp. + + for (i = 0; i < num_depths; ++i) + { + if (pixel_depths[i].description == supported_bpps[selected_bpp]) + { + return pixel_depths[i].bpp; + } + } + + // Default fallback value. + + return 8; +} + +// Get the index into supported_bpps of the specified pixel depth string. + +static int GetSupportedBPPIndex(char *description) +{ + unsigned int i; + + for (i = 0; i < num_supported_bpps; ++i) + { + if (supported_bpps[i] == description) + { + return i; + } + } + + // Shouldn't happen; fall back to the first in the list. + + return 0; +} + +// Set selected_bpp to match screen_bpp. + +static int TrySetSelectedBPP(void) +{ + unsigned int num_depths = sizeof(pixel_depths) / sizeof(*pixel_depths); + unsigned int i; + + // Search pixel_depths, find the bpp that corresponds to screen_bpp, + // then set selected_bpp to match. + + for (i = 0; i < num_depths; ++i) + { + if (pixel_depths[i].bpp == screen_bpp) + { + selected_bpp = GetSupportedBPPIndex(pixel_depths[i].description); + return 1; + } + } + + return 0; +} + +static void SetSelectedBPP(void) +{ + const SDL_VideoInfo *info; + + if (TrySetSelectedBPP()) + { + return; + } + + // screen_bpp does not match any supported pixel depth. Query SDL + // to find out what it recommends using. + + info = SDL_GetVideoInfo(); + + if (info != NULL && info->vfmt != NULL) + { + screen_bpp = info->vfmt->BitsPerPixel; + } + + // Try again. + + if (!TrySetSelectedBPP()) + { + // Give up and just use the first in the list. + + selected_bpp = 0; + screen_bpp = GetSelectedBPP(); + } +} + static void ModeSelected(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(mode)) { TXT_CAST_ARG(screen_mode_t, mode); @@ -177,6 +350,7 @@ static int GoodFullscreenMode(screen_mode_t *mode) static void BuildFullscreenModesList(void) { + SDL_PixelFormat format; SDL_Rect **modes; screen_mode_t *m1; screen_mode_t *m2; @@ -194,7 +368,10 @@ static void BuildFullscreenModesList(void) // Get a list of fullscreen modes and find out how many // modes are in the list. - modes = SDL_ListModes(NULL, SDL_FULLSCREEN); + format.BitsPerPixel = screen_bpp; + format.BytesPerPixel = (screen_bpp + 7) / 8; + + modes = SDL_ListModes(&format, SDL_FULLSCREEN); if (modes == NULL || modes == (SDL_Rect **) -1) { @@ -317,8 +494,25 @@ static void GenerateModesTable(TXT_UNCAST_ARG(widget), vidmode = FindBestMode(modes); - screen_width = modes[vidmode].w; - screen_height = modes[vidmode].h; + if (vidmode > 0) + { + screen_width = modes[vidmode].w; + screen_height = modes[vidmode].h; + } +} + +// Callback invoked when the BPP selector is changed. + +static void UpdateBPP(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(modes_table)) +{ + TXT_CAST_ARG(txt_table_t, modes_table); + + screen_bpp = GetSelectedBPP(); + + // Rebuild list of fullscreen modes. + + BuildFullscreenModesList(); + GenerateModesTable(NULL, modes_table); } #if defined(_WIN32) && !defined(_WIN32_WCE) @@ -372,6 +566,11 @@ static void UpdateVideoDriver(TXT_UNCAST_ARG(widget), RestartTextscreen(); + // Rebuild the list of supported pixel depths. + + IdentifyPixelDepths(); + SetSelectedBPP(); + // Rebuild the video modes list BuildFullscreenModesList(); @@ -385,8 +584,16 @@ void ConfigDisplay(void) { txt_window_t *window; txt_table_t *modes_table; + txt_table_t *bpp_table; txt_checkbox_t *fs_checkbox; txt_checkbox_t *ar_checkbox; + txt_dropdown_list_t *bpp_selector; + + // What color depths are supported? Generate supported_bpps array + // and set selected_bpp to match the current value of screen_bpp. + + IdentifyPixelDepths(); + SetSelectedBPP(); // First time in? Initialise selected_screen_{width,height} @@ -442,6 +649,7 @@ void ConfigDisplay(void) TXT_AddWidgets(window, TXT_NewSeparator("Screen mode"), + bpp_table = TXT_NewTable(2), modes_table, TXT_NewSeparator("Misc."), NULL); @@ -458,6 +666,15 @@ void ConfigDisplay(void) TXT_NewCheckBox("Show ENDOOM screen", &show_endoom)); } + TXT_AddWidgets(bpp_table, + TXT_NewLabel("Color depth: "), + bpp_selector = TXT_NewDropdownList(&selected_bpp, + supported_bpps, + num_supported_bpps), + NULL); + + + TXT_SignalConnect(bpp_selector, "changed", UpdateBPP, modes_table); TXT_SignalConnect(fs_checkbox, "changed", GenerateModesTable, modes_table); TXT_SignalConnect(ar_checkbox, "changed", GenerateModesTable, modes_table); @@ -486,5 +703,25 @@ void BindDisplayVariables(void) M_BindVariable("graphical_startup", &graphical_startup); } + // Windows Vista or later? Set screen color depth to + // 32 bits per pixel, as 8-bit palettized screen modes + // don't work properly in recent versions. + +#if defined(_WIN32) && !defined(_WIN32_WCE) + { + OSVERSIONINFOEX version_info; + + ZeroMemory(&version_info, sizeof(OSVERSIONINFOEX)); + version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + GetVersionEx((OSVERSIONINFO *) &version_info); + + if (version_info.dwPlatformId == VER_PLATFORM_WIN32_NT + && version_info.dwMajorVersion >= 6) + { + screen_bpp = 32; + } + } +#endif } diff --git a/src/setup/joystick.c b/src/setup/joystick.c index fbe3a3f3..0fc00ea1 100644 --- a/src/setup/joystick.c +++ b/src/setup/joystick.c @@ -65,8 +65,8 @@ static int joystick_y_invert = 0; static txt_button_t *joystick_button; static int *all_joystick_buttons[] = { - &joybstraferight, &joybstrafeleft, &joybfire, &joybspeed, - &joybuse, &joybstrafe, &joybjump + &joybstraferight, &joybstrafeleft, &joybfire, &joybspeed, + &joybuse, &joybstrafe, &joybprevweapon, &joybnextweapon, &joybjump }; // diff --git a/textscreen/txt_sdl.c b/textscreen/txt_sdl.c index a0cbe3d6..33659fcc 100644 --- a/textscreen/txt_sdl.c +++ b/textscreen/txt_sdl.c @@ -119,6 +119,22 @@ static SDL_Color ega_colors[] = #endif +static txt_font_t *FontForName(char *name) +{ + if (!strcmp(name, "small")) + { + return &small_font; + } + else if (!strcmp(name, "normal")) + { + return &main_font; + } + else + { + return NULL; + } +} + // // Select the font to use, based on screen resolution // @@ -129,9 +145,22 @@ static SDL_Color ega_colors[] = static void ChooseFont(void) { SDL_Rect **modes; + char *env; int i; - font = &main_font; + // Allow normal selection to be overridden from an environment variable: + + env = getenv("TEXTSCREEN_FONT"); + + if (env != NULL) + { + font = FontForName(env); + + if (font != NULL) + { + return; + } + } // Check all modes @@ -140,6 +169,8 @@ static void ChooseFont(void) // If in doubt and we can't get a list, always prefer to // fall back to the normal font: + font = &main_font; + if (modes == NULL || modes == (SDL_Rect **) -1 || *modes == NULL) { #ifdef _WIN32_WCE |