ref: 8dab0a3e635db40359c8ddeb9afaa9eca626ee98
parent: 005747a6174d2d5b72e1af196a72cafb9b801a58
parent: 678a8f9aeea9fa1966b3e8a94974688fda4d8fe1
author: Simon Howard <[email protected]>
date: Fri Dec 10 17:44:01 EST 2010
Merge from trunk. This is slightly out of date as I did the merge several days ago. Subversion-branch: /branches/raven-branch Subversion-revision: 2212
--- a/Makefile.am
+++ b/Makefile.am
@@ -53,6 +53,7 @@
README.OPL \
TODO \
BUGS \
+ NOT-BUGS \
rpm.spec
MAINTAINERCLEANFILES = $(AUX_DIST_GEN)
--- a/NEWS
+++ b/NEWS
@@ -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,10 +26,31 @@
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
@@ -25,9 +57,19 @@
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):
--- /dev/null
+++ b/NOT-BUGS
@@ -1,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
+
--- a/README
+++ b/README
@@ -65,9 +65,13 @@
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
--- a/data/convert-icon
+++ b/data/convert-icon
@@ -1,7 +1,5 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
-# $Id: convert-icon 704 2006-10-18 00:51:11Z fraggle $
-#
# Copyright(C) 2005 Simon Howard
#
# This program is free software; you can redistribute it and/or
@@ -29,7 +27,7 @@
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)
@@ -70,5 +68,4 @@
outfile.write("};\n")
convert_image(sys.argv[1], sys.argv[2])
-
--- a/man/docgen
+++ b/man/docgen
@@ -150,11 +150,8 @@
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 @@
try:
for line in f:
line = line.replace("@content", content)
- print line.rstrip()
+ print(line.rstrip())
finally:
f.close()
@@ -407,7 +404,7 @@
read_wikipages()
for t in targets:
- print t.wiki_output()
+ print(t.wiki_output())
def plaintext_output(targets, template_file):
@@ -419,13 +416,13 @@
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
--- a/opl/opl_sdl.c
+++ b/opl/opl_sdl.c
@@ -176,8 +176,8 @@
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];
}
}
--- a/pkg/config.make.in
+++ b/pkg/config.make.in
@@ -25,6 +25,7 @@
INSTALL \
NEWS \
BUGS \
+ NOT-BUGS \
CMDLINE \
TODO
--- 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 @@
}
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 @@
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 @@
# 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 @@
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 @@
# 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 @@
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 @@
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 @@
# 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 @@
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":
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -33,6 +33,7 @@
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
--- a/src/d_iwad.c
+++ b/src/d_iwad.c
@@ -47,6 +47,7 @@
{ "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" },
--- a/src/d_mode.h
+++ b/src/d_mode.h
@@ -62,6 +62,7 @@
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)
--- 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,23 +94,42 @@
{
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 @@
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 @@
{
++context->linenum;
}
-
+
context->last_was_newline = result == '\n';
-
+
return result;
}
--- 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);
--- 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 @@
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 @@
// end of file?
if (line == NULL)
+ {
return;
+ }
while (line[0] != '\0' && isspace(line[0]))
++line;
@@ -347,6 +347,48 @@
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)
@@ -386,5 +428,4 @@
}
}
}
-
--- 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);
--- a/src/doom/d_main.c
+++ b/src/doom/d_main.c
@@ -122,8 +122,6 @@
boolean autostart;
int startloadgame;
-FILE* debugfile;
-
boolean advancedemo;
// Store demo, do not accept any inputs
@@ -424,14 +422,6 @@
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 @@
// 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 @@
" "
"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 @@
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 @@
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 @@
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 @@
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 @@
}
//!
+ // @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 @@
if (p > 0)
{
NET_QueryAddress(myargv[p+1]);
+ exit(0);
}
//!
@@ -1117,8 +1156,13 @@
// 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 @@
D_IdentifyVersion();
InitGameVersion();
LoadChexDeh();
+ LoadHacxDeh();
D_SetGameDescription();
SetSaveGameDir(iwadfile);
@@ -1443,10 +1488,9 @@
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 @@
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;
}
--- a/src/doom/d_net.c
+++ b/src/doom/d_net.c
@@ -499,6 +499,7 @@
if (i > 0)
{
addr = NET_FindLANServer();
+ NET_SV_RegisterWithMaster();
if (addr == NULL)
{
@@ -617,12 +618,22 @@
// 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_QuitNetGame (void)
{
- if (debugfile)
- fclose (debugfile);
-
#ifdef FEATURE_MULTIPLAYER
NET_SV_Shutdown();
--- a/src/doom/doomstat.h
+++ b/src/doom/doomstat.h
@@ -255,7 +255,6 @@
// File handling stuff.
extern char * savegamedir;
extern char basedefault[1024];
-extern FILE* debugfile;
// if true, load all graphics at level load
extern boolean precache;
--- a/src/doom/m_menu.c
+++ b/src/doom/m_menu.c
@@ -762,6 +762,8 @@
switch (gameversion)
{
case exe_doom_1_9:
+ case exe_hacx:
+
if (gamemode == commercial)
{
// Doom 2
--- a/src/doom/p_setup.c
+++ b/src/doom/p_setup.c
@@ -755,17 +755,7 @@
// 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 ();
--- a/src/doom/p_spec.c
+++ b/src/doom/p_spec.c
@@ -1389,10 +1389,9 @@
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 @@
{
levelTimer = false;
}
-
+
// Init special SECTORs.
sector = sectors;
for (i=0 ; i<numsectors ; i++, sector++)
--- a/src/doom/r_data.c
+++ b/src/doom/r_data.c
@@ -413,6 +413,7 @@
static void GenerateTextureHashTable(void)
{
+ texture_t **rover;
int i;
int key;
@@ -429,12 +430,25 @@
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];
}
}
--- 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 @@
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 @@
}
#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;
--- a/src/i_video.c
+++ b/src/i_video.c
@@ -139,6 +139,12 @@
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 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 @@
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 @@
// 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,16 +1377,62 @@
}
}
+// 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 @@
// 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 @@
//!
// @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,8 +1855,13 @@
// 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)
{
flags |= SDL_FULLSCREEN;
@@ -1763,13 +1871,19 @@
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 @@
}
}
+ // 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 @@
// Start with a clear black screen
// (screen will be flipped after we set the palette)
- if (SDL_LockSurface(screen) >= 0)
- {
- byte *screenpixels;
- int y;
+ SDL_FillRect(screenbuffer, NULL, 0);
- screenpixels = (byte *) screen->pixels;
-
- for (y=0; y<screen->h; ++y)
- memset(screenpixels + screen->pitch * y, 0, screen->w);
-
- SDL_UnlockSurface(screen);
- }
-
// 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 @@
// 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 @@
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
}
--- a/src/i_video.h
+++ b/src/i_video.h
@@ -116,6 +116,7 @@
extern char *video_driver;
extern boolean screenvisible;
+
extern float mouse_acceleration;
extern int mouse_threshold;
extern int vanilla_keyboard_mapping;
--- a/src/m_argv.c
+++ b/src/m_argv.c
@@ -88,7 +88,7 @@
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)
{
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -555,6 +555,12 @@
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.
--- a/src/net_dedicated.c
+++ b/src/net_dedicated.c
@@ -74,8 +74,8 @@
CheckForClientOptions();
NET_SV_Init();
-
NET_SV_AddModule(&net_sdl_module);
+ NET_SV_RegisterWithMaster();
while (true)
{
--- a/src/net_defs.h
+++ b/src/net_defs.h
@@ -128,6 +128,14 @@
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
--- a/src/net_loop.c
+++ b/src/net_loop.c
@@ -137,9 +137,16 @@
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 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 =
--- 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;
-// Add a new address to the list of hosts that has responded
+static boolean query_loop_running = false;
+static boolean printed_header = false;
-static queryresponse_t *AddResponder(net_addr_t *addr,
- net_querydata_t *data)
+// Resolve the master server address.
+
+net_addr_t *NET_Query_ResolveMaster(net_context_t *context)
{
- queryresponse_t *response;
+ net_addr_t *addr;
- responders = realloc(responders,
- sizeof(queryresponse_t) * (num_responses + 1));
+ addr = NET_ResolveAddress(context, MASTER_SERVER_ADDRESS);
- response = &responders[num_responses];
- response->addr = addr;
- response->data = *data;
- ++num_responses;
+ if (addr == NULL)
+ {
+ fprintf(stderr, "Warning: Failed to resolve address "
+ "for master server: %s\n", MASTER_SERVER_ADDRESS);
+ }
- return response;
+ return addr;
}
-// Returns true if the reply is from a host that has not previously
-// responded.
+// Send a registration packet to the master server to register
+// ourselves with the global list.
-static boolean CheckResponder(net_addr_t *addr)
+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);
+ }
+ }
+}
+
+// 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; 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 @@
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;
- while (i < wide)
+ // Read the header
+
+ 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;
- // Have we already received a packet from this host?
+ // This might be the master server responding.
- if (!CheckResponder(addr))
+ target = GetTargetForAddr(addr, false);
+
+ 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;
+}
- PrintResponse(response);
+// 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)
+ {
+ // 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;
+}
- last_send_time = -1;
- start_time = I_GetTimeMS();
+// Loop waiting for responses.
+// The specified callback is invoked when a new server responds.
- while (I_GetTimeMS() < start_time + 5000)
+static void NET_Query_QueryLoop(net_query_callback_t callback,
+ void *user_data)
+{
+ query_loop_running = true;
+
+ 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 @@
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.
- if (net_addr == NULL)
+static query_target_t *FindFirstResponder(void)
+{
+ unsigned int i;
+
+ 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();
- return NET_Query_QueryLoop(NULL, true);
+ // 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;
+ }
}
-void NET_LANQuery(void)
+int NET_LANQuery(net_query_callback_t callback, void *user_data)
{
+ query_target_t *target;
+
NET_Query_Init();
- printf("\nSearching for servers on local LAN ...\n\n");
+ // Add a broadcast target to the list.
- if (!NET_Query_QueryLoop(NULL, false))
+ target = GetTargetForAddr(NULL, true);
+ target->type = QUERY_TARGET_BROADCAST;
+
+ NET_Query_QueryLoop(callback, user_data);
+
+ return GetNumResponses();
+}
+
+int NET_MasterQuery(net_query_callback_t callback, void *user_data)
+{
+ 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)
{
- I_Error("No servers found");
+ return 0;
}
- exit(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;
+
+ 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 (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) ");
+ }
+
+ NET_SafePuts(data->description);
}
--- 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 */
--- 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_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 @@
{
net_packet_t *reply;
net_querydata_t querydata;
+ int p;
// Version
@@ -1086,10 +1097,23 @@
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.
reply = NET_NewPacket(64);
@@ -1106,6 +1130,14 @@
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 @@
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,10 +1583,19 @@
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
--- a/src/net_server.h
+++ b/src/net_server.h
@@ -41,5 +41,9 @@
void NET_SV_AddModule(net_module_t *module);
+// Register server with master server.
+
+void NET_SV_RegisterWithMaster(void);
+
#endif /* #ifndef NET_SERVER_H */
--- 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 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 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 @@
}
}
+// 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 void BuildFullscreenModesList(void)
{
+ SDL_PixelFormat format;
SDL_Rect **modes;
screen_mode_t *m1;
screen_mode_t *m2;
@@ -194,8 +368,11 @@
// 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)
{
num_modes = 0;
@@ -317,10 +494,27 @@
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)
static int win32_video_driver = 0;
@@ -372,6 +566,11 @@
RestartTextscreen();
+ // Rebuild the list of supported pixel depths.
+
+ IdentifyPixelDepths();
+ SetSelectedBPP();
+
// Rebuild the video modes list
BuildFullscreenModesList();
@@ -385,9 +584,17 @@
{
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}
if (selected_screen_width == 0)
@@ -442,6 +649,7 @@
TXT_AddWidgets(window,
TXT_NewSeparator("Screen mode"),
+ bpp_table = TXT_NewTable(2),
modes_table,
TXT_NewSeparator("Misc."),
NULL);
@@ -458,6 +666,15 @@
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 @@
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
}
--- a/src/setup/joystick.c
+++ b/src/setup/joystick.c
@@ -65,8 +65,8 @@
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
};
//
--- a/textscreen/txt_sdl.c
+++ b/textscreen/txt_sdl.c
@@ -119,6 +119,22 @@
#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,10 +145,23 @@
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
modes = SDL_ListModes(NULL, SDL_FULLSCREEN);
@@ -139,6 +168,8 @@
// 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)
{