Merge branch 'master' into vulkan2

This commit is contained in:
Christoph Oelckers 2019-02-23 19:53:38 +01:00
commit 413412f603
1198 changed files with 12011 additions and 8785 deletions

View file

@ -13,21 +13,6 @@ endif()
list( APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake )
include( FindPackageHandleStandardArgs )
# Produce a warning if XP support will be missing when building a 32 bit target for MSVC.
if( MSVC )
if(NOT "${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)")
list( APPEND WINXP_TOOLSETS v140_xp v141_xp)
list( FIND WINXP_TOOLSETS "${CMAKE_GENERATOR_TOOLSET}" HAVE_WINXP_SUPPORT)
if( HAVE_WINXP_SUPPORT EQUAL -1 )
string( REPLACE ";" " or " WINXP_TOOLSETS_STR "${WINXP_TOOLSETS}" )
message( WARNING "This project supports Windows XP but you must set the optional toolset to ${WINXP_TOOLSETS_STR} manually to have it in your build!\n"
"Assign toolset's name to CMAKE_GENERATOR_TOOLSET variable or use -T <toolset> from the command prompt." )
endif()
endif()
endif()
# Support cross compiling
option( FORCE_CROSSCOMPILE "Turn on cross compiling." NO )
if( FORCE_CROSSCOMPILE )
@ -229,9 +214,7 @@ if( MSVC )
set( DEB_C_FLAGS "/D _CRTDBG_MAP_ALLOC /MTd" )
# Disable warnings for unsecure CRT functions from VC8+
if( MSVC_VERSION GREATER 1399 )
set( ALL_C_FLAGS "${ALL_C_FLAGS} /wd4996" )
endif()
set( ALL_C_FLAGS "${ALL_C_FLAGS} /wd4996 /DUNICODE /D_UNICODE" )
# The CMake configurations set /GR and /MD by default, which conflict with our settings.
string(REPLACE "/MD " " " CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} )

View file

@ -487,7 +487,6 @@ endif()
# Start defining source files for ZDoom
set( PLAT_WIN32_SOURCES
sound/mididevices/music_win_mididevice.cpp
win32/critsec.cpp
win32/hardware.cpp
win32/helperthread.cpp
win32/i_cd.cpp
@ -511,7 +510,6 @@ set( PLAT_POSIX_SOURCES
posix/i_steam.cpp )
set( PLAT_SDL_SOURCES
posix/sdl/crashcatcher.c
posix/sdl/critsec.cpp
posix/sdl/hardware.cpp
posix/sdl/i_gui.cpp
posix/sdl/i_input.cpp
@ -528,7 +526,6 @@ set( PLAT_OSX_SOURCES
posix/osx/i_specialpaths.mm
posix/osx/zdoom.icns )
set( PLAT_COCOA_SOURCES
posix/cocoa/critsec.cpp
posix/cocoa/i_input.mm
posix/cocoa/i_joystick.cpp
posix/cocoa/i_main.mm
@ -655,6 +652,7 @@ file( GLOB HEADER_FILES
g_statusbar/*.h
gamedata/*.h
gamedata/resourcefiles/*.h
gamedata/fonts/*.h
gamedata/textures/*.h
gamedata/textures/hires/hqnx/*.h
gamedata/textures/hires/hqnx_asm/*.h
@ -690,6 +688,7 @@ file( GLOB HEADER_FILES
sound/timidity/*.h
sound/timiditypp/*.h
sound/wildmidi/*.h
rendering/2d/*.h
rendering/swrenderer/*.h
rendering/swrenderer/textures/*.h
rendering/swrenderer/drawers/*.h
@ -925,7 +924,6 @@ set (PCH_SOURCES
c_expr.cpp
c_functions.cpp
ct_chat.cpp
cycler.cpp
d_iwad.cpp
d_main.cpp
d_anonstats.cpp
@ -936,7 +934,6 @@ set (PCH_SOURCES
dobjgc.cpp
dobjtype.cpp
doomstat.cpp
f_wipe.cpp
g_cvars.cpp
g_dumpinfo.cpp
g_game.cpp
@ -968,7 +965,6 @@ set (PCH_SOURCES
p_states.cpp
p_things.cpp
p_tick.cpp
p_usdf.cpp
p_user.cpp
r_utility.cpp
r_sky.cpp
@ -980,16 +976,8 @@ set (PCH_SOURCES
serializer.cpp
scriptutil.cpp
st_stuff.cpp
statistics.cpp
stats.cpp
v_2ddrawer.cpp
v_blend.cpp
v_draw.cpp
v_font.cpp
v_framebuffer.cpp
v_palette.cpp
v_pfx.cpp
v_text.cpp
v_video.cpp
wi_stuff.cpp
gamedata/a_keys.cpp
@ -1006,6 +994,7 @@ set (PCH_SOURCES
gamedata/info.cpp
gamedata/keysections.cpp
gamedata/p_terrain.cpp
gamedata/statistics.cpp
gamedata/teaminfo.cpp
g_shared/a_pickups.cpp
g_shared/a_action.cpp
@ -1046,6 +1035,11 @@ set (PCH_SOURCES
g_statusbar/sbarinfo.cpp
g_statusbar/sbar_mugshot.cpp
g_statusbar/shared_sbar.cpp
rendering/2d/f_wipe.cpp
rendering/2d/v_2ddrawer.cpp
rendering/2d/v_drawtext.cpp
rendering/2d/v_blend.cpp
rendering/2d/v_draw.cpp
rendering/gl/renderer/gl_renderer.cpp
rendering/gl/renderer/gl_renderstate.cpp
rendering/gl/renderer/gl_renderbuffers.cpp
@ -1092,6 +1086,8 @@ set (PCH_SOURCES
maploader/slopes.cpp
maploader/glnodes.cpp
maploader/udmf.cpp
maploader/usdf.cpp
maploader/strifedialogue.cpp
maploader/polyobjects.cpp
maploader/renderinfo.cpp
maploader/compatibility.cpp
@ -1142,6 +1138,12 @@ set (PCH_SOURCES
gamedata/textures/formats/tgatexture.cpp
gamedata/textures/hires/hqresize.cpp
gamedata/textures/hires/hirestex.cpp
gamedata/fonts/singlelumpfont.cpp
gamedata/fonts/singlepicfont.cpp
gamedata/fonts/specialfont.cpp
gamedata/fonts/font.cpp
gamedata/fonts/v_font.cpp
gamedata/fonts/v_text.cpp
gamedata/p_xlat.cpp
gamedata/xlat/parse_xlat.cpp
gamedata/xlat/parsecontext.cpp
@ -1157,6 +1159,7 @@ set (PCH_SOURCES
intermission/intermission.cpp
intermission/intermission_parse.cpp
r_data/colormaps.cpp
r_data/cycler.cpp
r_data/gldefs.cpp
r_data/a_dynlightdata.cpp
r_data/r_translate.cpp
@ -1277,6 +1280,7 @@ set (PCH_SOURCES
utility/nodebuilder/nodebuild_gl.cpp
utility/nodebuilder/nodebuild_utility.cpp
utility/sc_man.cpp
utility/stats.cpp
utility/cmdlib.cpp
utility/colormatcher.cpp
utility/configfile.cpp
@ -1288,6 +1292,7 @@ set (PCH_SOURCES
utility/name.cpp
utility/s_playlist.cpp
utility/v_collection.cpp
utility/utf8.cpp
utility/zstrformat.cpp
)
@ -1355,7 +1360,9 @@ include_directories( .
g_shared
gamedata
gamedata/textures
gamedata/fonts
rendering
rendering/2d
sound
sound/oplsynth
sound/timidity
@ -1476,6 +1483,7 @@ source_group("External\\SFMT" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/s
source_group("FraggleScript" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/fragglescript/.+")
source_group("Game Data" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gamedata/.+")
source_group("Game Data\\Resource Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gamedata/resourcefiles/.+")
source_group("Game Data\\Fonts" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gamedata/fonts/.+")
source_group("Game Data\\Textures" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gamedata/textures/.+")
source_group("Game Data\\Textures\\Hires" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gamedata/textures/hires/.+")
source_group("Game Data\\Textures\\Hires\\HQ Resize" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gamedata/textures/hires/hqnx/.+")
@ -1486,6 +1494,7 @@ source_group("Intermission" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/int
source_group("Map Loader" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/maploader/.+")
source_group("Menu" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/menu/.+")
source_group("Rendering" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/rendering/.+")
source_group("Rendering\\2D" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/rendering/2d/.+")
source_group("Rendering\\Hardware Renderer" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl/.+")
source_group("Rendering\\Hardware Renderer\\Data" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/rendering/hwrenderer/data/.+")
source_group("Rendering\\Hardware Renderer\\Dynamic Lights" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/rendering/hwrenderer/dynlights/.+")

View file

@ -61,6 +61,7 @@
#include "c_consolebuffer.h"
#include "g_levellocals.h"
#include "vm.h"
#include "utf8.h"
#include "gi.h"
@ -167,17 +168,19 @@ struct History
struct FCommandBuffer
{
private:
FString Text; // The actual command line text
unsigned CursorPos;
unsigned StartPos; // First character to display
unsigned CursorPos = 0;
unsigned StartPos = 0; // First character to display
unsigned CursorPosChars = 0;
unsigned StartPosChars = 0;
FString YankBuffer; // Deleted text buffer
bool AppendToYankBuffer; // Append consecutive deletes to buffer
FCommandBuffer()
{
CursorPos = StartPos = 0;
}
public:
bool AppendToYankBuffer = false; // Append consecutive deletes to buffer
FCommandBuffer() = default;
FCommandBuffer(const FCommandBuffer &o)
{
@ -186,6 +189,16 @@ struct FCommandBuffer
StartPos = o.StartPos;
}
FString GetText() const
{
return Text;
}
size_t TextLength() const
{
return Text.Len();
}
void Draw(int x, int y, int scale, bool cursor)
{
if (scale == 1)
@ -197,7 +210,7 @@ struct FCommandBuffer
if (cursor)
{
screen->DrawChar(ConFont, CR_YELLOW,
x + ConFont->GetCharWidth(0x1c) + (CursorPos - StartPos) * ConFont->GetCharWidth(0xb),
x + ConFont->GetCharWidth(0x1c) + (CursorPosChars - StartPosChars) * ConFont->GetCharWidth(0xb),
y, '\xb', TAG_DONE);
}
}
@ -217,7 +230,7 @@ struct FCommandBuffer
if (cursor)
{
screen->DrawChar(ConFont, CR_YELLOW,
x + ConFont->GetCharWidth(0x1c) + (CursorPos - StartPos) * ConFont->GetCharWidth(0xb),
x + ConFont->GetCharWidth(0x1c) + (CursorPosChars - StartPosChars) * ConFont->GetCharWidth(0xb),
y, '\xb',
DTA_VirtualWidth, screen->GetWidth() / scale,
DTA_VirtualHeight, screen->GetHeight() / scale,
@ -226,108 +239,127 @@ struct FCommandBuffer
}
}
unsigned BytesForChars(unsigned chars)
{
unsigned bytes = 0;
while (chars > 0)
{
if ((Text[bytes++] & 0xc0) != 0x80) chars--;
}
return bytes;
}
void MakeStartPosGood()
{
int n = StartPos;
int n = StartPosChars;
unsigned cols = ConCols / active_con_scale();
if (StartPos >= Text.Len())
if (StartPosChars >= Text.CharacterCount())
{ // Start of visible line is beyond end of line
n = CursorPos - cols + 2;
n = CursorPosChars - cols + 2;
}
if ((CursorPos - StartPos) >= cols - 2)
if ((CursorPosChars - StartPosChars) >= cols - 2)
{ // The cursor is beyond the visible part of the line
n = CursorPos - cols + 2;
n = CursorPosChars - cols + 2;
}
if (StartPos > CursorPos)
if (StartPosChars > CursorPosChars)
{ // The cursor is in front of the visible part of the line
n = CursorPos;
n = CursorPosChars;
}
StartPos = MAX(0, n);
}
unsigned WordBoundaryRight()
{
unsigned index = CursorPos;
while (index < Text.Len() && Text[index] == ' ') {
index++;
}
while (index < Text.Len() && Text[index] != ' ') {
index++;
}
return index;
}
unsigned WordBoundaryLeft()
{
int index = CursorPos - 1;
while (index > -1 && Text[index] == ' ') {
index--;
}
while (index > -1 && Text[index] != ' ') {
index--;
}
return (unsigned)index + 1;
StartPosChars = MAX(0, n);
StartPos = BytesForChars(StartPosChars);
}
void CursorStart()
{
CursorPos = 0;
StartPos = 0;
CursorPosChars = 0;
StartPosChars = 0;
}
void CursorEnd()
{
CursorPos = (unsigned)Text.Len();
StartPos = 0;
CursorPosChars = (unsigned)Text.CharacterCount();
StartPosChars = 0;
MakeStartPosGood();
}
private:
void MoveCursorLeft()
{
CursorPosChars--;
do CursorPos--;
while ((Text[CursorPos] & 0xc0) == 0x80); // Step back to the last non-continuation byte.
}
void MoveCursorRight()
{
CursorPosChars++;
do CursorPos++;
while ((Text[CursorPos] & 0xc0) == 0x80); // Step back to the last non-continuation byte.
}
public:
void CursorLeft()
{
if (CursorPos > 0)
if (CursorPosChars > 0)
{
CursorPos--;
MoveCursorLeft();
MakeStartPosGood();
}
}
void CursorRight()
{
if (CursorPos < Text.Len())
if (CursorPosChars < Text.CharacterCount())
{
CursorPos++;
MoveCursorRight();
MakeStartPosGood();
}
}
void CursorWordLeft()
{
CursorPos = WordBoundaryLeft();
MakeStartPosGood();
if (CursorPosChars > 0)
{
do MoveCursorLeft();
while (CursorPosChars > 0 && Text[CursorPos - 1] != ' ');
MakeStartPosGood();
}
}
void CursorWordRight()
{
CursorPos = WordBoundaryRight();
MakeStartPosGood();
if (CursorPosChars < Text.CharacterCount())
{
do MoveCursorRight();
while (CursorPosChars < Text.CharacterCount() && Text[CursorPos] != ' ');
MakeStartPosGood();
}
}
void DeleteLeft()
{
if (CursorPos > 0)
{
Text.Remove(CursorPos - 1, 1);
CursorPos--;
auto now = CursorPos;
MoveCursorLeft();
Text.Remove(CursorPos, now - CursorPos);
MakeStartPosGood();
}
}
void DeleteRight()
{
if (CursorPos < Text.Len())
if (CursorPosChars < Text.CharacterCount())
{
Text.Remove(CursorPos, 1);
auto now = CursorPos;
MoveCursorRight();
Text.Remove(now, CursorPos - now);
CursorPos = now;
CursorPosChars--;
MakeStartPosGood();
}
}
@ -336,14 +368,16 @@ struct FCommandBuffer
{
if (CursorPos > 0)
{
unsigned index = WordBoundaryLeft();
auto now = CursorPos;
CursorWordLeft();
if (AppendToYankBuffer) {
YankBuffer = FString(&Text[index], CursorPos - index) + YankBuffer;
YankBuffer = FString(&Text[CursorPos], now - CursorPos) + YankBuffer;
} else {
YankBuffer = FString(&Text[index], CursorPos - index);
YankBuffer = FString(&Text[CursorPos], now - CursorPos);
}
Text.Remove(index, CursorPos - index);
CursorPos = index;
Text.Remove(CursorPos, now - CursorPos);
MakeStartPosGood();
}
}
@ -378,18 +412,22 @@ struct FCommandBuffer
void AddChar(int character)
{
///FIXME: Not Unicode-aware
if (CursorPos == Text.Len())
int size;
auto encoded = MakeUTF8(character, &size);
if (*encoded != 0)
{
Text += char(character);
if (Text.IsEmpty())
{
Text = encoded;
}
else
{
Text.Insert(CursorPos, (char*)encoded);
}
CursorPos += size;
CursorPosChars++;
MakeStartPosGood();
}
else
{
char foo = char(character);
Text.Insert(CursorPos, &foo, 1);
}
CursorPos++;
MakeStartPosGood();
}
void AddString(FString clip)
@ -401,6 +439,7 @@ struct FCommandBuffer
if (brk >= 0)
{
clip.Truncate(brk);
clip = MakeUTF8(clip.GetChars()); // Make sure that we actually have UTF-8 text.
}
if (Text.IsEmpty())
{
@ -411,16 +450,22 @@ struct FCommandBuffer
Text.Insert(CursorPos, clip);
}
CursorPos += (unsigned)clip.Len();
CursorPosChars += (unsigned)clip.CharacterCount();
MakeStartPosGood();
}
}
void SetString(FString str)
void SetString(const FString &str)
{
Text = str;
CursorPos = (unsigned)Text.Len();
Text = MakeUTF8(str);
CursorEnd();
MakeStartPosGood();
}
void AddYankBuffer()
{
AddString(YankBuffer);
}
};
static FCommandBuffer CmdLine;
@ -800,33 +845,39 @@ void FNotifyBuffer::AddString(int printlevel, FString source)
void AddToConsole (int printlevel, const char *text)
{
conbuffer->AddText(printlevel, text, Logfile);
conbuffer->AddText(printlevel, MakeUTF8(text), Logfile);
}
/* Adds a string to the console and also to the notify buffer */
int PrintString (int printlevel, const char *outline)
{
if (printlevel < msglevel || *outline == '\0')
{
return 0;
}
if (printlevel != PRINT_LOG)
if (printlevel != PRINT_LOG || Logfile != nullptr)
{
I_PrintStr (outline);
// Convert everything coming through here to UTF-8 so that all console text is in a consistent format
int count;
outline = MakeUTF8(outline, &count);
AddToConsole (printlevel, outline);
if (vidactive && screen && SmallFont)
if (printlevel != PRINT_LOG)
{
NotifyStrings.AddString(printlevel, outline);
I_PrintStr(outline);
conbuffer->AddText(printlevel, outline, Logfile);
if (vidactive && screen && SmallFont)
{
NotifyStrings.AddString(printlevel, outline);
}
}
else if (Logfile != nullptr)
{
fputs(outline, Logfile);
fflush(Logfile);
}
return count;
}
else if (Logfile != NULL)
{
fputs (outline, Logfile);
fflush (Logfile);
}
return (int)strlen (outline);
return 0; // Don't waste time on calculating this if nothing at all was printed...
}
extern bool gameisdead;
@ -1209,10 +1260,7 @@ void C_DrawConsole ()
{
if (gamestate != GS_STARTUP)
{
// Make a copy of the command line, in case an input event is handled
// while we draw the console and it changes.
FCommandBuffer command(CmdLine);
command.Draw(left, bottomline, textScale, cursoron);
CmdLine.Draw(left, bottomline, textScale, cursoron);
}
if (RowAdjust && ConBottom >= ConFont->GetHeight()*7/2)
{
@ -1494,7 +1542,7 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer)
break;
case 'D':
if (ev->data3 & GKM_CTRL && buffer.Text.Len() == 0)
if (ev->data3 & GKM_CTRL && buffer.TextLength() == 0)
{ // Control-D pressed on an empty line
if (strlen(con_ctrl_d) == 0)
{
@ -1509,16 +1557,18 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer)
// Intentional fall-through for command(s) added with Ctrl-D
case '\r':
{
// Execute command line (ENTER)
FString bufferText = buffer.GetText();
buffer.Text.StripLeftRight();
Printf(127, TEXTCOLOR_WHITE "]%s\n", buffer.Text.GetChars());
bufferText.StripLeftRight();
Printf(127, TEXTCOLOR_WHITE "]%s\n", bufferText.GetChars());
if (buffer.Text.Len() == 0)
if (bufferText.Len() == 0)
{
// Command line is empty, so do nothing to the history
// Command line is empty, so do nothing to the history
}
else if (HistHead && HistHead->String.CompareNoCase(buffer.Text) == 0)
else if (HistHead && HistHead->String.CompareNoCase(bufferText) == 0)
{
// Command line was the same as the previous one,
// so leave the history list alone
@ -1530,7 +1580,7 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer)
// so add it to the history list.
History *temp = new History;
temp->String = buffer.Text;
temp->String = bufferText;
temp->Older = HistHead;
if (HistHead)
{
@ -1556,16 +1606,12 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer)
}
}
HistPos = NULL;
{
// Work with a copy of command to avoid side effects caused by
// exception raised during execution, like with 'error' CCMD.
FString copy = buffer.Text;
buffer.SetString("");
AddCommandString(copy);
}
buffer.SetString("");
AddCommandString(bufferText);
TabbedLast = false;
TabbedList = false;
break;
}
case '`':
// Check to see if we have ` bound to the console before accepting
@ -1606,9 +1652,9 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer)
{
if (data1 == 'C')
{ // copy to clipboard
if (buffer.Text.IsNotEmpty())
if (buffer.TextLength() > 0)
{
I_PutInClipboard(buffer.Text);
I_PutInClipboard(buffer.GetText());
}
}
else
@ -1663,7 +1709,7 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer)
case 'Y':
if (ev->data3 & GKM_CTRL)
{
buffer.AddString(buffer.YankBuffer);
buffer.AddYankBuffer();
TabbedLast = false;
TabbedList = false;
HistPos = NULL;
@ -1906,25 +1952,27 @@ static void C_TabComplete (bool goForward)
unsigned i;
int diffpoint;
auto CmdLineText = CmdLine.GetText();
if (!TabbedLast)
{
bool cancomplete;
// Skip any spaces at beginning of command line
for (i = 0; i < CmdLine.Text.Len(); ++i)
for (i = 0; i < CmdLineText.Len(); ++i)
{
if (CmdLine.Text[i] != ' ')
if (CmdLineText[i] != ' ')
break;
}
if (i == CmdLine.Text.Len())
if (i == CmdLineText.Len())
{ // Line was nothing but spaces
return;
}
TabStart = i;
TabSize = (int)CmdLine.Text.Len() - TabStart;
TabSize = (int)CmdLineText.Len() - TabStart;
if (!FindTabCommand(&CmdLine.Text[TabStart], &TabPos, TabSize))
if (!FindTabCommand(&CmdLineText[TabStart], &TabPos, TabSize))
return; // No initial matches
// Show a list of possible completions, if more than one.
@ -1947,7 +1995,7 @@ static void C_TabComplete (bool goForward)
{ // Find the last matching tab, then go one past it.
while (++TabPos < (int)TabCommands.Size())
{
if (FindDiffPoint(TabCommands[TabPos].TabName, &CmdLine.Text[TabStart]) < TabSize)
if (FindDiffPoint(TabCommands[TabPos].TabName, &CmdLineText[TabStart]) < TabSize)
{
break;
}
@ -1964,25 +2012,25 @@ static void C_TabComplete (bool goForward)
(!goForward && --TabPos < 0))
{
TabbedLast = false;
CmdLine.Text.Truncate(TabSize);
CmdLineText.Truncate(TabSize);
}
else
{
diffpoint = FindDiffPoint(TabCommands[TabPos].TabName, &CmdLine.Text[TabStart]);
diffpoint = FindDiffPoint(TabCommands[TabPos].TabName, &CmdLineText[TabStart]);
if (diffpoint < TabSize)
{
// No more matches
TabbedLast = false;
CmdLine.Text.Truncate(TabSize - TabStart);
CmdLineText.Truncate(TabSize - TabStart);
}
else
{
CmdLine.Text.Truncate(TabStart);
CmdLine.Text << TabCommands[TabPos].TabName << ' ';
CmdLineText.Truncate(TabStart);
CmdLineText << TabCommands[TabPos].TabName << ' ';
}
}
CmdLine.CursorPos = (unsigned)CmdLine.Text.Len();
CmdLine.SetString(CmdLineText);
CmdLine.MakeStartPosGood();
}
@ -1995,9 +2043,10 @@ static bool C_TabCompleteList ()
nummatches = 0;
maxwidth = 0;
auto CmdLineText = CmdLine.GetText();
for (i = TabPos; i < (int)TabCommands.Size(); ++i)
{
if (FindDiffPoint (TabCommands[i].TabName, &CmdLine.Text[TabStart]) < TabSize)
if (FindDiffPoint (TabCommands[i].TabName, &CmdLineText[TabStart]) < TabSize)
{
break;
}
@ -2022,7 +2071,7 @@ static bool C_TabCompleteList ()
{
size_t x = 0;
maxwidth += 3;
Printf (TEXTCOLOR_BLUE "Completions for %s:\n", CmdLine.Text.GetChars());
Printf (TEXTCOLOR_BLUE "Completions for %s:\n", CmdLineText.GetChars());
for (i = TabPos; nummatches > 0; ++i, --nummatches)
{
// [Dusk] Print console commands blue, CVars green, aliases red.
@ -2054,9 +2103,9 @@ static bool C_TabCompleteList ()
if (TabSize != commonsize)
{
TabSize = commonsize;
CmdLine.Text.Truncate(TabStart);
CmdLine.Text.AppendCStrPart(TabCommands[TabPos].TabName.GetChars(), commonsize);
CmdLine.CursorPos = (unsigned)CmdLine.Text.Len();
CmdLineText.Truncate(TabStart);
CmdLineText.AppendCStrPart(TabCommands[TabPos].TabName.GetChars(), commonsize);
CmdLine.SetString(CmdLineText);
}
return false;
}

View file

@ -270,8 +270,7 @@ void FConsoleBuffer::FormatText(FFont *formatfont, int displaywidth)
unsigned brokensize = m_BrokenConsoleText.Size();
if (brokensize == mConsoleText.Size())
{
// The last line got text appended. We have to wait until here to format it because
// it is possible that during display new text will be added from the NetUpdate calls in the software version of DrawTextureV.
// The last line got text appended.
if (mLastLineNeedsUpdate)
{
brokensize--;

View file

@ -1569,13 +1569,14 @@ void FExecList::AddPullins(TArray<FString> &wads) const
FExecList *C_ParseExecFile(const char *file, FExecList *exec)
{
FILE *f;
char cmd[4096];
int retval = 0;
if ( (f = fopen (file, "r")) )
FileReader fr;
if ( (fr.OpenFile(file)) )
{
while (fgets(cmd, countof(cmd)-1, f))
while (fr.Gets(cmd, countof(cmd)-1))
{
// Comments begin with //
char *stop = cmd + strlen(cmd) - 1;
@ -1611,11 +1612,6 @@ FExecList *C_ParseExecFile(const char *file, FExecList *exec)
}
exec->AddCommand(cmd, file);
}
if (!feof(f))
{
Printf("Error parsing \"%s\"\n", file);
}
fclose(f);
}
else
{

View file

@ -1,37 +0,0 @@
#pragma once
// System independent critical sections without polluting the namespace with the operating system headers.
class FInternalCriticalSection;
FInternalCriticalSection *CreateCriticalSection();
void DeleteCriticalSection(FInternalCriticalSection *c);
void EnterCriticalSection(FInternalCriticalSection *c);
void LeaveCriticalSection(FInternalCriticalSection *c);
// This is just a convenience wrapper around the function interface.
class FCriticalSection
{
public:
FCriticalSection()
{
c = CreateCriticalSection();
}
~FCriticalSection()
{
DeleteCriticalSection(c);
}
void Enter()
{
EnterCriticalSection(c);
}
void Leave()
{
LeaveCriticalSection(c);
}
private:
FInternalCriticalSection *c;
};

View file

@ -39,14 +39,13 @@
#include "d_event.h"
#include "sbar.h"
#include "v_video.h"
#include "utf8.h"
#define QUEUESIZE 128
#define MESSAGESIZE 128
#define MESSAGELEN 265
#define HU_INPUTX 0
#define HU_INPUTY (0 + (screen->Font->GetHeight () + 1))
enum
{
QUEUESIZE = 128
};
void CT_PasteChat(const char *clip);
EXTERN_CVAR (Int, con_scaletext)
@ -61,19 +60,20 @@ int active_con_scaletext();
void CT_Init ();
void CT_Drawer ();
bool CT_Responder (event_t *ev);
void CT_PasteChat(const char *clip);
int chatmodeon;
// Private data
static void CT_ClearChatMessage ();
static void CT_AddChar (char c);
static void CT_AddChar (int c);
static void CT_BackSpace ();
static void ShoveChatStr (const char *str, uint8_t who);
static bool DoSubstitution (FString &out, const char *in);
static int len;
static uint8_t ChatQueue[QUEUESIZE];
static int CharLen;
static TArray<uint8_t> ChatQueue;
CVAR (String, chatmacro1, "I'm ready to kick butt!", CVAR_ARCHIVE)
CVAR (String, chatmacro2, "I'm OK.", CVAR_ARCHIVE)
@ -111,9 +111,9 @@ CVAR (Bool, chat_substitution, false, CVAR_ARCHIVE)
void CT_Init ()
{
len = 0; // initialize the queue index
ChatQueue.Clear();
CharLen = 0;
chatmodeon = 0;
ChatQueue[0] = 0;
}
//===========================================================================
@ -141,7 +141,9 @@ bool CT_Responder (event_t *ev)
{
if (ev->data1 == '\r')
{
ShoveChatStr ((char *)ChatQueue, chatmodeon - 1);
ChatQueue.Push(0);
ShoveChatStr ((char *)ChatQueue.Data(), chatmodeon - 1);
ChatQueue.Pop();
CT_Stop ();
return true;
}
@ -161,7 +163,9 @@ bool CT_Responder (event_t *ev)
else if (ev->data1 == 'C' && (ev->data3 & GKM_CTRL))
#endif // __APPLE__
{
I_PutInClipboard ((char *)ChatQueue);
ChatQueue.Push(0);
I_PutInClipboard ((char *)ChatQueue.Data());
ChatQueue.Pop();
return true;
}
#ifdef __APPLE__
@ -183,7 +187,7 @@ bool CT_Responder (event_t *ev)
}
else
{
CT_AddChar (char(ev->data1));
CT_AddChar (ev->data1);
}
return true;
}
@ -206,16 +210,17 @@ bool CT_Responder (event_t *ev)
void CT_PasteChat(const char *clip)
{
if (clip != NULL && *clip != '\0')
if (clip != nullptr && *clip != '\0')
{
auto p = (const uint8_t *)clip;
// Only paste the first line.
while (*clip != '\0')
while (auto chr = GetCharFromString(p))
{
if (*clip == '\n' || *clip == '\r' || *clip == '\b')
if (chr == '\n' || chr == '\r' || chr == '\b')
{
break;
}
CT_AddChar (*clip++);
CT_AddChar (chr);
}
}
}
@ -228,10 +233,11 @@ void CT_PasteChat(const char *clip)
void CT_Drawer (void)
{
FFont *displayfont = ConFont;
if (chatmodeon)
{
static const char *prompt = "Say: ";
int i, x, scalex, y, promptwidth;
int x, scalex, y, promptwidth;
y = (viewactive || gamestate != GS_LEVEL) ? -10 : -30;
@ -243,33 +249,25 @@ void CT_Drawer (void)
y += ((SCREENHEIGHT == viewheight && viewactive) || gamestate != GS_LEVEL) ? screen_height : st_y;
promptwidth = SmallFont->StringWidth (prompt) * scalex;
x = SmallFont->GetCharWidth ('_') * scalex * 2 + promptwidth;
promptwidth = displayfont->StringWidth (prompt) * scalex;
x = displayfont->GetCharWidth (displayfont->GetCursor()) * scalex * 2 + promptwidth;
// figure out if the text is wider than the screen->
FString printstr = ChatQueue;
// figure out if the text is wider than the screen
// if so, only draw the right-most portion of it.
for (i = len - 1; i >= 0 && x < screen_width; i--)
const uint8_t *textp = (const uint8_t*)printstr.GetChars();
while(*textp)
{
x += SmallFont->GetCharWidth (ChatQueue[i] & 0x7f) * scalex;
auto textw = displayfont->StringWidth(textp);
if (x + textw * scalex < screen_width) break;
GetCharFromString(textp);
}
printstr += displayfont->GetCursor();
if (i >= 0)
{
i++;
}
else
{
i = 0;
}
// draw the prompt, text, and cursor
ChatQueue[len] = SmallFont->GetCursor();
ChatQueue[len+1] = '\0';
screen->DrawText (SmallFont, CR_GREEN, 0, y, prompt,
screen->DrawText (displayfont, CR_GREEN, 0, y, prompt,
DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE);
screen->DrawText (SmallFont, CR_GREY, promptwidth, y, (char *)(ChatQueue + i),
screen->DrawText (displayfont, CR_GREY, promptwidth, y, printstr,
DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE);
ChatQueue[len] = '\0';
}
if (players[consoleplayer].camera != NULL &&
@ -289,12 +287,20 @@ void CT_Drawer (void)
//
//===========================================================================
static void CT_AddChar (char c)
static void CT_AddChar (int c)
{
if (len < QUEUESIZE-2)
if (CharLen < QUEUESIZE-2)
{
ChatQueue[len++] = c;
ChatQueue[len] = 0;
int size;
auto encode = MakeUTF8(c, &size);
if (*encode)
{
for (int i = 0; i < size; i++)
{
ChatQueue.Push(encode[i]);
}
CharLen++;
}
}
}
@ -307,9 +313,12 @@ static void CT_AddChar (char c)
static void CT_BackSpace ()
{
if (len)
if (CharLen)
{
ChatQueue[--len] = 0;
int endpos = ChatQueue.Size() - 1;
while (endpos > 0 && ChatQueue[endpos] >= 0x80 && ChatQueue[endpos] < 0xc0) endpos--;
ChatQueue.Clamp(endpos);
CharLen--;
}
}
@ -322,8 +331,7 @@ static void CT_BackSpace ()
static void CT_ClearChatMessage ()
{
ChatQueue[0] = 0;
len = 0;
ChatQueue.Clear();
}
//===========================================================================
@ -355,11 +363,11 @@ static void ShoveChatStr (const char *str, uint8_t who)
if (!chat_substitution || !DoSubstitution (substBuff, str))
{
Net_WriteString (str);
Net_WriteString(MakeUTF8(str));
}
else
{
Net_WriteString (substBuff);
Net_WriteString(MakeUTF8(substBuff));
}
}
@ -392,9 +400,9 @@ static bool DoSubstitution (FString &out, const char *in)
++b;
}
ptrdiff_t len = b - a;
ptrdiff_t ByteLen = b - a;
if (len == 6)
if (ByteLen == 6)
{
if (strnicmp(a, "health", 6) == 0)
{
@ -412,7 +420,7 @@ static bool DoSubstitution (FString &out, const char *in)
}
}
}
else if (len == 5)
else if (ByteLen == 5)
{
if (strnicmp(a, "armor", 5) == 0)
{
@ -420,7 +428,7 @@ static bool DoSubstitution (FString &out, const char *in)
out.AppendFormat("%d", armor != NULL ? armor->IntVar(NAME_Amount) : 0);
}
}
else if (len == 9)
else if (ByteLen == 9)
{
if (strnicmp(a, "ammocount", 9) == 0)
{
@ -438,7 +446,7 @@ static bool DoSubstitution (FString &out, const char *in)
}
}
}
else if (len == 4)
else if (ByteLen == 4)
{
if (strnicmp(a, "ammo", 4) == 0)
{
@ -456,7 +464,7 @@ static bool DoSubstitution (FString &out, const char *in)
}
}
}
else if (len == 0)
else if (ByteLen == 0)
{
out += '$';
if (*b == '$')
@ -467,7 +475,7 @@ static bool DoSubstitution (FString &out, const char *in)
else
{
out += '$';
out.AppendCStrPart(a, len);
out.AppendCStrPart(a, ByteLen);
}
a = b;
}

View file

@ -163,6 +163,13 @@ void FIWadManager::ParseIWadInfo(const char *fn, const char *data, int datasize,
sc.MustGetString();
iwad->BkColor = V_GetColor(NULL, sc);
}
else if (sc.Compare("IgnoreTitlePatches"))
{
sc.MustGetStringName("=");
sc.MustGetNumber();
if (sc.Number) iwad->flags |= GI_IGNORETITLEPATCHES;
else iwad->flags &= ~GI_IGNORETITLEPATCHES;
}
else if (sc.Compare("Load"))
{
sc.MustGetStringName("=");

View file

@ -2027,8 +2027,6 @@ static void D_DoomInit()
gamestate = GS_STARTUP;
SetLanguageIDs ();
const char *v = Args->CheckValue("-rngseed");
if (v)
{
@ -2428,7 +2426,7 @@ void D_DoomMain (void)
}
// [RH] Initialize localizable strings.
GStrings.LoadStrings (false);
GStrings.LoadStrings ();
V_InitFontColors ();
@ -2679,7 +2677,7 @@ void D_DoomMain (void)
if (StoredWarp.IsNotEmpty())
{
AddCommandString(StoredWarp);
StoredWarp = NULL;
StoredWarp = "";
}
}
else

View file

@ -160,7 +160,7 @@ public:
int GetIWadFlags(unsigned int num) const
{
if (num < mIWadInfos.Size()) return mIWadInfos[num].flags;
else return false;
else return 0;
}
};

View file

@ -933,7 +933,7 @@ void ReadUserInfo(FSerializer &arc, userinfo_t &info, FString &skin)
const char *str;
info.Reset();
skin = NULL;
skin = "";
if (arc.BeginObject("userinfo"))
{
while ((key = arc.GetKey()))

View file

@ -151,8 +151,6 @@ enum
extern AActor *WP_NOCHANGE;
#define MAXPLAYERNAME 15
// [GRB] Custom player classes
enum
{

View file

@ -56,10 +56,10 @@ CUSTOM_CVAR (Float, teamdamage, 0.f, CVAR_SERVERINFO)
}
}
CUSTOM_CVAR (String, language, "auto", CVAR_ARCHIVE)
CUSTOM_CVAR (String, language, "auto", CVAR_ARCHIVE|CVAR_NOINITCALL)
{
SetLanguageIDs ();
GStrings.LoadStrings (false);
GStrings.UpdateLanguage();
for (auto Level : AllLevels())
{
// does this even make sense on secondary levels...?

View file

@ -41,6 +41,7 @@
#include "d_net.h"
#include "g_game.h"
#include "info.h"
#include "utf8.h"
EventManager staticEventManager;
EventManager eventManager;
@ -1026,7 +1027,7 @@ FUiEvent::FUiEvent(const event_t *ev)
break;
case EV_GUI_Char:
KeyChar = ev->data1;
KeyString = FString(char(ev->data1));
KeyString = MakeUTF8(ev->data1);
IsAlt = !!ev->data2; // only true for Win32, not sure about SDL
break;
default: // mouse event

View file

@ -349,3 +349,58 @@ CCMD(targetinv)
"the NOBLOCKMAP flag or have height/radius of 0.\n");
}
//==========================================================================
//
// Lists all currently defined maps
//
//==========================================================================
CCMD(listmaps)
{
for (unsigned i = 0; i < wadlevelinfos.Size(); i++)
{
level_info_t *info = &wadlevelinfos[i];
MapData *map = P_OpenMapData(info->MapName, true);
if (map != NULL)
{
Printf("%s: '%s' (%s)\n", info->MapName.GetChars(), info->LookupLevelName().GetChars(),
Wads.GetWadName(Wads.GetLumpFile(map->lumpnum)));
delete map;
}
}
}
//==========================================================================
//
// For testing sky fog sheets
//
//==========================================================================
CCMD(skyfog)
{
if (argv.argc() > 1)
{
// Do this only on the primary level.
primaryLevel->skyfog = MAX(0, (int)strtoull(argv[1], NULL, 0));
}
}
//==========================================================================
//
//
//==========================================================================
CCMD(listsnapshots)
{
for (unsigned i = 0; i < wadlevelinfos.Size(); ++i)
{
FCompressedBuffer *snapshot = &wadlevelinfos[i].Snapshot;
if (snapshot->mBuffer != nullptr)
{
Printf("%s (%u -> %u bytes)\n", wadlevelinfos[i].MapName.GetChars(), snapshot->mCompressedSize, snapshot->mSize);
}
}
}

View file

@ -174,9 +174,7 @@ uint8_t* zdembodyend; // end of ZDEM BODY chunk
bool singledemo; // quit after playing a demo from cmdline
bool precache = true; // if true, load all graphics at start
wbstartstruct_t wminfo; // parms for world map / intermission
short consistancy[MAXPLAYERS][BACKUPTICS];

View file

@ -129,12 +129,13 @@ void G_LeavingHub(FLevelLocals *Level, int mode, cluster_info_t * cluster, wbsta
{
if (cluster->flags & CLUSTER_LOOKUPNAME)
{
Level->LevelName = GStrings(cluster->ClusterName);
wbs->thisname = GStrings(cluster->ClusterName);
}
else
{
Level->LevelName = cluster->ClusterName;
wbs->thisname = cluster->ClusterName;
}
wbs->LName0.SetInvalid(); // The level's own name was just invalidated, and so was its name patch.
}
}
}

View file

@ -81,6 +81,7 @@
#include "a_dynlight.h"
#include "p_conversation.h"
#include "p_effect.h"
#include "stringtable.h"
#include "gi.h"
@ -753,6 +754,7 @@ void FLevelLocals::SecretExitLevel (int position)
//
//
//==========================================================================
static wbstartstruct_t staticWmInfo;
void G_DoCompleted (void)
{
@ -779,7 +781,7 @@ void G_DoCompleted (void)
// Close the conversation menu if open.
P_FreeStrifeConversations ();
if (primaryLevel->DoCompleted(nextlevel, wminfo))
if (primaryLevel->DoCompleted(nextlevel, staticWmInfo))
{
gamestate = GS_INTERMISSION;
viewactive = false;
@ -789,10 +791,16 @@ void G_DoCompleted (void)
// if (statcopy)
// memcpy (statcopy, &wminfo, sizeof(wminfo));
WI_Start (&wminfo);
WI_Start (&staticWmInfo);
}
}
//==========================================================================
//
// Prepare the level to be exited and
// set up the wminfo struct for the coming intermission screen
//
//==========================================================================
bool FLevelLocals::DoCompleted (FString nextlevel, wbstartstruct_t &wminfo)
{
@ -802,29 +810,57 @@ bool FLevelLocals::DoCompleted (FString nextlevel, wbstartstruct_t &wminfo)
if (!(flags & LEVEL_CHANGEMAPCHEAT))
FindLevelInfo (MapName)->flags |= LEVEL_VISITED;
uint32_t langtable[2] = {};
wminfo.finished_ep = cluster - 1;
wminfo.LName0 = TexMan.CheckForTexture(info->PName, ETextureType::MiscPatch);
wminfo.thisname = info->LookupLevelName(&langtable[0]); // re-get the name so we have more info about its origin.
wminfo.current = MapName;
if (deathmatch &&
(dmflags & DF_SAME_LEVEL) &&
(*dmflags & DF_SAME_LEVEL) &&
!(flags & LEVEL_CHANGEMAPCHEAT))
{
wminfo.next = MapName;
wminfo.LName1 = wminfo.LName0;
wminfo.nextname = wminfo.thisname;
}
else
{
level_info_t *nextinfo = FindLevelInfo (nextlevel, false);
if (nextinfo == NULL || strncmp (nextlevel, "enDSeQ", 6) == 0)
{
wminfo.next = nextlevel;
wminfo.next = "";
wminfo.LName1.SetInvalid();
wminfo.nextname = "";
}
else
{
wminfo.next = nextinfo->MapName;
wminfo.LName1 = TexMan.CheckForTexture(nextinfo->PName, ETextureType::MiscPatch);
wminfo.nextname = nextinfo->LookupLevelName(&langtable[1]);
}
}
// This cannot use any common localization logic because it may not replace user content at all.
// Unlike the menus, replacements here do not merely change the style but also the content.
// On the other hand, the IWAD lumps may always be replaced with text, because they are the same style as the BigFont.
if (gameinfo.flags & GI_IGNORETITLEPATCHES)
{
FTextureID *texids[] = { &wminfo.LName0, &wminfo.LName1 };
for (int i = 0; i < 2; i++)
{
if (texids[i]->isValid() && langtable[i] != FStringTable::default_table)
{
FTexture *tex = TexMan.GetTexture(*texids[i]);
if (tex != nullptr)
{
int filenum = Wads.GetLumpFile(tex->GetSourceLump());
if (filenum >= 0 && filenum <= Wads.GetIwadNum())
{
texids[i]->SetInvalid();
}
}
}
}
}
@ -1296,7 +1332,7 @@ DEFINE_ACTION_FUNCTION(FLevelLocals, WorldDone)
void G_DoWorldDone (void)
{
gamestate = GS_LEVEL;
if (wminfo.next[0] == 0)
if (nextlevel.IsEmpty())
{
// Don't crash if no next map is given. Just repeat the current one.
Printf ("No next map specified.\n");
@ -1598,68 +1634,6 @@ void FLevelLocals::Init()
notexturefill = info->notexturefill < 0 ? gl_notexturefill : !!info->notexturefill;
}
//==========================================================================
//
//
//==========================================================================
bool FLevelLocals::IsJumpingAllowed() const
{
if (dmflags & DF_NO_JUMP)
return false;
if (dmflags & DF_YES_JUMP)
return true;
return !(flags & LEVEL_JUMP_NO);
}
DEFINE_ACTION_FUNCTION(FLevelLocals, IsJumpingAllowed)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
ACTION_RETURN_BOOL(self->IsJumpingAllowed());
}
//==========================================================================
//
//
//==========================================================================
bool FLevelLocals::IsCrouchingAllowed() const
{
if (dmflags & DF_NO_CROUCH)
return false;
if (dmflags & DF_YES_CROUCH)
return true;
return !(flags & LEVEL_CROUCH_NO);
}
DEFINE_ACTION_FUNCTION(FLevelLocals, IsCrouchingAllowed)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
ACTION_RETURN_BOOL(self->IsCrouchingAllowed());
}
//==========================================================================
//
//
//==========================================================================
bool FLevelLocals::IsFreelookAllowed() const
{
if (dmflags & DF_NO_FREELOOK)
return false;
if (dmflags & DF_YES_FREELOOK)
return true;
return !(flags & LEVEL_FREELOOK_NO);
}
DEFINE_ACTION_FUNCTION(FLevelLocals, IsFreelookAllowed)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
ACTION_RETURN_BOOL(self->IsFreelookAllowed());
}
//==========================================================================
//
//
@ -1699,89 +1673,6 @@ void FLevelLocals::AirControlChanged ()
}
}
//==========================================================================
//
// Archives the current level
//
//==========================================================================
void FLevelLocals::SnapshotLevel ()
{
info->Snapshot.Clean();
if (info->isValid())
{
FSerializer arc(this);
if (arc.OpenWriter(save_formatted))
{
SaveVersion = SAVEVER;
Serialize(arc, false);
info->Snapshot = arc.GetCompressedOutput();
}
}
}
//==========================================================================
//
// Unarchives the current level based on its snapshot
// The level should have already been loaded and setup.
//
//==========================================================================
void FLevelLocals::UnSnapshotLevel (bool hubLoad)
{
if (info->Snapshot.mBuffer == nullptr)
return;
if (info->isValid())
{
FSerializer arc(this);
if (!arc.OpenReader(&info->Snapshot))
{
I_Error("Failed to load savegame");
return;
}
Serialize (arc, hubLoad);
FromSnapshot = true;
auto it = GetThinkerIterator<AActor>(NAME_PlayerPawn);
AActor *pawn, *next;
next = it.Next();
while ((pawn = next) != 0)
{
next = it.Next();
if (pawn->player == nullptr || pawn->player->mo == nullptr || !PlayerInGame(pawn->player))
{
int i;
// If this isn't the unmorphed original copy of a player, destroy it, because it's extra.
for (i = 0; i < MAXPLAYERS; ++i)
{
if (PlayerInGame(i) && Players[i]->morphTics && Players[i]->mo->alternative == pawn)
{
break;
}
}
if (i == MAXPLAYERS)
{
pawn->Destroy ();
}
}
}
arc.Close();
}
// No reason to keep the snapshot around once the level's been entered.
info->Snapshot.Clean();
if (hubLoad)
{
// Unlock ACS global strings that were locked when the snapshot was made.
Behaviors.UnlockLevelVarStrings(levelnum);
}
}
//==========================================================================
//
//
@ -1932,23 +1823,6 @@ void G_ReadVisited(FSerializer &arc)
//
//==========================================================================
CCMD(listsnapshots)
{
for (unsigned i = 0; i < wadlevelinfos.Size(); ++i)
{
FCompressedBuffer *snapshot = &wadlevelinfos[i].Snapshot;
if (snapshot->mBuffer != nullptr)
{
Printf("%s (%u -> %u bytes)\n", wadlevelinfos[i].MapName.GetChars(), snapshot->mCompressedSize, snapshot->mSize);
}
}
}
//==========================================================================
//
//
//==========================================================================
void P_WriteACSDefereds (FSerializer &arc)
{
bool found = false;
@ -2286,64 +2160,3 @@ int IsPointInMap(FLevelLocals *Level, double x, double y, double z)
return true;
}
//==========================================================================
//
// Lists all currently defined maps
//
//==========================================================================
CCMD(listmaps)
{
for(unsigned i = 0; i < wadlevelinfos.Size(); i++)
{
level_info_t *info = &wadlevelinfos[i];
MapData *map = P_OpenMapData(info->MapName, true);
if (map != NULL)
{
Printf("%s: '%s' (%s)\n", info->MapName.GetChars(), info->LookupLevelName().GetChars(),
Wads.GetWadName(Wads.GetLumpFile(map->lumpnum)));
delete map;
}
}
}
//==========================================================================
//
// For testing sky fog sheets
//
//==========================================================================
CCMD(skyfog)
{
if (argv.argc()>1)
{
// Do this only on the primary level.
primaryLevel->skyfog = MAX(0, (int)strtoull(argv[1], NULL, 0));
}
}
//==========================================================================
//
// ZScript counterpart to ACS ChangeSky, uses TextureIDs
//
//==========================================================================
DEFINE_ACTION_FUNCTION(FLevelLocals, ChangeSky)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
PARAM_INT(sky1);
PARAM_INT(sky2);
self->skytexture1 = FSetTextureID(sky1);
self->skytexture2 = FSetTextureID(sky2);
InitSkyMap(self);
return 0;
}
DEFINE_ACTION_FUNCTION(FLevelLocals, StartIntermission)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
PARAM_NAME(seq);
PARAM_INT(state);
F_StartIntermission(seq, (uint8_t)state);
return 0;
}

View file

@ -143,6 +143,13 @@ private:
void AddDisplacementForPortal(FSectorPortal *portal);
void AddDisplacementForPortal(FLinePortal *portal);
bool ConnectPortalGroups();
void SerializePlayers(FSerializer &arc, bool skipload);
void CopyPlayer(player_t *dst, player_t *src, const char *name);
void ReadOnePlayer(FSerializer &arc, bool skipload);
void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload);
void SerializeSounds(FSerializer &arc);
public:
void SnapshotLevel();
void UnSnapshotLevel(bool hubLoad);
@ -646,9 +653,47 @@ public:
TObjPtr<DSpotState *> SpotState = nullptr;
bool IsJumpingAllowed() const;
bool IsCrouchingAllowed() const;
bool IsFreelookAllowed() const;
//==========================================================================
//
//
//==========================================================================
bool IsJumpingAllowed() const
{
if (dmflags & DF_NO_JUMP)
return false;
if (dmflags & DF_YES_JUMP)
return true;
return !(flags & LEVEL_JUMP_NO);
}
//==========================================================================
//
//
//==========================================================================
bool IsCrouchingAllowed() const
{
if (dmflags & DF_NO_CROUCH)
return false;
if (dmflags & DF_YES_CROUCH)
return true;
return !(flags & LEVEL_CROUCH_NO);
}
//==========================================================================
//
//
//==========================================================================
bool IsFreelookAllowed() const
{
if (dmflags & DF_NO_FREELOOK)
return false;
if (dmflags & DF_YES_FREELOOK)
return true;
return !(flags & LEVEL_FREELOOK_NO);
}
node_t *HeadNode() const
{

View file

@ -1,7 +1,7 @@
#pragma once
#include "c_cvars.h"
#include "actor.h"
#include "cycler.h"
#include "r_data/cycler.h"
#include "g_levellocals.h"
struct side_t;

View file

@ -713,7 +713,7 @@ DHUDMessageTypeOnFadeOut::DHUDMessageTypeOnFadeOut (FFont *font, const char *tex
if (TypeOnTime == 0.f)
TypeOnTime = 0.1f;
CurrLine = 0;
LineLen = (int)Lines[0].Text.Len();
LineLen = Lines.Size() > 0? (int)Lines[0].Text.Len() : 0;
LineVisible = 0;
State = 3;
}
@ -741,7 +741,7 @@ void DHUDMessageTypeOnFadeOut::Serialize(FSerializer &arc)
bool DHUDMessageTypeOnFadeOut::Tick ()
{
if (!Super::Tick ())
if (LineLen > 0 && !Super::Tick ())
{
if (State == 3)
{
@ -771,7 +771,8 @@ bool DHUDMessageTypeOnFadeOut::Tick ()
if (State == 3 && --step >= 0)
{
linedrawcount++;
if (text[linevis++] == TEXTCOLOR_ESCAPE)
if (text.GetNextCharacter(linevis) == TEXTCOLOR_ESCAPE)
{
if (text[linevis] == '[')
{ // named color

View file

@ -1356,20 +1356,20 @@ public:
{
Scale = { 1.,1. };
}
while(*str != '\0')
int ch;
while (ch = GetCharFromString(str), ch != '\0')
{
if(*str == ' ')
if(ch == ' ')
{
if(script->spacingCharacter == '\0')
ax += font->GetSpaceWidth();
else
ax += font->GetCharWidth((unsigned char) script->spacingCharacter);
str++;
continue;
}
else if(*str == TEXTCOLOR_ESCAPE)
else if(ch == TEXTCOLOR_ESCAPE)
{
EColorRange newColor = V_ParseFontColor(++str, translation, boldTranslation);
EColorRange newColor = V_ParseFontColor(str, translation, boldTranslation);
if(newColor != CR_UNDEFINED)
fontcolor = newColor;
continue;
@ -1377,17 +1377,15 @@ public:
int width;
if(script->spacingCharacter == '\0') //No monospace?
width = font->GetCharWidth((unsigned char) *str);
width = font->GetCharWidth(ch);
else
width = font->GetCharWidth((unsigned char) script->spacingCharacter);
bool redirected = false;
FTexture* c = font->GetChar((unsigned char) *str, fontcolor, &width);
FTexture* c = font->GetChar(ch, fontcolor, &width);
if(c == NULL) //missing character.
{
str++;
continue;
}
int character = (unsigned char)*str;
if (script->spacingCharacter == '\0') //If we are monospaced lets use the offset
ax += (c->GetDisplayLeftOffset() + 1); //ignore x offsets since we adapt to character size
@ -1441,14 +1439,14 @@ public:
double salpha = (Alpha *HR_SHADOW);
double srx = rx + (shadowX*Scale.X);
double sry = ry + (shadowY*Scale.Y);
screen->DrawChar(font, CR_UNTRANSLATED, srx, sry, character,
screen->DrawChar(font, CR_UNTRANSLATED, srx, sry, ch,
DTA_DestWidthF, rw,
DTA_DestHeightF, rh,
DTA_Alpha, salpha,
DTA_FillColor, 0,
TAG_DONE);
}
screen->DrawChar(font, fontcolor, rx, ry, character,
screen->DrawChar(font, fontcolor, rx, ry, ch,
DTA_DestWidthF, rw,
DTA_DestHeightF, rh,
DTA_Alpha, Alpha,
@ -1457,7 +1455,6 @@ public:
ax += width + spacing - (c->GetDisplayLeftOffsetDouble() + 1);
else //width gets changed at the call to GetChar()
ax += font->GetCharWidth((unsigned char) script->spacingCharacter) + spacing;
str++;
}
}

View file

@ -1025,7 +1025,7 @@ class CommandDrawNumber : public CommandDrawString
usePrefix(false), interpolationSpeed(0), drawValue(0), length(3),
lowValue(-1), lowTranslation(CR_UNTRANSLATED), highValue(-1),
highTranslation(CR_UNTRANSLATED), value(CONSTANT),
inventoryItem(NULL), cvarName(nullptr)
inventoryItem(NULL)
{
}

View file

@ -1481,15 +1481,15 @@ void DBaseStatusBar::DrawString(FFont *font, const FString &cstring, double x, d
break;
case DI_TEXT_ALIGN_RIGHT:
if (!monospaced)
x -= static_cast<int> (font->StringWidth(cstring) + (spacing * cstring.Len()));
x -= static_cast<int> (font->StringWidth(cstring) + (spacing * cstring.CharacterCount()));
else //monospaced, so just multiply the character size
x -= static_cast<int> ((spacing) * cstring.Len());
x -= static_cast<int> ((spacing) * cstring.CharacterCount());
break;
case DI_TEXT_ALIGN_CENTER:
if (!monospaced)
x -= static_cast<int> (font->StringWidth(cstring) + (spacing * cstring.Len())) / 2;
x -= static_cast<int> (font->StringWidth(cstring) + (spacing * cstring.CharacterCount())) / 2;
else //monospaced, so just multiply the character size
x -= static_cast<int> ((spacing)* cstring.Len()) / 2;
x -= static_cast<int> ((spacing)* cstring.CharacterCount()) / 2;
break;
}
@ -1527,7 +1527,7 @@ void DBaseStatusBar::DrawString(FFont *font, const FString &cstring, double x, d
Scale = { 1.,1. };
}
int ch;
while (ch = *str++, ch != '\0')
while (ch = GetCharFromString(str), ch != '\0')
{
if (ch == ' ')
{
@ -1543,7 +1543,7 @@ void DBaseStatusBar::DrawString(FFont *font, const FString &cstring, double x, d
}
int width;
FTexture* c = font->GetChar((unsigned char)ch, fontcolor, &width);
FTexture* c = font->GetChar(ch, fontcolor, &width);
if (c == NULL) //missing character.
{
continue;

View file

@ -285,7 +285,7 @@ static bool including, includenotext;
static const char *unknown_str = "Unknown key %s encountered in %s %d.\n";
static FStringTable *EnglishStrings;
static StringMap EnglishStrings, DehStrings;
// This is an offset to be used for computing the text stuff.
// Straight from the DeHackEd source which was
@ -2169,7 +2169,7 @@ static int PatchMusic (int dummy)
keystring << "MUSIC_" << Line1;
GStrings.SetString (keystring, newname);
DehStrings.Insert(keystring, newname);
DPrintf (DMSG_SPAMMY, "Music %s set to:\n%s\n", keystring.GetChars(), newname);
}
@ -2280,11 +2280,11 @@ static int PatchText (int oldSize)
const char *str;
do
{
str = EnglishStrings->MatchString(oldStr);
str = EnglishStrings.MatchString(oldStr);
if (str != NULL)
{
GStrings.SetString(str, newStr);
EnglishStrings->SetString(str, "~~"); // set to something invalid so that it won't get found again by the next iteration or by another replacement later
DehStrings.Insert(str, newStr);
EnglishStrings.Remove(str); // remove entry so that it won't get found again by the next iteration or by another replacement later
good = true;
}
}
@ -2337,7 +2337,7 @@ static int PatchStrings (int dummy)
// Account for a discrepancy between Boom's and ZDoom's name for the red skull key pickup message
const char *ll = Line1;
if (!stricmp(ll, "GOTREDSKULL")) ll = "GOTREDSKUL";
GStrings.SetString (ll, holdstring);
DehStrings.Insert(ll, holdstring);
DPrintf (DMSG_SPAMMY, "%s set to:\n%s\n", Line1, holdstring.GetChars());
}
@ -2670,11 +2670,6 @@ static void UnloadDehSupp ()
StyleNames.Reset();
AmmoNames.Reset();
UnchangedSpriteNames.Reset();
if (EnglishStrings != NULL)
{
delete EnglishStrings;
EnglishStrings = NULL;
}
}
}
@ -2704,12 +2699,8 @@ static bool LoadDehSupp ()
return true;
}
if (EnglishStrings == NULL)
{
EnglishStrings = new FStringTable;
EnglishStrings->LoadStrings (true);
}
if (EnglishStrings.CountUsed() == 0)
EnglishStrings = GStrings.GetDefaultStrings();
UnchangedSpriteNames.Resize(sprites.Size());
for (unsigned i = 0; i < UnchangedSpriteNames.Size(); ++i)
@ -3079,6 +3070,8 @@ void FinishDehPatch ()
StateMap.ShrinkToFit();
TouchedActors.Clear();
TouchedActors.ShrinkToFit();
EnglishStrings.Clear();
GStrings.SetDehackedStrings(std::move(DehStrings));
// Now it gets nasty: We have to fiddle around with the weapons' ammo use info to make Doom's original
// ammo consumption work as intended.

980
src/gamedata/fonts/font.cpp Normal file
View file

@ -0,0 +1,980 @@
/*
** v_font.cpp
** Font management
**
**---------------------------------------------------------------------------
** Copyright 1998-2016 Randy Heit
** Copyright 2005-2019 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "templates.h"
#include "doomtype.h"
#include "m_swap.h"
#include "v_font.h"
#include "v_video.h"
#include "w_wad.h"
#include "gi.h"
#include "cmdlib.h"
#include "sc_man.h"
#include "hu_stuff.h"
#include "gstrings.h"
#include "v_text.h"
#include "vm.h"
#include "image.h"
#include "utf8.h"
#include "textures/formats/fontchars.h"
#include "textures/formats/multipatchtexture.h"
#include "fontinternals.h"
//==========================================================================
//
// FFont :: FFont
//
// Loads a multi-texture font.
//
//==========================================================================
FFont::FFont (const char *name, const char *nametemplate, const char *filetemplate, int lfirst, int lcount, int start, int fdlump, int spacewidth, bool notranslate)
{
int i;
FTextureID lump;
char buffer[12];
int maxyoffs;
bool doomtemplate = (nametemplate && (gameinfo.gametype & GAME_DoomChex)) ? strncmp (nametemplate, "STCFN", 5) == 0 : false;
DVector2 Scale = { 1, 1 };
noTranslate = notranslate;
Lump = fdlump;
FontHeight = 0;
GlobalKerning = false;
FontName = name;
Next = FirstFont;
FirstFont = this;
Cursor = '_';
ActiveColors = 0;
SpaceWidth = 0;
FontHeight = 0;
uint8_t pp = 0;
for (auto &p : PatchRemap) p = pp++;
translateUntranslated = false;
int FixedWidth = 0;
maxyoffs = 0;
TMap<int, FTexture*> charMap;
int minchar = INT_MAX;
int maxchar = INT_MIN;
// Read the font's configuration.
// This will not be done for the default fonts, because they are not atomic and the default content does not need it.
TArray<FolderEntry> folderdata;
if (filetemplate != nullptr)
{
FStringf path("fonts/%s/", filetemplate);
// If a name template is given, collect data from all resource files.
// For anything else, each folder is being treated as an atomic, self-contained unit and mixing from different glyph sets is blocked.
Wads.GetLumpsInFolder(path, folderdata, nametemplate == nullptr);
if (nametemplate == nullptr)
{
// Only take font.inf from the actual folder we are processing but not from an older folder that may have been superseded.
FStringf infpath("fonts/%s/font.inf", filetemplate);
unsigned index = folderdata.FindEx([=](const FolderEntry &entry)
{
return infpath.CompareNoCase(entry.name) == 0;
});
if (index < folderdata.Size())
{
FScanner sc;
sc.OpenLumpNum(folderdata[index].lumpnum);
while (sc.GetToken())
{
sc.TokenMustBe(TK_Identifier);
if (sc.Compare("Kerning"))
{
sc.MustGetValue(false);
GlobalKerning = sc.Number;
}
else if (sc.Compare("Scale"))
{
sc.MustGetValue(true);
Scale.Y = Scale.X = sc.Float;
if (sc.CheckToken(','))
{
sc.MustGetValue(true);
Scale.Y = sc.Float;
}
}
else if (sc.Compare("SpaceWidth"))
{
sc.MustGetValue(false);
SpaceWidth = sc.Number;
}
else if (sc.Compare("FontHeight"))
{
sc.MustGetValue(false);
FontHeight = sc.Number;
}
else if (sc.Compare("CellSize"))
{
sc.MustGetValue(false);
FixedWidth = sc.Number;
sc.MustGetToken(',');
sc.MustGetValue(false);
FontHeight = sc.Number;
}
else if (sc.Compare("Translationtype"))
{
sc.MustGetToken(TK_Identifier);
if (sc.Compare("console"))
{
TranslationType = 1;
}
else if (sc.Compare("standard"))
{
TranslationType = 0;
}
else
{
sc.ScriptError("Unknown translation type %s", sc.String);
}
}
}
}
}
}
if (FixedWidth > 0)
{
ReadSheetFont(folderdata, FixedWidth, FontHeight, Scale);
}
else
{
if (nametemplate != nullptr)
{
for (i = 0; i < lcount; i++)
{
int position = '!' + i;
mysnprintf(buffer, countof(buffer), nametemplate, i + start);
lump = TexMan.CheckForTexture(buffer, ETextureType::MiscPatch);
if (doomtemplate && lump.isValid() && i + start == 121)
{ // HACKHACK: Don't load STCFN121 in doom(2), because
// it's not really a lower-case 'y' but a '|'.
// Because a lot of wads with their own font seem to foolishly
// copy STCFN121 and make it a '|' themselves, wads must
// provide STCFN120 (x) and STCFN122 (z) for STCFN121 to load as a 'y'.
if (!TexMan.CheckForTexture("STCFN120", ETextureType::MiscPatch).isValid() ||
!TexMan.CheckForTexture("STCFN122", ETextureType::MiscPatch).isValid())
{
// insert the incorrectly named '|' graphic in its correct position.
position = 124;
}
}
if (lump.isValid())
{
if (position < minchar) minchar = position;
if (position > maxchar) maxchar = position;
charMap.Insert(position, TexMan.GetTexture(lump));
}
}
}
if (folderdata.Size() > 0)
{
// all valid lumps must be named with a hex number that represents its Unicode character index.
for (auto &entry : folderdata)
{
char *endp;
auto base = ExtractFileBase(entry.name);
auto position = strtoll(base.GetChars(), &endp, 16);
if ((*endp == 0 || (*endp == '.' && position >= '!' && position < 0xffff)))
{
auto lump = TexMan.CheckForTexture(entry.name, ETextureType::MiscPatch);
if (lump.isValid())
{
if ((int)position < minchar) minchar = (int)position;
if ((int)position > maxchar) maxchar = (int)position;
auto tex = TexMan.GetTexture(lump);
tex->SetScale(Scale);
charMap.Insert((int)position, tex);
}
}
}
}
FirstChar = minchar;
LastChar = maxchar;
auto count = maxchar - minchar + 1;
Chars.Resize(count);
int fontheight = 0;
for (i = 0; i < count; i++)
{
auto lump = charMap.CheckKey(FirstChar + i);
if (lump != nullptr)
{
FTexture *pic = *lump;
if (pic != nullptr)
{
int height = pic->GetDisplayHeight();
int yoffs = pic->GetDisplayTopOffset();
if (yoffs > maxyoffs)
{
maxyoffs = yoffs;
}
height += abs(yoffs);
if (height > fontheight)
{
fontheight = height;
}
}
pic->SetUseType(ETextureType::FontChar);
if (!noTranslate)
{
Chars[i].OriginalPic = pic;
Chars[i].TranslatedPic = new FImageTexture(new FFontChar1(pic->GetImage()), "");
Chars[i].TranslatedPic->CopySize(pic);
Chars[i].TranslatedPic->SetUseType(ETextureType::FontChar);
TexMan.AddTexture(Chars[i].TranslatedPic);
}
else
{
Chars[i].TranslatedPic = pic;
}
Chars[i].XMove = Chars[i].TranslatedPic->GetDisplayWidth();
}
else
{
Chars[i].TranslatedPic = nullptr;
Chars[i].XMove = INT_MIN;
}
}
if (SpaceWidth == 0) // An explicit override from the .inf file must always take precedence
{
if (spacewidth != -1)
{
SpaceWidth = spacewidth;
}
else if ('N' - FirstChar >= 0 && 'N' - FirstChar < count && Chars['N' - FirstChar].TranslatedPic != nullptr)
{
SpaceWidth = (Chars['N' - FirstChar].XMove + 1) / 2;
}
else
{
SpaceWidth = 4;
}
}
if (FontHeight == 0) FontHeight = fontheight;
FixXMoves();
}
if (!noTranslate) LoadTranslations();
}
void FFont::ReadSheetFont(TArray<FolderEntry> &folderdata, int width, int height, const DVector2 &Scale)
{
// all valid lumps must be named with a hex number that represents the Unicode character index for its first character,
TArray<TexPart> part(1, true);
TMap<int, FTexture*> charMap;
int minchar = INT_MAX;
int maxchar = INT_MIN;
for (auto &entry : folderdata)
{
char *endp;
auto base = ExtractFileBase(entry.name);
auto position = strtoll(base.GetChars(), &endp, 16);
if ((*endp == 0 || (*endp == '.' && position >= 0 && position < 0xffff))) // Sheet fonts may fill in the low control chars.
{
auto lump = TexMan.CheckForTexture(entry.name, ETextureType::MiscPatch);
if (lump.isValid())
{
auto tex = TexMan.GetTexture(lump);
int numtex_x = tex->GetWidth() / width;
int numtex_y = tex->GetHeight() / height;
int maxinsheet = int(position) + numtex_x * numtex_y - 1;
if (minchar > position) minchar = int(position);
if (maxchar < maxinsheet) maxchar = maxinsheet;
for (int y = 0; y < numtex_y; y++)
{
for (int x = 0; x < numtex_x; x++)
{
part[0].OriginX = -width * x;
part[0].OriginY = -height * y;
part[0].Image = tex->GetImage();
FMultiPatchTexture *image = new FMultiPatchTexture(width, height, part, false, false);
FImageTexture *tex = new FImageTexture(image, "");
tex->SetUseType(ETextureType::FontChar);
tex->bMultiPatch = true;
tex->Width = width;
tex->Height = height;
tex->_LeftOffset[0] =
tex->_LeftOffset[1] =
tex->_TopOffset[0] =
tex->_TopOffset[1] = 0;
tex->Scale = Scale;
tex->bMasked = true;
tex->bTranslucent = -1;
tex->bWorldPanning = true;
tex->bNoDecals = false;
tex->SourceLump = -1; // We do not really care.
TexMan.AddTexture(tex);
charMap.Insert(int(position) + x + y * numtex_x, tex);
}
}
}
}
}
FirstChar = minchar;
bool map1252 = false;
if (minchar < 0x80 && maxchar >= 0xa0) // should be a settable option, but that'd probably cause more problems than it'd solve.
{
if (maxchar < 0x2122) maxchar = 0x2122;
map1252 = true;
}
LastChar = maxchar;
auto count = maxchar - minchar + 1;
Chars.Resize(count);
int fontheight = 0;
for (int i = 0; i < count; i++)
{
auto lump = charMap.CheckKey(FirstChar + i);
if (lump != nullptr)
{
FTexture *pic = *lump;
auto b = pic->Get8BitPixels(false);
Chars[i].OriginalPic = pic;
Chars[i].TranslatedPic = new FImageTexture(new FFontChar1(pic->GetImage()), "");
Chars[i].TranslatedPic->CopySize(pic);
Chars[i].TranslatedPic->SetUseType(ETextureType::FontChar);
TexMan.AddTexture(Chars[i].TranslatedPic);
}
Chars[i].XMove = width;
}
if (map1252)
{
// Move the Windows-1252 characters to their proper place.
for (int i = 0x80; i < 0xa0; i++)
{
if (win1252map[i - 0x80] != i && Chars[i - minchar].TranslatedPic != nullptr && Chars[win1252map[i - 0x80] - minchar].TranslatedPic == nullptr)
{
std::swap(Chars[i - minchar], Chars[win1252map[i - 0x80] - minchar]);
}
}
}
SpaceWidth = width;
}
//==========================================================================
//
// FFont :: ~FFont
//
//==========================================================================
FFont::~FFont ()
{
FFont **prev = &FirstFont;
FFont *font = *prev;
while (font != nullptr && font != this)
{
prev = &font->Next;
font = *prev;
}
if (font != nullptr)
{
*prev = font->Next;
}
}
//==========================================================================
//
// FFont :: FindFont
//
// Searches for the named font in the list of loaded fonts, returning the
// font if it was found. The disk is not checked if it cannot be found.
//
//==========================================================================
FFont *FFont::FindFont (FName name)
{
if (name == NAME_None)
{
return nullptr;
}
FFont *font = FirstFont;
while (font != nullptr)
{
if (font->FontName == name) return font;
font = font->Next;
}
return nullptr;
}
//==========================================================================
//
// RecordTextureColors
//
// Given a 256 entry buffer, sets every entry that corresponds to a color
// used by the texture to 1.
//
//==========================================================================
void RecordTextureColors (FImageSource *pic, uint32_t *usedcolors)
{
int x;
auto pixels = pic->GetPalettedPixels(false);
auto size = pic->GetWidth() * pic->GetHeight();
for(x = 0;x < size; x++)
{
usedcolors[pixels[x]]++;
}
}
//==========================================================================
//
// compare
//
// Used for sorting colors by brightness.
//
//==========================================================================
static int compare (const void *arg1, const void *arg2)
{
if (RPART(GPalette.BaseColors[*((uint8_t *)arg1)]) * 299 +
GPART(GPalette.BaseColors[*((uint8_t *)arg1)]) * 587 +
BPART(GPalette.BaseColors[*((uint8_t *)arg1)]) * 114 <
RPART(GPalette.BaseColors[*((uint8_t *)arg2)]) * 299 +
GPART(GPalette.BaseColors[*((uint8_t *)arg2)]) * 587 +
BPART(GPalette.BaseColors[*((uint8_t *)arg2)]) * 114)
return -1;
else
return 1;
}
//==========================================================================
//
// FFont :: SimpleTranslation
//
// Colorsused, translation, and reverse must all be 256 entry buffers.
// Colorsused must already be filled out.
// Translation be set to remap the source colors to a new range of
// consecutive colors based at 1 (0 is transparent).
// Reverse will be just the opposite of translation: It maps the new color
// range to the original colors.
// *Luminosity will be an array just large enough to hold the brightness
// levels of all the used colors, in consecutive order. It is sorted from
// darkest to lightest and scaled such that the darkest color is 0.0 and
// the brightest color is 1.0.
// The return value is the number of used colors and thus the number of
// entries in *luminosity.
//
//==========================================================================
int FFont::SimpleTranslation (uint32_t *colorsused, uint8_t *translation, uint8_t *reverse, TArray<double> &Luminosity)
{
double min, max, diver;
int i, j;
memset (translation, 0, 256);
reverse[0] = 0;
for (i = 1, j = 1; i < 256; i++)
{
if (colorsused[i])
{
reverse[j++] = i;
}
}
qsort (reverse+1, j-1, 1, compare);
Luminosity.Resize(j);
Luminosity[0] = 0.0; // [BL] Prevent uninitalized memory
max = 0.0;
min = 100000000.0;
for (i = 1; i < j; i++)
{
translation[reverse[i]] = i;
Luminosity[i] = RPART(GPalette.BaseColors[reverse[i]]) * 0.299 +
GPART(GPalette.BaseColors[reverse[i]]) * 0.587 +
BPART(GPalette.BaseColors[reverse[i]]) * 0.114;
if (Luminosity[i] > max)
max = Luminosity[i];
if (Luminosity[i] < min)
min = Luminosity[i];
}
diver = 1.0 / (max - min);
for (i = 1; i < j; i++)
{
Luminosity[i] = (Luminosity[i] - min) * diver;
}
return j;
}
//==========================================================================
//
// FFont :: BuildTranslations
//
// Build color translations for this font. Luminosity is an array of
// brightness levels. The ActiveColors member must be set to indicate how
// large this array is. Identity is an array that remaps the colors to
// their original values; it is only used for CR_UNTRANSLATED. Ranges
// is an array of TranslationParm structs defining the ranges for every
// possible color, in order. Palette is the colors to use for the
// untranslated version of the font.
//
//==========================================================================
void FFont::BuildTranslations (const double *luminosity, const uint8_t *identity,
const void *ranges, int total_colors, const PalEntry *palette)
{
int i, j;
const TranslationParm *parmstart = (const TranslationParm *)ranges;
FRemapTable remap(total_colors);
// Create different translations for different color ranges
Ranges.Clear();
for (i = 0; i < NumTextColors; i++)
{
if (i == CR_UNTRANSLATED)
{
if (identity != nullptr)
{
memcpy (remap.Remap, identity, ActiveColors);
if (palette != nullptr)
{
memcpy (remap.Palette, palette, ActiveColors*sizeof(PalEntry));
}
else
{
remap.Palette[0] = GPalette.BaseColors[identity[0]] & MAKEARGB(0,255,255,255);
for (j = 1; j < ActiveColors; ++j)
{
remap.Palette[j] = GPalette.BaseColors[identity[j]] | MAKEARGB(255,0,0,0);
}
}
}
else
{
remap = Ranges[0];
}
Ranges.Push(remap);
continue;
}
assert(parmstart->RangeStart >= 0);
remap.Remap[0] = 0;
remap.Palette[0] = 0;
for (j = 1; j < ActiveColors; j++)
{
int v = int(luminosity[j] * 256.0);
// Find the color range that this luminosity value lies within.
const TranslationParm *parms = parmstart - 1;
do
{
parms++;
if (parms->RangeStart <= v && parms->RangeEnd >= v)
break;
}
while (parms[1].RangeStart > parms[0].RangeEnd);
// Linearly interpolate to find out which color this luminosity level gets.
int rangev = ((v - parms->RangeStart) << 8) / (parms->RangeEnd - parms->RangeStart);
int r = ((parms->Start[0] << 8) + rangev * (parms->End[0] - parms->Start[0])) >> 8; // red
int g = ((parms->Start[1] << 8) + rangev * (parms->End[1] - parms->Start[1])) >> 8; // green
int b = ((parms->Start[2] << 8) + rangev * (parms->End[2] - parms->Start[2])) >> 8; // blue
r = clamp(r, 0, 255);
g = clamp(g, 0, 255);
b = clamp(b, 0, 255);
remap.Remap[j] = ColorMatcher.Pick(r, g, b);
remap.Palette[j] = PalEntry(255,r,g,b);
}
Ranges.Push(remap);
// Advance to the next color range.
while (parmstart[1].RangeStart > parmstart[0].RangeEnd)
{
parmstart++;
}
parmstart++;
}
}
//==========================================================================
//
// FFont :: GetColorTranslation
//
//==========================================================================
FRemapTable *FFont::GetColorTranslation (EColorRange range, PalEntry *color) const
{
if (noTranslate)
{
PalEntry retcolor = PalEntry(255, 255, 255, 255);
if (range >= 0 && range < NumTextColors && range != CR_UNTRANSLATED)
{
retcolor = TranslationColors[range];
retcolor.a = 255;
}
if (color != nullptr) *color = retcolor;
}
if (ActiveColors == 0)
return nullptr;
else if (range >= NumTextColors)
range = CR_UNTRANSLATED;
//if (range == CR_UNTRANSLATED && !translateUntranslated) return nullptr;
return &Ranges[range];
}
//==========================================================================
//
// FFont :: GetCharCode
//
// If the character code is in the font, returns it. If it is not, but it
// is lowercase and has an uppercase variant present, return that. Otherwise
// return -1.
//
//==========================================================================
int FFont::GetCharCode(int code, bool needpic) const
{
if (code < 0 && code >= -128)
{
// regular chars turn negative when the 8th bit is set.
code &= 255;
}
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].TranslatedPic != nullptr))
{
return code;
}
// Use different substitution logic based on the fonts content:
// In a font which has both upper and lower case, prefer unaccented small characters over capital ones.
// In a pure upper-case font, do not check for lower case replacements.
if (!MixedCase)
{
// Try converting lowercase characters to uppercase.
if (myislower(code))
{
code = upperforlower[code];
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].TranslatedPic != nullptr))
{
return code;
}
}
// Try stripping accents from accented characters.
int newcode = stripaccent(code);
if (newcode != code)
{
code = newcode;
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].TranslatedPic != nullptr))
{
return code;
}
}
}
else
{
int originalcode = code;
int newcode;
// Try stripping accents from accented characters. This may repeat to allow multi-step fallbacks.
while ((newcode = stripaccent(code)) != code)
{
code = newcode;
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].TranslatedPic != nullptr))
{
return code;
}
}
if (myislower(code))
{
int upper = upperforlower[code];
// Stripping accents did not help - now try uppercase for lowercase
if (upper != code) return GetCharCode(upper, needpic);
}
}
return -1;
}
//==========================================================================
//
// FFont :: GetChar
//
//==========================================================================
FTexture *FFont::GetChar (int code, int translation, int *const width, bool *redirected) const
{
code = GetCharCode(code, false);
int xmove = SpaceWidth;
if (code >= 0)
{
code -= FirstChar;
xmove = Chars[code].XMove;
if (Chars[code].TranslatedPic == nullptr)
{
code = GetCharCode(code + FirstChar, true);
if (code >= 0)
{
code -= FirstChar;
xmove = Chars[code].XMove;
}
}
}
if (width != nullptr)
{
*width = xmove;
}
if (code < 0) return nullptr;
if (translation == CR_UNTRANSLATED)
{
bool redirect = Chars[code].OriginalPic && Chars[code].OriginalPic != Chars[code].TranslatedPic;
if (redirected) *redirected = redirect;
if (redirect)
{
assert(Chars[code].OriginalPic->UseType == ETextureType::FontChar);
return Chars[code].OriginalPic;
}
}
if (redirected) *redirected = false;
assert(Chars[code].TranslatedPic->UseType == ETextureType::FontChar);
return Chars[code].TranslatedPic;
}
//==========================================================================
//
// FFont :: GetCharWidth
//
//==========================================================================
int FFont::GetCharWidth (int code) const
{
code = GetCharCode(code, false);
return (code < 0) ? SpaceWidth : Chars[code - FirstChar].XMove;
}
//==========================================================================
//
//
//
//==========================================================================
double GetBottomAlignOffset(FFont *font, int c)
{
int w;
FTexture *tex_zero = font->GetChar('0', CR_UNDEFINED, &w);
FTexture *texc = font->GetChar(c, CR_UNDEFINED, &w);
double offset = 0;
if (texc) offset += texc->GetDisplayTopOffsetDouble();
if (tex_zero) offset += -tex_zero->GetDisplayTopOffsetDouble() + tex_zero->GetDisplayHeightDouble();
return offset;
}
//==========================================================================
//
// Find string width using this font
//
//==========================================================================
int FFont::StringWidth(const uint8_t *string) const
{
int w = 0;
int maxw = 0;
while (*string)
{
auto chr = GetCharFromString(string);
if (chr == TEXTCOLOR_ESCAPE)
{
// We do not need to check for UTF-8 in here.
if (*string == '[')
{
while (*string != '\0' && *string != ']')
{
++string;
}
}
if (*string != '\0')
{
++string;
}
continue;
}
else if (chr == '\n')
{
if (w > maxw)
maxw = w;
w = 0;
}
else
{
w += GetCharWidth(chr) + GlobalKerning;
}
}
return MAX(maxw, w);
}
//==========================================================================
//
// FFont :: LoadTranslations
//
//==========================================================================
void FFont::LoadTranslations()
{
unsigned int count = LastChar - FirstChar + 1;
uint32_t usedcolors[256] = {};
uint8_t identity[256];
TArray<double> Luminosity;
for (unsigned int i = 0; i < count; i++)
{
if (Chars[i].TranslatedPic)
{
FFontChar1 *pic = static_cast<FFontChar1 *>(Chars[i].TranslatedPic->GetImage());
if (pic)
{
pic->SetSourceRemap(nullptr); // Force the FFontChar1 to return the same pixels as the base texture
RecordTextureColors(pic, usedcolors);
}
}
}
// Fixme: This needs to build a translation based on the source palette, not some intermediate 'ordered' table.
ActiveColors = SimpleTranslation (usedcolors, PatchRemap, identity, Luminosity);
for (unsigned int i = 0; i < count; i++)
{
if(Chars[i].TranslatedPic)
static_cast<FFontChar1 *>(Chars[i].TranslatedPic->GetImage())->SetSourceRemap(PatchRemap);
}
BuildTranslations (Luminosity.Data(), identity, &TranslationParms[TranslationType][0], ActiveColors, nullptr);
}
//==========================================================================
//
// FFont :: FFont - default constructor
//
//==========================================================================
FFont::FFont (int lump)
{
Lump = lump;
FontName = NAME_None;
Cursor = '_';
noTranslate = false;
uint8_t pp = 0;
for (auto &p : PatchRemap) p = pp++;
}
//==========================================================================
//
// FFont :: FixXMoves
//
// If a font has gaps in its characters, set the missing characters'
// XMoves to either SpaceWidth or the unaccented or uppercase variant's
// XMove. Missing XMoves must be initialized with INT_MIN beforehand.
//
//==========================================================================
void FFont::FixXMoves()
{
for (int i = 0; i <= LastChar - FirstChar; ++i)
{
if (Chars[i].XMove == INT_MIN)
{
// Try an uppercase character.
if (myislower(i + FirstChar))
{
int upper = upperforlower[FirstChar + i];
if (upper >= FirstChar && upper <= LastChar )
{
Chars[i].XMove = Chars[upper - FirstChar].XMove;
continue;
}
}
// Try an unnaccented character.
int noaccent = stripaccent(i + FirstChar);
if (noaccent != i + FirstChar)
{
noaccent -= FirstChar;
if (noaccent >= 0)
{
Chars[i].XMove = Chars[noaccent].XMove;
continue;
}
}
Chars[i].XMove = SpaceWidth;
}
}
}

View file

@ -0,0 +1,44 @@
#pragma once
#include <stdint.h>
#include "tarray.h"
// This structure is used by BuildTranslations() to hold color information.
struct TranslationParm
{
short RangeStart; // First level for this range
short RangeEnd; // Last level for this range
uint8_t Start[3]; // Start color for this range
uint8_t End[3]; // End color for this range
};
struct TempParmInfo
{
unsigned int StartParm[2];
unsigned int ParmLen[2];
int Index;
};
struct TempColorInfo
{
FName Name;
unsigned int ParmInfo;
PalEntry LogColor;
};
struct TranslationMap
{
FName Name;
int Number;
};
extern TArray<TranslationParm> TranslationParms[2];
extern TArray<TranslationMap> TranslationLookup;
extern TArray<PalEntry> TranslationColors;
extern uint16_t lowerforupper[65536];
extern uint16_t upperforlower[65536];
class FImageSource;
void RecordTextureColors (FImageSource *pic, uint32_t *usedcolors);
bool myislower(int code);
int stripaccent(int code);

View file

@ -0,0 +1,653 @@
/*
** singlelumpfont.cpp
** Management for compiled font lumps
**
**---------------------------------------------------------------------------
** Copyright 1998-2016 Randy Heit
** Copyright 2005-2019 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "doomerrors.h"
#include "textures.h"
#include "image.h"
#include "v_font.h"
#include "w_wad.h"
#include "utf8.h"
#include "textures/formats/fontchars.h"
#include "fontinternals.h"
/* Special file formats handled here:
FON1 "console" fonts have the following header:
char Magic[4]; -- The characters "FON1"
uword CharWidth; -- Character cell width
uword CharHeight; -- Character cell height
The FON1 header is followed by RLE character data for all 256
8-bit ASCII characters.
FON2 "standard" fonts have the following header:
char Magic[4]; -- The characters "FON2"
uword FontHeight; -- Every character in a font has the same height
ubyte FirstChar; -- First character defined by this font.
ubyte LastChar; -- Last character definde by this font.
ubyte bConstantWidth;
ubyte ShadingType;
ubyte PaletteSize; -- size of palette in entries (not bytes!)
ubyte Flags;
There is presently only one flag for FON2:
FOF_WHOLEFONTKERNING 1 -- The Kerning field is present in the file
The FON2 header is followed by variable length data:
word Kerning;
-- only present if FOF_WHOLEFONTKERNING is set
ubyte Palette[PaletteSize+1][3];
-- The last entry is the delimiter color. The delimiter is not used
-- by the font but is used by imagetool when converting the font
-- back to an image. Color 0 is the transparent color and is also
-- used only for converting the font back to an image. The other
-- entries are all presorted in increasing order of brightness.
ubyte CharacterData[...];
-- RLE character data, in order
*/
class FSingleLumpFont : public FFont
{
public:
FSingleLumpFont (const char *fontname, int lump);
protected:
void CheckFON1Chars (double *luminosity);
void BuildTranslations2 ();
void FixupPalette (uint8_t *identity, double *luminosity, const uint8_t *palette,
bool rescale, PalEntry *out_palette);
void LoadTranslations ();
void LoadFON1 (int lump, const uint8_t *data);
void LoadFON2 (int lump, const uint8_t *data);
void LoadBMF (int lump, const uint8_t *data);
void CreateFontFromPic (FTextureID picnum);
static int BMFCompare(const void *a, const void *b);
enum
{
FONT1,
FONT2,
BMFFONT
} FontType;
uint8_t PaletteData[768];
bool RescalePalette;
};
//==========================================================================
//
// FSingleLumpFont :: FSingleLumpFont
//
// Loads a FON1 or FON2 font resource.
//
//==========================================================================
FSingleLumpFont::FSingleLumpFont (const char *name, int lump) : FFont(lump)
{
assert(lump >= 0);
FontName = name;
FMemLump data1 = Wads.ReadLump (lump);
const uint8_t *data = (const uint8_t *)data1.GetMem();
if (data[0] == 0xE1 && data[1] == 0xE6 && data[2] == 0xD5 && data[3] == 0x1A)
{
LoadBMF(lump, data);
}
else if (data[0] != 'F' || data[1] != 'O' || data[2] != 'N' ||
(data[3] != '1' && data[3] != '2'))
{
I_Error ("%s is not a recognizable font", name);
}
else
{
switch (data[3])
{
case '1':
LoadFON1 (lump, data);
break;
case '2':
LoadFON2 (lump, data);
break;
}
}
Next = FirstFont;
FirstFont = this;
}
//==========================================================================
//
// FSingleLumpFont :: CreateFontFromPic
//
//==========================================================================
void FSingleLumpFont::CreateFontFromPic (FTextureID picnum)
{
FTexture *pic = TexMan.GetTexture(picnum);
FontHeight = pic->GetDisplayHeight ();
SpaceWidth = pic->GetDisplayWidth ();
GlobalKerning = 0;
FirstChar = LastChar = 'A';
Chars.Resize(1);
Chars[0].TranslatedPic = pic;
// Only one color range. Don't bother with the others.
ActiveColors = 0;
}
//==========================================================================
//
// FSingleLumpFont :: LoadTranslations
//
//==========================================================================
void FSingleLumpFont::LoadTranslations()
{
double luminosity[256];
uint8_t identity[256];
PalEntry local_palette[256];
bool useidentity = true;
bool usepalette = false;
const void* ranges;
unsigned int count = LastChar - FirstChar + 1;
switch(FontType)
{
case FONT1:
useidentity = false;
ranges = &TranslationParms[1][0];
CheckFON1Chars (luminosity);
break;
case BMFFONT:
case FONT2:
usepalette = true;
FixupPalette (identity, luminosity, PaletteData, RescalePalette, local_palette);
ranges = &TranslationParms[0][0];
break;
default:
// Should be unreachable.
I_Error("Unknown font type in FSingleLumpFont::LoadTranslation.");
return;
}
for(unsigned int i = 0;i < count;++i)
{
if(Chars[i].TranslatedPic)
static_cast<FFontChar2*>(Chars[i].TranslatedPic->GetImage())->SetSourceRemap(PatchRemap);
}
BuildTranslations (luminosity, useidentity ? identity : nullptr, ranges, ActiveColors, usepalette ? local_palette : nullptr);
}
//==========================================================================
//
// FSingleLumpFont :: LoadFON1
//
// FON1 is used for the console font.
//
//==========================================================================
void FSingleLumpFont::LoadFON1 (int lump, const uint8_t *data)
{
int w, h;
// The default console font is for Windows-1252 and fills the 0x80-0x9f range with valid glyphs.
// Since now all internal text is processed as Unicode, these have to be remapped to their proper places.
// The highest valid character in this range is 0x2122, so we need 0x2123 entries in our character table.
Chars.Resize(0x2123);
w = data[4] + data[5]*256;
h = data[6] + data[7]*256;
FontType = FONT1;
FontHeight = h;
SpaceWidth = w;
FirstChar = 0;
LastChar = 255; // This is to allow LoadTranslations to function. The way this is all set up really needs to be changed.
GlobalKerning = 0;
translateUntranslated = true;
LoadTranslations();
LastChar = 0x2122;
// Move the Windows-1252 characters to their proper place.
for (int i = 0x80; i < 0xa0; i++)
{
if (win1252map[i-0x80] != i && Chars[i].TranslatedPic != nullptr && Chars[win1252map[i - 0x80]].TranslatedPic == nullptr)
{
std::swap(Chars[i], Chars[win1252map[i - 0x80]]);
}
}
}
//==========================================================================
//
// FSingleLumpFont :: LoadFON2
//
// FON2 is used for everything but the console font. The console font should
// probably use FON2 as well, but oh well.
//
//==========================================================================
void FSingleLumpFont::LoadFON2 (int lump, const uint8_t *data)
{
int count, i, totalwidth;
uint16_t *widths;
const uint8_t *palette;
const uint8_t *data_p;
FontType = FONT2;
FontHeight = data[4] + data[5]*256;
FirstChar = data[6];
LastChar = data[7];
ActiveColors = data[10]+1;
RescalePalette = data[9] == 0;
count = LastChar - FirstChar + 1;
Chars.Resize(count);
TArray<int> widths2(count, true);
if (data[11] & 1)
{ // Font specifies a kerning value.
GlobalKerning = LittleShort(*(int16_t *)&data[12]);
widths = (uint16_t *)(data + 14);
}
else
{ // Font does not specify a kerning value.
GlobalKerning = 0;
widths = (uint16_t *)(data + 12);
}
totalwidth = 0;
if (data[8])
{ // Font is mono-spaced.
totalwidth = LittleShort(widths[0]);
for (i = 0; i < count; ++i)
{
widths2[i] = totalwidth;
}
totalwidth *= count;
palette = (uint8_t *)&widths[1];
}
else
{ // Font has varying character widths.
for (i = 0; i < count; ++i)
{
widths2[i] = LittleShort(widths[i]);
totalwidth += widths2[i];
}
palette = (uint8_t *)(widths + i);
}
if (FirstChar <= ' ' && LastChar >= ' ')
{
SpaceWidth = widths2[' '-FirstChar];
}
else if (FirstChar <= 'N' && LastChar >= 'N')
{
SpaceWidth = (widths2['N' - FirstChar] + 1) / 2;
}
else
{
SpaceWidth = totalwidth * 2 / (3 * count);
}
memcpy(PaletteData, palette, ActiveColors*3);
data_p = palette + ActiveColors*3;
for (i = 0; i < count; ++i)
{
int destSize = widths2[i] * FontHeight;
Chars[i].XMove = widths2[i];
if (destSize <= 0)
{
Chars[i].TranslatedPic = nullptr;
}
else
{
Chars[i].TranslatedPic = new FImageTexture(new FFontChar2 (lump, int(data_p - data), widths2[i], FontHeight));
Chars[i].TranslatedPic->SetUseType(ETextureType::FontChar);
TexMan.AddTexture(Chars[i].TranslatedPic);
do
{
int8_t code = *data_p++;
if (code >= 0)
{
data_p += code+1;
destSize -= code+1;
}
else if (code != -128)
{
data_p++;
destSize -= (-code)+1;
}
} while (destSize > 0);
}
if (destSize < 0)
{
i += FirstChar;
I_FatalError ("Overflow decompressing char %d (%c) of %s", i, i, FontName.GetChars());
}
}
LoadTranslations();
}
//==========================================================================
//
// FSingleLumpFont :: LoadBMF
//
// Loads a BMF font. The file format is described at
// <http://bmf.wz.cz/bmf-format.htm>
//
//==========================================================================
void FSingleLumpFont::LoadBMF(int lump, const uint8_t *data)
{
const uint8_t *chardata;
int numchars, count, totalwidth, nwidth;
int infolen;
int i, chari;
uint8_t raw_palette[256*3];
PalEntry sort_palette[256];
FontType = BMFFONT;
FontHeight = data[5];
GlobalKerning = (int8_t)data[8];
ActiveColors = data[16];
SpaceWidth = -1;
nwidth = -1;
RescalePalette = true;
infolen = data[17 + ActiveColors*3];
chardata = data + 18 + ActiveColors*3 + infolen;
numchars = chardata[0] + 256*chardata[1];
chardata += 2;
// Scan for lowest and highest characters defined and total font width.
FirstChar = 256;
LastChar = 0;
totalwidth = 0;
for (i = chari = 0; i < numchars; ++i, chari += 6 + chardata[chari+1] * chardata[chari+2])
{
if ((chardata[chari+1] == 0 || chardata[chari+2] == 0) && chardata[chari+5] == 0)
{ // Don't count empty characters.
continue;
}
if (chardata[chari] < FirstChar)
{
FirstChar = chardata[chari];
}
if (chardata[chari] > LastChar)
{
LastChar = chardata[chari];
}
totalwidth += chardata[chari+1];
}
if (LastChar < FirstChar)
{
I_FatalError("BMF font defines no characters");
}
count = LastChar - FirstChar + 1;
Chars.Resize(count);
// BMF palettes are only six bits per component. Fix that.
for (i = 0; i < ActiveColors*3; ++i)
{
raw_palette[i+3] = (data[17 + i] << 2) | (data[17 + i] >> 4);
}
ActiveColors++;
// Sort the palette by increasing brightness
for (i = 0; i < ActiveColors; ++i)
{
PalEntry *pal = &sort_palette[i];
pal->a = i; // Use alpha part to point back to original entry
pal->r = raw_palette[i*3 + 0];
pal->g = raw_palette[i*3 + 1];
pal->b = raw_palette[i*3 + 2];
}
qsort(sort_palette + 1, ActiveColors - 1, sizeof(PalEntry), BMFCompare);
// Create the PatchRemap table from the sorted "alpha" values.
PatchRemap[0] = 0;
for (i = 1; i < ActiveColors; ++i)
{
PatchRemap[sort_palette[i].a] = i;
}
memcpy(PaletteData, raw_palette, 768);
// Now scan through the characters again, creating glyphs for each one.
for (i = chari = 0; i < numchars; ++i, chari += 6 + chardata[chari+1] * chardata[chari+2])
{
assert(chardata[chari] - FirstChar >= 0);
assert(chardata[chari] - FirstChar < count);
if (chardata[chari] == ' ')
{
SpaceWidth = chardata[chari+5];
}
else if (chardata[chari] == 'N')
{
nwidth = chardata[chari+5];
}
Chars[chardata[chari] - FirstChar].XMove = chardata[chari+5];
if (chardata[chari+1] == 0 || chardata[chari+2] == 0)
{ // Empty character: skip it.
continue;
}
auto tex = new FImageTexture(new FFontChar2(lump, int(chardata + chari + 6 - data),
chardata[chari+1], // width
chardata[chari+2], // height
-(int8_t)chardata[chari+3], // x offset
-(int8_t)chardata[chari+4] // y offset
));
tex->SetUseType(ETextureType::FontChar);
Chars[chardata[chari] - FirstChar].TranslatedPic = tex;
TexMan.AddTexture(tex);
}
// If the font did not define a space character, determine a suitable space width now.
if (SpaceWidth < 0)
{
if (nwidth >= 0)
{
SpaceWidth = nwidth;
}
else
{
SpaceWidth = totalwidth * 2 / (3 * count);
}
}
FixXMoves();
LoadTranslations();
}
//==========================================================================
//
// FSingleLumpFont :: BMFCompare STATIC
//
// Helper to sort BMF palettes.
//
//==========================================================================
int FSingleLumpFont::BMFCompare(const void *a, const void *b)
{
const PalEntry *pa = (const PalEntry *)a;
const PalEntry *pb = (const PalEntry *)b;
return (pa->r * 299 + pa->g * 587 + pa->b * 114) -
(pb->r * 299 + pb->g * 587 + pb->b * 114);
}
//==========================================================================
//
// FSingleLumpFont :: CheckFON1Chars
//
// Scans a FON1 resource for all the color values it uses and sets up
// some tables like SimpleTranslation. Data points to the RLE data for
// the characters. Also sets up the character textures.
//
//==========================================================================
void FSingleLumpFont::CheckFON1Chars (double *luminosity)
{
FMemLump memLump = Wads.ReadLump(Lump);
const uint8_t* data = (const uint8_t*) memLump.GetMem();
uint8_t used[256], reverse[256];
const uint8_t *data_p;
int i, j;
memset (used, 0, 256);
data_p = data + 8;
for (i = 0; i < 256; ++i)
{
int destSize = SpaceWidth * FontHeight;
if(!Chars[i].TranslatedPic)
{
Chars[i].TranslatedPic = new FImageTexture(new FFontChar2 (Lump, int(data_p - data), SpaceWidth, FontHeight));
Chars[i].TranslatedPic->SetUseType(ETextureType::FontChar);
Chars[i].XMove = SpaceWidth;
TexMan.AddTexture(Chars[i].TranslatedPic);
}
// Advance to next char's data and count the used colors.
do
{
int8_t code = *data_p++;
if (code >= 0)
{
destSize -= code+1;
while (code-- >= 0)
{
used[*data_p++] = 1;
}
}
else if (code != -128)
{
used[*data_p++] = 1;
destSize -= 1 - code;
}
} while (destSize > 0);
}
memset (PatchRemap, 0, 256);
reverse[0] = 0;
for (i = 1, j = 1; i < 256; ++i)
{
if (used[i])
{
reverse[j++] = i;
}
}
for (i = 1; i < j; ++i)
{
PatchRemap[reverse[i]] = i;
luminosity[i] = (reverse[i] - 1) / 254.0;
}
ActiveColors = j;
}
//==========================================================================
//
// FSingleLumpFont :: FixupPalette
//
// Finds the best matches for the colors used by a FON2 font and sets up
// some tables like SimpleTranslation.
//
//==========================================================================
void FSingleLumpFont::FixupPalette (uint8_t *identity, double *luminosity, const uint8_t *palette, bool rescale, PalEntry *out_palette)
{
int i;
double maxlum = 0.0;
double minlum = 100000000.0;
double diver;
identity[0] = 0;
palette += 3; // Skip the transparent color
for (i = 1; i < ActiveColors; ++i, palette += 3)
{
int r = palette[0];
int g = palette[1];
int b = palette[2];
double lum = r*0.299 + g*0.587 + b*0.114;
identity[i] = ColorMatcher.Pick (r, g, b);
luminosity[i] = lum;
out_palette[i].r = r;
out_palette[i].g = g;
out_palette[i].b = b;
out_palette[i].a = 255;
if (lum > maxlum)
maxlum = lum;
if (lum < minlum)
minlum = lum;
}
out_palette[0] = 0;
if (rescale)
{
diver = 1.0 / (maxlum - minlum);
}
else
{
diver = 1.0 / 255.0;
}
for (i = 1; i < ActiveColors; ++i)
{
luminosity[i] = (luminosity[i] - minlum) * diver;
}
}
FFont *CreateSingleLumpFont (const char *fontname, int lump)
{
return new FSingleLumpFont(fontname, lump);
}

View file

@ -0,0 +1,127 @@
/*
** v_font.cpp
** Font management
**
**---------------------------------------------------------------------------
** Copyright 1998-2016 Randy Heit
** Copyright 2005-2019 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "doomerrors.h"
#include "textures.h"
#include "v_font.h"
#include "w_wad.h"
class FSinglePicFont : public FFont
{
public:
FSinglePicFont(const char *picname);
// FFont interface
FTexture *GetChar(int code, int translation, int *const width, bool *redirected = nullptr) const override;
int GetCharWidth (int code) const;
protected:
FTextureID PicNum;
};
//==========================================================================
//
// FSinglePicFont :: FSinglePicFont
//
// Creates a font to wrap a texture so that you can use hudmessage as if it
// were a hudpic command. It does not support translation, but animation
// is supported, unlike all the real fonts.
//
//==========================================================================
FSinglePicFont::FSinglePicFont(const char *picname) :
FFont(-1) // Since lump is only needed for priority information we don't need to worry about this here.
{
FTextureID picnum = TexMan.CheckForTexture (picname, ETextureType::Any);
if (!picnum.isValid())
{
I_FatalError ("%s is not a font or texture", picname);
}
FTexture *pic = TexMan.GetTexture(picnum);
FontName = picname;
FontHeight = pic->GetDisplayHeight();
SpaceWidth = pic->GetDisplayWidth();
GlobalKerning = 0;
FirstChar = LastChar = 'A';
ActiveColors = 0;
PicNum = picnum;
Next = FirstFont;
FirstFont = this;
}
//==========================================================================
//
// FSinglePicFont :: GetChar
//
// Returns the texture if code is 'a' or 'A', otherwise nullptr.
//
//==========================================================================
FTexture *FSinglePicFont::GetChar (int code, int translation, int *const width, bool *redirected) const
{
*width = SpaceWidth;
if (redirected) *redirected = false;
if (code == 'a' || code == 'A')
{
return TexMan.GetPalettedTexture(PicNum, true);
}
else
{
return nullptr;
}
}
//==========================================================================
//
// FSinglePicFont :: GetCharWidth
//
// Don't expect the text functions to work properly if I actually allowed
// the character width to vary depending on the animation frame.
//
//==========================================================================
int FSinglePicFont::GetCharWidth (int code) const
{
return SpaceWidth;
}
FFont *CreateSinglePicFont(const char *picname)
{
return new FSinglePicFont(picname);
}

View file

@ -0,0 +1,221 @@
/*
** v_font.cpp
** Font management
**
**---------------------------------------------------------------------------
** Copyright 1998-2016 Randy Heit
** Copyright 2005-2019 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "v_font.h"
#include "textures.h"
#include "image.h"
#include "textures/formats/fontchars.h"
#include "fontinternals.h"
// Essentially a normal multilump font but with an explicit list of character patches
class FSpecialFont : public FFont
{
public:
FSpecialFont (const char *name, int first, int count, FTexture **lumplist, const bool *notranslate, int lump, bool donttranslate);
void LoadTranslations();
protected:
bool notranslate[256];
};
//==========================================================================
//
// FSpecialFont :: FSpecialFont
//
//==========================================================================
FSpecialFont::FSpecialFont (const char *name, int first, int count, FTexture **lumplist, const bool *notranslate, int lump, bool donttranslate)
: FFont(lump)
{
int i;
TArray<FTexture *> charlumps(count, true);
int maxyoffs;
FTexture *pic;
memcpy(this->notranslate, notranslate, 256*sizeof(bool));
noTranslate = donttranslate;
FontName = name;
Chars.Resize(count);
FirstChar = first;
LastChar = first + count - 1;
FontHeight = 0;
GlobalKerning = false;
Next = FirstFont;
FirstFont = this;
maxyoffs = 0;
for (i = 0; i < count; i++)
{
pic = charlumps[i] = lumplist[i];
if (pic != nullptr)
{
int height = pic->GetDisplayHeight();
int yoffs = pic->GetDisplayTopOffset();
if (yoffs > maxyoffs)
{
maxyoffs = yoffs;
}
height += abs (yoffs);
if (height > FontHeight)
{
FontHeight = height;
}
}
if (charlumps[i] != nullptr)
{
charlumps[i]->SetUseType(ETextureType::FontChar);
Chars[i].OriginalPic = charlumps[i];
if (!noTranslate)
{
Chars[i].TranslatedPic = new FImageTexture(new FFontChar1 (charlumps[i]->GetImage()), "");
Chars[i].TranslatedPic->CopySize(charlumps[i]);
Chars[i].TranslatedPic->SetUseType(ETextureType::FontChar);
TexMan.AddTexture(Chars[i].TranslatedPic);
}
else Chars[i].TranslatedPic = charlumps[i];
Chars[i].XMove = Chars[i].TranslatedPic->GetDisplayWidth();
}
else
{
Chars[i].TranslatedPic = nullptr;
Chars[i].XMove = INT_MIN;
}
}
// Special fonts normally don't have all characters so be careful here!
if ('N'-first >= 0 && 'N'-first < count && Chars['N' - first].TranslatedPic != nullptr)
{
SpaceWidth = (Chars['N' - first].XMove + 1) / 2;
}
else
{
SpaceWidth = 4;
}
FixXMoves();
if (noTranslate)
{
ActiveColors = 0;
}
else
{
LoadTranslations();
}
}
//==========================================================================
//
// FSpecialFont :: LoadTranslations
//
//==========================================================================
void FSpecialFont::LoadTranslations()
{
int count = LastChar - FirstChar + 1;
uint32_t usedcolors[256] = {};
uint8_t identity[256];
TArray<double> Luminosity;
int TotalColors;
int i, j;
for (i = 0; i < count; i++)
{
if (Chars[i].TranslatedPic)
{
FFontChar1 *pic = static_cast<FFontChar1 *>(Chars[i].TranslatedPic->GetImage());
if (pic)
{
pic->SetSourceRemap(nullptr); // Force the FFontChar1 to return the same pixels as the base texture
RecordTextureColors(pic, usedcolors);
}
}
}
// exclude the non-translated colors from the translation calculation
for (i = 0; i < 256; i++)
if (notranslate[i])
usedcolors[i] = false;
TotalColors = ActiveColors = SimpleTranslation (usedcolors, PatchRemap, identity, Luminosity);
// Map all untranslated colors into the table of used colors
for (i = 0; i < 256; i++)
{
if (notranslate[i])
{
PatchRemap[i] = TotalColors;
identity[TotalColors] = i;
TotalColors++;
}
}
for (i = 0; i < count; i++)
{
if(Chars[i].TranslatedPic)
static_cast<FFontChar1 *>(Chars[i].TranslatedPic->GetImage())->SetSourceRemap(PatchRemap);
}
BuildTranslations (Luminosity.Data(), identity, &TranslationParms[0][0], TotalColors, nullptr);
// add the untranslated colors to the Ranges tables
if (ActiveColors < TotalColors)
{
for (i = 0; i < NumTextColors; i++)
{
FRemapTable *remap = &Ranges[i];
for (j = ActiveColors; j < TotalColors; ++j)
{
remap->Remap[j] = identity[j];
remap->Palette[j] = GPalette.BaseColors[identity[j]];
remap->Palette[j].a = 0xff;
}
}
}
ActiveColors = TotalColors;
}
FFont *CreateSpecialFont (const char *name, int first, int count, FTexture **lumplist, const bool *notranslate, int lump, bool donttranslate)
{
return new FSpecialFont(name, first, count, lumplist, notranslate, lump, donttranslate);
}

File diff suppressed because it is too large Load diff

View file

@ -35,6 +35,8 @@
#define __V_FONT_H__
#include "doomtype.h"
#include "w_wad.h"
#include "vectors.h"
class DCanvas;
struct FRemapTable;
@ -79,7 +81,7 @@ extern int NumTextColors;
class FFont
{
public:
FFont (const char *fontname, const char *nametemplate, int first, int count, int base, int fdlump, int spacewidth=-1, bool notranslate = false);
FFont (const char *fontname, const char *nametemplate, const char *filetemplate, int first, int count, int base, int fdlump, int spacewidth=-1, bool notranslate = false);
virtual ~FFont ();
virtual FTexture *GetChar (int code, int translation, int *const width, bool *redirected = nullptr) const;
@ -102,6 +104,7 @@ public:
int GetCharCode(int code, bool needpic) const;
char GetCursor() const { return Cursor; }
void SetCursor(char c) { Cursor = c; }
void SetKerning(int c) { GlobalKerning = c; }
bool NoTranslate() const { return noTranslate; }
protected:
@ -111,16 +114,20 @@ protected:
const void *ranges, int total_colors, const PalEntry *palette);
void FixXMoves();
static int SimpleTranslation (uint8_t *colorsused, uint8_t *translation,
static int SimpleTranslation (uint32_t *colorsused, uint8_t *translation,
uint8_t *identity, TArray<double> &Luminosity);
void ReadSheetFont(TArray<FolderEntry> &folderdata, int width, int height, const DVector2 &Scale);
int FirstChar, LastChar;
int SpaceWidth;
int FontHeight;
int GlobalKerning;
int TranslationType = 0;
char Cursor;
bool noTranslate;
bool translateUntranslated;
bool MixedCase = false;
struct CharData
{
FTexture *TranslatedPic = nullptr; // Texture for use with font translations.
@ -150,7 +157,7 @@ void V_ClearFonts();
EColorRange V_FindFontColor (FName name);
PalEntry V_LogColorFromColorRange (EColorRange range);
EColorRange V_ParseFontColor (const uint8_t *&color_value, int normalcolor, int boldcolor);
FFont *V_GetFont(const char *);
FFont *V_GetFont(const char *fontname, const char *fontlumpname = nullptr);
void V_InitFontColors();
#endif //__V_FONT_H__

View file

@ -0,0 +1,237 @@
/*
** v_text.cpp
** Draws text to a canvas. Also has a text line-breaker thingy.
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <wctype.h>
#include "v_text.h"
#include "v_video.h"
#include "w_wad.h"
#include "gstrings.h"
#include "vm.h"
#include "serializer.h"
//==========================================================================
//
// Break long lines of text into multiple lines no longer than maxwidth pixels
//
//==========================================================================
static void breakit (FBrokenLines *line, FFont *font, const uint8_t *start, const uint8_t *stop, FString &linecolor)
{
if (!linecolor.IsEmpty())
{
line->Text = TEXTCOLOR_ESCAPE;
line->Text += linecolor;
}
line->Text.AppendCStrPart ((const char *)start, stop - start);
line->Width = font->StringWidth (line->Text);
}
TArray<FBrokenLines> V_BreakLines (FFont *font, int maxwidth, const uint8_t *string, bool preservecolor)
{
TArray<FBrokenLines> Lines(128);
const uint8_t *space = NULL, *start = string;
int c, w, nw;
FString lastcolor, linecolor;
bool lastWasSpace = false;
int kerning = font->GetDefaultKerning ();
w = 0;
while ( (c = GetCharFromString(string)) )
{
if (c == TEXTCOLOR_ESCAPE)
{
if (*string)
{
if (*string == '[')
{
const uint8_t *start = string;
while (*string != ']' && *string != '\0')
{
string++;
}
if (*string != '\0')
{
string++;
}
lastcolor = FString((const char *)start, string - start);
}
else
{
lastcolor = *string++;
}
}
continue;
}
if (iswspace(c))
{
if (!lastWasSpace)
{
space = string - 1;
lastWasSpace = true;
}
}
else
{
lastWasSpace = false;
}
nw = font->GetCharWidth (c);
if ((w > 0 && w + nw > maxwidth) || c == '\n')
{ // Time to break the line
if (!space)
space = string - 1;
auto index = Lines.Reserve(1);
breakit (&Lines[index], font, start, space, linecolor);
if (c == '\n' && !preservecolor)
{
lastcolor = ""; // Why, oh why, did I do it like this?
}
linecolor = lastcolor;
w = 0;
lastWasSpace = false;
start = space;
space = NULL;
while (*start && iswspace (*start) && *start != '\n')
start++;
if (*start == '\n')
start++;
else
while (*start && iswspace (*start))
start++;
string = start;
}
else
{
w += nw + kerning;
}
}
// String here is pointing one character after the '\0'
if (--string - start >= 1)
{
const uint8_t *s = start;
while (s < string)
{
// If there is any non-white space in the remainder of the string, add it.
if (!iswspace (*s++))
{
auto i = Lines.Reserve(1);
breakit (&Lines[i], font, start, string, linecolor);
break;
}
}
}
return Lines;
}
FSerializer &Serialize(FSerializer &arc, const char *key, FBrokenLines& g, FBrokenLines *def)
{
if (arc.BeginObject(key))
{
arc("text", g.Text)
("width", g.Width)
.EndObject();
}
return arc;
}
class DBrokenLines : public DObject
{
DECLARE_CLASS(DBrokenLines, DObject)
public:
TArray<FBrokenLines> mBroken;
DBrokenLines() = default;
DBrokenLines(TArray<FBrokenLines> &broken)
{
mBroken = std::move(broken);
}
void Serialize(FSerializer &arc) override
{
arc("lines", mBroken);
}
};
IMPLEMENT_CLASS(DBrokenLines, false, false);
DEFINE_ACTION_FUNCTION(DBrokenLines, Count)
{
PARAM_SELF_PROLOGUE(DBrokenLines);
ACTION_RETURN_INT(self->mBroken.Size());
}
DEFINE_ACTION_FUNCTION(DBrokenLines, StringWidth)
{
PARAM_SELF_PROLOGUE(DBrokenLines);
PARAM_INT(index);
ACTION_RETURN_INT((unsigned)index >= self->mBroken.Size()? -1 : self->mBroken[index].Width);
}
DEFINE_ACTION_FUNCTION(DBrokenLines, StringAt)
{
PARAM_SELF_PROLOGUE(DBrokenLines);
PARAM_INT(index);
ACTION_RETURN_STRING((unsigned)index >= self->mBroken.Size() ? -1 : self->mBroken[index].Text);
}
DEFINE_ACTION_FUNCTION(FFont, BreakLines)
{
PARAM_SELF_STRUCT_PROLOGUE(FFont);
PARAM_STRING(text);
PARAM_INT(maxwidth);
auto broken = V_BreakLines(self, maxwidth, text, true);
ACTION_RETURN_OBJECT(Create<DBrokenLines>(broken));
}

View file

@ -51,7 +51,7 @@
#include "events.h"
#include "i_system.h"
TArray<cluster_info_t> wadclusterinfos;
static TArray<cluster_info_t> wadclusterinfos;
TArray<level_info_t> wadlevelinfos;
level_info_t TheDefaultLevelInfo;
@ -299,14 +299,14 @@ void level_info_t::Reset()
//
//==========================================================================
FString level_info_t::LookupLevelName()
FString level_info_t::LookupLevelName(uint32_t *langtable)
{
// All IWAD names that may be substituted by a graphics patch are declared as language strings.
if (langtable) *langtable = 0;
if (flags & LEVEL_LOOKUPLEVELNAME)
{
const char *thename;
const char *lookedup;
lookedup = GStrings[LevelName];
const char *lookedup = GStrings.GetString(LevelName, langtable);
if (lookedup == NULL)
{
thename = LevelName;
@ -827,6 +827,28 @@ void FMapInfoParser::ParseCluster()
break;
}
}
// Remap Hexen's CLUS?MSG lumps to the string table, if applicable. The code here only checks what can actually be in an IWAD.
if (clusterinfo->flags & CLUSTER_EXITTEXTINLUMP)
{
int lump = Wads.CheckNumForFullName(clusterinfo->ExitText, true);
if (lump > 0)
{
// Check if this comes from either Hexen.wad or Hexdd.wad and if so, map to the string table.
int fileno = Wads.GetLumpFile(lump);
auto fn = Wads.GetWadName(fileno);
if (fn && (!stricmp(fn, "HEXEN.WAD") || !stricmp(fn, "HEXDD.WAD")))
{
FStringf key("TXT_%.5s_%s", fn, clusterinfo->ExitText.GetChars());
if (GStrings.exists(key))
{
clusterinfo->ExitText = key;
clusterinfo->flags &= ~CLUSTER_EXITTEXTINLUMP;
clusterinfo->flags |= CLUSTER_LOOKUPEXITTEXT;
}
}
}
}
CheckEndOfFile("cluster");
}
@ -1901,8 +1923,28 @@ level_info_t *FMapInfoParser::ParseMapHeader(level_info_t &defaultinfo)
{
sc.MustGetString ();
levelinfo->flags |= LEVEL_LOOKUPLEVELNAME;
levelinfo->LevelName = sc.String;
}
else
{
levelinfo->LevelName = sc.String;
if (HexenHack)
{
// Try to localize Hexen's map names.
int fileno = Wads.GetLumpFile(sc.LumpNum);
auto fn = Wads.GetWadName(fileno);
if (fn && (!stricmp(fn, "HEXEN.WAD") || !stricmp(fn, "HEXDD.WAD")))
{
FStringf key("TXT_%.5s_%s", fn, levelinfo->MapName.GetChars());
if (GStrings.exists(key))
{
levelinfo->flags |= LEVEL_LOOKUPLEVELNAME;
levelinfo->LevelName = key;
}
}
}
}
levelinfo->LevelName = sc.String;
}
// Set up levelnum now so that you can use Teleport_NewMap specials

View file

@ -400,7 +400,7 @@ struct level_info_t
}
void Reset();
bool isValid();
FString LookupLevelName ();
FString LookupLevelName (uint32_t *langtable = nullptr);
void ClearDefered()
{
deferred.Clear();
@ -427,15 +427,18 @@ struct cluster_info_t
};
// Cluster flags
#define CLUSTER_HUB 0x00000001 // Cluster uses hub behavior
#define CLUSTER_EXITTEXTINLUMP 0x00000002 // Exit text is the name of a lump
#define CLUSTER_ENTERTEXTINLUMP 0x00000004 // Enter text is the name of a lump
#define CLUSTER_FINALEPIC 0x00000008 // Finale "flat" is actually a full-sized image
#define CLUSTER_LOOKUPEXITTEXT 0x00000010 // Exit text is the name of a language string
#define CLUSTER_LOOKUPENTERTEXT 0x00000020 // Enter text is the name of a language string
#define CLUSTER_LOOKUPNAME 0x00000040 // Name is the name of a language string
#define CLUSTER_LOOKUPCLUSTERNAME 0x00000080 // Cluster name is the name of a language string
#define CLUSTER_ALLOWINTERMISSION 0x00000100 // Allow intermissions between levels in a hub.
enum
{
CLUSTER_HUB = 0x00000001, // Cluster uses hub behavior
CLUSTER_EXITTEXTINLUMP = 0x00000002, // Exit text is the name of a lump
CLUSTER_ENTERTEXTINLUMP = 0x00000004, // Enter text is the name of a lump
CLUSTER_FINALEPIC = 0x00000008, // Finale "flat" is actually a full-sized image
CLUSTER_LOOKUPEXITTEXT = 0x00000010, // Exit text is the name of a language string
CLUSTER_LOOKUPENTERTEXT = 0x00000020, // Enter text is the name of a language string
CLUSTER_LOOKUPNAME = 0x00000040, // Name is the name of a language string
CLUSTER_LOOKUPCLUSTERNAME = 0x00000080, // Cluster name is the name of a language string
CLUSTER_ALLOWINTERMISSION = 0x00000100 // Allow intermissions between levels in a hub.
};
extern TArray<level_info_t> wadlevelinfos;

View file

@ -396,6 +396,7 @@ void FMapInfoParser::ParseGameInfo()
GAMEINFOKEY_BOOL(swapmenu, "swapmenu")
GAMEINFOKEY_BOOL(dontcrunchcorpses, "dontcrunchcorpses")
GAMEINFOKEY_BOOL(correctprintbold, "correctprintbold")
GAMEINFOKEY_BOOL(forcetextinmenus, "forcetextinmenus")
GAMEINFOKEY_BOOL(intermissioncounter, "intermissioncounter")
GAMEINFOKEY_BOOL(nightmarefast, "nightmarefast")
GAMEINFOKEY_COLOR(dimcolor, "dimcolor")
@ -423,8 +424,6 @@ void FMapInfoParser::ParseGameInfo()
GAMEINFOKEY_FONT(mStatscreenMapNameFont, "statscreen_mapnamefont")
GAMEINFOKEY_FONT(mStatscreenFinishedFont, "statscreen_finishedfont")
GAMEINFOKEY_FONT(mStatscreenEnteringFont, "statscreen_enteringfont")
GAMEINFOKEY_PATCH(mStatscreenFinishedFont, "statscreen_finishedpatch")
GAMEINFOKEY_PATCH(mStatscreenEnteringFont, "statscreen_enteringpatch")
GAMEINFOKEY_BOOL(norandomplayerclass, "norandomplayerclass")
GAMEINFOKEY_BOOL(forcekillscripts, "forcekillscripts") // [JM] Force kill scripts on thing death. (MF7_NOKILLSCRIPTS overrides.)
GAMEINFOKEY_STRING(Dialogue, "dialogue")

View file

@ -38,15 +38,19 @@
#include "zstring.h"
// Flags are not user configurable and only depend on the standard IWADs
#define GI_MAPxx 0x00000001
#define GI_SHAREWARE 0x00000002
#define GI_MENUHACK_EXTENDED 0x00000004 // (Heretic)
#define GI_TEASER2 0x00000008 // Alternate version of the Strife Teaser
#define GI_COMPATSHORTTEX 0x00000010 // always force COMPAT_SHORTTEX for IWAD maps.
#define GI_COMPATSTAIRS 0x00000020 // same for stairbuilding
#define GI_COMPATPOLY1 0x00000040 // Hexen's MAP36 needs old polyobject drawing
#define GI_COMPATPOLY2 0x00000080 // so does HEXDD's MAP47
#define GI_NOTEXTCOLOR 0x00000100 // Chex Quest 3 would have everything green
enum
{
GI_MAPxx = 0x00000001,
GI_SHAREWARE = 0x00000002,
GI_MENUHACK_EXTENDED = 0x00000004, // (Heretic)
GI_TEASER2 = 0x00000008, // Alternate version of the Strife Teaser
GI_COMPATSHORTTEX = 0x00000010, // always force COMPAT_SHORTTEX for IWAD maps.
GI_COMPATSTAIRS = 0x00000020, // same for stairbuilding
GI_COMPATPOLY1 = 0x00000040, // Hexen's MAP36 needs old polyobject drawing
GI_COMPATPOLY2 = 0x00000080, // so does HEXDD's MAP47
GI_NOTEXTCOLOR = 0x00000100, // Chex Quest 3 would have everything green
GI_IGNORETITLEPATCHES = 0x00000200, // Ignore the map name graphics when not runnning in English language
};
#include "gametype.h"
@ -115,6 +119,7 @@ struct gameinfo_t
bool swapmenu;
bool dontcrunchcorpses;
bool correctprintbold;
bool forcetextinmenus;
TArray<FName> creditPages;
TArray<FName> finalePages;
TArray<FName> infoPages;

View file

@ -79,7 +79,7 @@ bool FLumpFile::Open(bool quiet)
Lumps[0].LumpSize = (int)Reader.GetLength();
Lumps[0].Namespace = ns_global;
Lumps[0].Flags = 0;
Lumps[0].FullName = NULL;
Lumps[0].FullName = "";
NumLumps = 1;
if (!quiet)
{

View file

@ -187,7 +187,7 @@ bool FWadFile::Open(bool quiet)
Lumps[i].LumpSize = isBigEndian ? BigLong(fileinfo[i].Size) : LittleLong(fileinfo[i].Size);
Lumps[i].Namespace = ns_global;
Lumps[i].Flags = Lumps[i].Compressed? LUMPF_COMPRESSED : 0;
Lumps[i].FullName = NULL;
Lumps[i].FullName = "";
// Check if the lump is within the WAD file and print a warning if not.
if (Lumps[i].Position + Lumps[i].LumpSize > wadSize || Lumps[i].Position < 0 || Lumps[i].LumpSize < 0)

View file

@ -490,7 +490,7 @@ void FResourceFile::JunkLeftoverFilters(void *lumps, size_t lumpsize, uint32_t m
for (void *p = (uint8_t *)lumps + start * lumpsize; p < stop; p = (uint8_t *)p + lumpsize)
{
FResourceLump *lump = (FResourceLump *)p;
lump->FullName = 0;
lump->FullName = "";
lump->Name[0] = '\0';
lump->Namespace = ns_hidden;
}
@ -720,7 +720,7 @@ bool FMemoryFile::Open(bool quiet)
Lumps[0].LumpSize = (int)Reader.GetLength();
Lumps[0].Namespace = ns_global;
Lumps[0].Flags = 0;
Lumps[0].FullName = nullptr;
Lumps[0].FullName = "";
NumLumps = 1;
return true;
}

View file

@ -43,119 +43,32 @@
#include "v_text.h"
#include "gi.h"
// PassNum identifies which language pass this string is from.
// PassNum 0 is for DeHacked.
// PassNum 1 is for * strings.
// PassNum 2+ are for specific locales.
struct FStringTable::StringEntry
{
StringEntry *Next;
char *Name;
uint8_t PassNum;
char String[];
};
FStringTable::FStringTable ()
{
for (int i = 0; i < HASH_SIZE; ++i)
{
Buckets[i] = NULL;
}
}
FStringTable::~FStringTable ()
{
FreeData ();
}
void FStringTable::FreeData ()
{
for (int i = 0; i < HASH_SIZE; ++i)
{
StringEntry *entry = Buckets[i], *next;
Buckets[i] = NULL;
while (entry != NULL)
{
next = entry->Next;
M_Free (entry);
entry = next;
}
}
}
void FStringTable::FreeNonDehackedStrings ()
{
for (int i = 0; i < HASH_SIZE; ++i)
{
StringEntry *entry, *next, **pentry;
for (pentry = &Buckets[i], entry = *pentry; entry != NULL; )
{
next = entry->Next;
if (entry->PassNum != 0)
{
*pentry = next;
M_Free (entry);
}
else
{
pentry = &entry->Next;
}
entry = next;
}
}
}
void FStringTable::LoadStrings (bool enuOnly)
void FStringTable::LoadStrings ()
{
int lastlump, lump;
int i, j;
FreeNonDehackedStrings ();
lastlump = 0;
while ((lump = Wads.FindLump ("LANGUAGE", &lastlump)) != -1)
{
j = 0;
if (!enuOnly)
{
LoadLanguage (lump, MAKE_ID('*',0,0,0), true, ++j);
for (i = 0; i < 4; ++i)
{
LoadLanguage (lump, LanguageIDs[i], true, ++j);
LoadLanguage (lump, LanguageIDs[i] & MAKE_ID(0xff,0xff,0,0), true, ++j);
LoadLanguage (lump, LanguageIDs[i], false, ++j);
}
}
// Fill in any missing strings with the default language
LoadLanguage (lump, MAKE_ID('*','*',0,0), true, ++j);
LoadLanguage (lump);
}
SetLanguageIDs();
UpdateLanguage();
}
void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, int passnum)
void FStringTable::LoadLanguage (int lumpnum)
{
static bool errordone = false;
const uint32_t orMask = exactMatch ? 0 : MAKE_ID(0,0,0xff,0);
uint32_t inCode = 0;
StringEntry *entry, **pentry;
uint32_t bucket;
int cmpval;
bool skip = true;
code |= orMask;
bool errordone = false;
TArray<uint32_t> activeMaps;
FScanner sc(lumpnum);
sc.SetCMode (true);
while (sc.GetString ())
{
if (sc.Compare ("["))
{ // Process language identifiers
bool donot = false;
bool forceskip = false;
skip = true;
activeMaps.Clear();
sc.MustGetString ();
do
{
@ -164,17 +77,18 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in
{
if (len == 1 && sc.String[0] == '~')
{
donot = true;
// deprecated and ignored
sc.ScriptMessage("Deprecated option '~' found in language list");
sc.MustGetString ();
continue;
}
if (len == 1 && sc.String[0] == '*')
{
inCode = MAKE_ID('*',0,0,0);
activeMaps.Push(global_table);
}
else if (len == 7 && stricmp (sc.String, "default") == 0)
{
inCode = MAKE_ID('*','*',0,0);
activeMaps.Push(default_table);
}
else
{
@ -184,31 +98,14 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in
}
else
{
inCode = MAKE_ID(tolower(sc.String[0]), tolower(sc.String[1]), tolower(sc.String[2]), 0);
}
if ((inCode | orMask) == code)
{
if (donot)
{
forceskip = true;
donot = false;
}
else
{
skip = false;
}
activeMaps.Push(MAKE_ID(tolower(sc.String[0]), tolower(sc.String[1]), tolower(sc.String[2]), 0));
}
sc.MustGetString ();
} while (!sc.Compare ("]"));
if (donot)
{
sc.ScriptError ("You must specify a language after ~");
}
skip |= forceskip;
}
else
{ // Process string definitions.
if (inCode == 0)
if (activeMaps.Size() == 0)
{
// LANGUAGE lump is bad. We need to check if this is an old binary
// lump and if so just skip it to allow old WADs to run which contain
@ -222,32 +119,26 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in
sc.ScriptError ("Found a string without a language specified.");
}
bool savedskip = skip;
bool skip = false;
if (sc.Compare("$"))
{
sc.MustGetStringName("ifgame");
sc.MustGetStringName("(");
sc.MustGetString();
skip |= !sc.Compare(GameTypeName());
if (sc.Compare("strifeteaser"))
{
skip |= (gameinfo.gametype != GAME_Strife) || !(gameinfo.flags & GI_SHAREWARE);
}
else
{
skip |= !sc.Compare(GameTypeName());
}
sc.MustGetStringName(")");
sc.MustGetString();
}
if (skip)
{ // We're not interested in this language, so skip the string.
sc.MustGetStringName ("=");
sc.MustGetString ();
do
{
sc.MustGetString ();
}
while (!sc.Compare (";"));
skip = savedskip;
continue;
}
FString strName (sc.String);
FName strName (sc.String);
sc.MustGetStringName ("=");
sc.MustGetString ();
FString strText (sc.String, ProcessEscapes (sc.String));
@ -258,39 +149,39 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in
strText += sc.String;
sc.MustGetString ();
}
// Does this string exist? If so, should we overwrite it?
bucket = MakeKey (strName.GetChars()) & (HASH_SIZE-1);
pentry = &Buckets[bucket];
entry = *pentry;
cmpval = 1;
while (entry != NULL)
if (!skip)
{
cmpval = stricmp (entry->Name, strName.GetChars());
if (cmpval >= 0)
break;
pentry = &entry->Next;
entry = *pentry;
}
if (cmpval == 0 && entry->PassNum >= passnum)
{
*pentry = entry->Next;
M_Free (entry);
entry = NULL;
}
if (entry == NULL || cmpval > 0)
{
entry = (StringEntry *)M_Malloc (sizeof(*entry) + strText.Len() + strName.Len() + 2);
entry->Next = *pentry;
*pentry = entry;
strcpy (entry->String, strText.GetChars());
strcpy (entry->Name = entry->String + strText.Len() + 1, strName.GetChars());
entry->PassNum = passnum;
// Insert the string into all relevant tables.
for (auto map : activeMaps)
{
allStrings[map].Insert(strName, strText);
}
}
}
}
}
void FStringTable::UpdateLanguage()
{
currentLanguageSet.Clear();
auto checkone = [&](uint32_t lang_id)
{
auto list = allStrings.CheckKey(lang_id);
if (list && currentLanguageSet.FindEx([&](const auto &element) { return element.first == lang_id; }) == currentLanguageSet.Size())
currentLanguageSet.Push(std::make_pair(lang_id, list));
};
checkone(dehacked_table);
checkone(global_table);
for (int i = 0; i < 4; ++i)
{
checkone(LanguageIDs[i]);
checkone(LanguageIDs[i] & MAKE_ID(0xff, 0xff, 0, 0));
}
checkone(default_table);
}
// Replace \ escape sequences in a string with the escaped characters.
size_t FStringTable::ProcessEscapes (char *iptr)
{
@ -318,30 +209,53 @@ size_t FStringTable::ProcessEscapes (char *iptr)
return optr - sptr;
}
// Finds a string by name and returns its value
const char *FStringTable::operator[] (const char *name) const
bool FStringTable::exists(const char *name)
{
if (name == NULL)
// Checks if the given key exists in any one of the default string tables that are valid for all languages.
// To replace IWAD content this condition must be true.
if (name == nullptr || *name == 0)
{
return NULL;
return false;
}
uint32_t bucket = MakeKey (name) & (HASH_SIZE - 1);
StringEntry *entry = Buckets[bucket];
FName nm(name, true);
if (nm != NAME_None)
{
uint32_t defaultStrings[] = { default_table, global_table, dehacked_table };
while (entry != NULL)
{
int cmpval = stricmp (entry->Name, name);
if (cmpval == 0)
for (auto mapid : defaultStrings)
{
return entry->String;
auto map = allStrings.CheckKey(mapid);
if (map)
{
auto item = map->CheckKey(nm);
if (item) return true;
}
}
if (cmpval == 1)
{
return NULL;
}
entry = entry->Next;
}
return NULL;
return false;
}
// Finds a string by name and returns its value
const char *FStringTable::GetString(const char *name, uint32_t *langtable) const
{
if (name == nullptr || *name == 0)
{
return nullptr;
}
FName nm(name, true);
if (nm != NAME_None)
{
for (auto map : currentLanguageSet)
{
auto item = map.second->CheckKey(nm);
if (item)
{
if (langtable) *langtable = map.first;
return item->GetChars();
}
}
}
return nullptr;
}
// Finds a string by name and returns its value. If the string does
@ -352,75 +266,19 @@ const char *FStringTable::operator() (const char *name) const
return str ? str : name;
}
// Find a string by name. pentry1 is a pointer to a pointer to it, and entry1 is a
// pointer to it. Return NULL for entry1 if it wasn't found.
void FStringTable::FindString (const char *name, StringEntry **&pentry1, StringEntry *&entry1)
{
uint32_t bucket = MakeKey (name) & (HASH_SIZE - 1);
StringEntry **pentry = &Buckets[bucket], *entry = *pentry;
while (entry != NULL)
{
int cmpval = stricmp (entry->Name, name);
if (cmpval == 0)
{
pentry1 = pentry;
entry1 = entry;
return;
}
if (cmpval == 1)
{
pentry1 = pentry;
entry1 = NULL;
return;
}
pentry = &entry->Next;
entry = *pentry;
}
pentry1 = pentry;
entry1 = entry;
}
// Find a string with the same exact text. Returns its name.
const char *FStringTable::MatchString (const char *string) const
const char *StringMap::MatchString (const char *string) const
{
for (int i = 0; i < HASH_SIZE; ++i)
StringMap::ConstIterator it(*this);
StringMap::ConstPair *pair;
while (it.NextPair(pair))
{
for (StringEntry *entry = Buckets[i]; entry != NULL; entry = entry->Next)
if (pair->Value.Compare(string) == 0)
{
if (strcmp (entry->String, string) == 0)
{
return entry->Name;
}
return pair->Key.GetChars();
}
}
return NULL;
}
void FStringTable::SetString (const char *name, const char *newString)
{
StringEntry **pentry, *oentry;
FindString (name, pentry, oentry);
size_t newlen = strlen (newString);
size_t namelen = strlen (name);
// Create a new string entry
StringEntry *entry = (StringEntry *)M_Malloc (sizeof(*entry) + newlen + namelen + 2);
strcpy (entry->String, newString);
strcpy (entry->Name = entry->String + newlen + 1, name);
entry->PassNum = 0;
// If this is a new string, insert it. Otherwise, replace the old one.
if (oentry == NULL)
{
entry->Next = *pentry;
*pentry = entry;
}
else
{
*pentry = entry;
entry->Next = oentry->Next;
M_Free (oentry);
}
return nullptr;
}

View file

@ -44,34 +44,53 @@
#include <stdlib.h>
#include "doomdef.h"
#include "doomtype.h"
// This public interface is for Dehacked
class StringMap : public TMap<FName, FString>
{
public:
const char *MatchString(const char *string) const;
};
class FStringTable
{
public:
struct StringEntry;
enum : uint32_t
{
default_table = MAKE_ID('*', '*', 0, 0),
global_table = MAKE_ID('*', 0, 0, 0),
dehacked_table = MAKE_ID('*', '*', '*', 0)
};
FStringTable ();
~FStringTable ();
void LoadStrings (bool enuOnly);
using LangMap = TMap<uint32_t, StringMap>;
void LoadStrings ();
void UpdateLanguage();
StringMap GetDefaultStrings() { return allStrings[default_table]; } // Dehacked needs these for comparison
void SetDehackedStrings(StringMap && map)
{
allStrings.Insert(dehacked_table, map);
UpdateLanguage();
}
const char *GetString(const char *name, uint32_t *langtable) const;
const char *operator() (const char *name) const; // Never returns NULL
const char *operator[] (const char *name) const; // Can return NULL
const char *MatchString (const char *string) const;
void SetString (const char *name, const char *newString);
const char *operator[] (const char *name) const
{
return GetString(name, nullptr);
}
bool exists(const char *name);
private:
enum { HASH_SIZE = 128 };
StringEntry *Buckets[HASH_SIZE];
LangMap allStrings;
TArray<std::pair<uint32_t, StringMap*>> currentLanguageSet;
void FreeData ();
void FreeNonDehackedStrings ();
void LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, int passnum);
void LoadLanguage (int lumpnum);
static size_t ProcessEscapes (char *str);
void FindString (const char *stringName, StringEntry **&pentry, StringEntry *&entry);
};
#endif //__STRINGTABLE_H__

View file

@ -113,6 +113,12 @@ public:
{
return std::make_pair(LeftOffset, TopOffset);
}
void SetOffsets(int x, int y)
{
LeftOffset = x;
TopOffset = y;
}
int LumpNum() const
{

View file

@ -38,6 +38,8 @@
#include "doomstat.h"
#include "w_wad.h"
#include "templates.h"
#include "i_system.h"
#include "gstrings.h"
#include "r_data/r_translate.h"
#include "r_data/sprites.h"
@ -389,7 +391,72 @@ FTextureID FTextureManager::GetTextureID (const char *name, ETextureType usetype
FTexture *FTextureManager::FindTexture(const char *texname, ETextureType usetype, BITFIELD flags)
{
FTextureID texnum = CheckForTexture (texname, usetype, flags);
return !texnum.isValid()? NULL : Textures[texnum.GetIndex()].Texture;
return GetTexture(texnum.GetIndex());
}
//==========================================================================
//
// Defines how graphics substitution is handled.
// 0: Never replace a text-containing graphic with a font-based text.
// 1: Always replace, regardless of any missing information. Useful for testing the substitution without providing full data.
// 2: Only replace for non-default texts, i.e. if some language redefines the string's content, use it instead of the graphic. Never replace a localized graphic.
// 3: Only replace if the string is not the default and the graphic comes from the IWAD. Never replace a localized graphic.
// 4: Like 1, but lets localized graphics pass.
//
//==========================================================================
CUSTOM_CVAR(Int, cl_localizationmode,0, CVAR_ARCHIVE)
{
if (self < 0 || self > 4) self = 0;
}
//==========================================================================
//
// FTextureManager :: OkForLocalization
//
//==========================================================================
bool FTextureManager::OkForLocalization(FTextureID texnum, const char *substitute)
{
if (!texnum.isValid()) return false;
// First the unconditional settings, 0='never' and 1='always'.
if (cl_localizationmode == 1 || gameinfo.forcetextinmenus) return false;
if (cl_localizationmode == 0) return true;
uint32_t langtable = 0;
if (*substitute == '$') substitute = GStrings.GetString(substitute+1, &langtable);
if (substitute == nullptr) return true; // The text does not exist.
// Modes 2, 3 and 4 must not replace localized textures.
int localizedTex = ResolveLocalizedTexture(texnum.GetIndex());
if (localizedTex != texnum.GetIndex()) return true; // Do not substitute a localized variant of the graphics patch.
// For mode 4 we are done now.
if (cl_localizationmode == 4) return false;
// Mode 2 and 3 must reject any text replacement from the default language tables.
if ((langtable & MAKE_ID(255,0,0,0)) == MAKE_ID('*', 0, 0, 0)) return true; // Do not substitute if the string comes from the default table.
if (cl_localizationmode == 2) return false;
// Mode 3 must also reject substitutions for non-IWAD content.
int file = Wads.GetLumpFile(Textures[texnum.GetIndex()].Texture->SourceLump);
if (file > Wads.GetIwadNum()) return true;
return false;
}
static int OkForLocalization(int index, const FString &substitute)
{
return TexMan.OkForLocalization(FSetTextureID(index), substitute);
}
DEFINE_ACTION_FUNCTION_NATIVE(_TexMan, OkForLocalization, OkForLocalization)
{
PARAM_PROLOGUE;
PARAM_INT(name);
PARAM_STRING(subst)
ACTION_RETURN_INT(OkForLocalization(name, subst));
}
//==========================================================================
@ -1005,12 +1072,88 @@ void FTextureManager::SortTexturesByType(int start, int end)
}
}
//==========================================================================
//
// FTextureManager :: AddLocalizedVariants
//
//==========================================================================
void FTextureManager::AddLocalizedVariants()
{
TArray<FolderEntry> content;
Wads.GetLumpsInFolder("localized/textures/", content, false);
for (auto &entry : content)
{
FString name = entry.name;
auto tokens = name.Split(".", FString::TOK_SKIPEMPTY);
if (tokens.Size() == 2)
{
auto ext = tokens[1];
// Do not interpret common extensions for images as language IDs.
if (ext.CompareNoCase("png") == 0 || ext.CompareNoCase("jpg") == 0 || ext.CompareNoCase("gfx") == 0 || ext.CompareNoCase("tga") == 0 || ext.CompareNoCase("lmp") == 0)
{
Printf("%s contains no language IDs and will be ignored\n", entry.name);
continue;
}
}
if (tokens.Size() >= 2)
{
FString base = ExtractFileBase(tokens[0]);
FTextureID origTex = CheckForTexture(base, ETextureType::MiscPatch);
if (origTex.isValid())
{
FTextureID tex = CheckForTexture(entry.name, ETextureType::MiscPatch);
if (tex.isValid())
{
FTexture *otex = GetTexture(origTex);
FTexture *ntex = GetTexture(tex);
if (otex->GetDisplayWidth() != ntex->GetDisplayWidth() || otex->GetDisplayHeight() != ntex->GetDisplayHeight())
{
Printf("Localized texture %s must be the same size as the one it replaces\n", entry.name);
}
else
{
tokens[1].ToLower();
auto langids = tokens[1].Split("-", FString::TOK_SKIPEMPTY);
for (auto &lang : langids)
{
if (lang.Len() == 2 || lang.Len() == 3)
{
uint32_t langid = MAKE_ID(lang[0], lang[1], lang[2], 0);
uint64_t comboid = (uint64_t(langid) << 32) | origTex.GetIndex();
LocalizedTextures.Insert(comboid, tex.GetIndex());
Textures[origTex.GetIndex()].HasLocalization = true;
}
else
{
Printf("Invalid language ID in texture %s\n", entry.name);
}
}
}
}
else
{
Printf("%s is not a texture\n", entry.name);
}
}
else
{
Printf("Unknown texture %s for localized variant %s\n", tokens[0].GetChars(), entry.name);
}
}
else
{
Printf("%s contains no language IDs and will be ignored\n", entry.name);
}
}
}
//==========================================================================
//
// FTextureManager :: Init
//
//==========================================================================
FTexture *GetBackdropTexture();
FTexture *CreateShaderTexture(bool, bool);
void FTextureManager::Init()
@ -1096,7 +1239,7 @@ void FTextureManager::Init()
glPart2 = TexMan.CheckForTexture("glstuff/glpart2.png", ETextureType::MiscPatch);
glPart = TexMan.CheckForTexture("glstuff/glpart.png", ETextureType::MiscPatch);
mirrorTexture = TexMan.CheckForTexture("glstuff/mirror.png", ETextureType::MiscPatch);
AddLocalizedVariants();
}
//==========================================================================
@ -1143,12 +1286,31 @@ void FTextureManager::InitPalettedVersions()
//
//==========================================================================
FTextureID FTextureManager::PalCheck(FTextureID tex)
int FTextureManager::PalCheck(int tex)
{
// In any true color mode this shouldn't do anything.
if (vid_nopalsubstitutions || V_IsTrueColor()) return tex;
auto ftex = GetTexture(tex);
if (ftex != nullptr && ftex->PalVersion != nullptr) return ftex->PalVersion->id;
auto ftex = Textures[tex].Texture;
if (ftex != nullptr && ftex->PalVersion != nullptr) return ftex->PalVersion->id.GetIndex();
return tex;
}
//==========================================================================
//
// FTextureManager :: PalCheck
//
//==========================================================================
int FTextureManager::ResolveLocalizedTexture(int tex)
{
for(int i = 0; i < 4; i++)
{
uint32_t lang = LanguageIDs[i];
uint64_t index = (uint64_t(lang) << 32) + tex;
if (auto pTex = LocalizedTextures.CheckKey(index)) return *pTex;
index = (uint64_t(lang & MAKE_ID(255, 255, 0, 0)) << 32) + tex;
if (auto pTex = LocalizedTextures.CheckKey(index)) return *pTex;
}
return tex;
}

View file

@ -300,7 +300,6 @@ class FTexture
friend class FBrightmapTexture;
friend class FFont;
friend class FSpecialFont;
friend void RecordTextureColors (FTexture *pic, uint8_t *usedcolors);
public:
@ -482,6 +481,10 @@ protected:
}
void SetScaledSize(int fitwidth, int fitheight);
void SetScale(const DVector2 &scale)
{
Scale = scale;
}
protected:
uint16_t Width, Height;
@ -521,47 +524,49 @@ class FTextureManager
public:
FTextureManager ();
~FTextureManager ();
private:
int ResolveLocalizedTexture(int texnum);
int PalCheck(int tex);
FTexture *InternalGetTexture(int texnum, bool animate, bool localize, bool palettesubst)
{
if ((unsigned)texnum >= Textures.Size()) return nullptr;
if (animate) texnum = Translation[texnum];
if (localize && Textures[texnum].HasLocalization) texnum = ResolveLocalizedTexture(texnum);
if (palettesubst) texnum = PalCheck(texnum);
return Textures[texnum].Texture;
}
public:
// This only gets used in UI code so we do not need PALVERS handling.
FTexture *GetTextureByName(const char *name, bool animate = false)
{
FTextureID texnum = GetTextureID (name, ETextureType::MiscPatch);
if (!texnum.Exists()) return nullptr;
if (!animate) return Textures[texnum.GetIndex()].Texture;
else return Textures[Translation[texnum.GetIndex()]].Texture;
return InternalGetTexture(texnum.GetIndex(), animate, true, false);
}
FTexture *GetTexture(FTextureID texnum, bool animate = false)
{
if ((size_t)texnum.GetIndex() >= Textures.Size()) return nullptr;
if (animate) texnum = Translation[texnum.GetIndex()];
return Textures[texnum.GetIndex()].Texture;
return InternalGetTexture(texnum.GetIndex(), animate, true, false);
}
// This is the only access function that should be used inside the software renderer.
FTexture *GetPalettedTexture(FTextureID texnum, bool animate)
{
if ((size_t)texnum.texnum >= Textures.Size()) return nullptr;
if (animate) texnum = Translation[texnum.GetIndex()];
texnum = PalCheck(texnum).GetIndex();
return Textures[texnum.GetIndex()].Texture;
return InternalGetTexture(texnum.GetIndex(), animate, true, true);
}
FTexture *ByIndex(int i, bool animate = false)
{
if (unsigned(i) >= Textures.Size()) return NULL;
if (animate) i = Translation[i];
return Textures[i].Texture;
return InternalGetTexture(i, animate, true, false);
}
FTexture *FindTexture(const char *texname, ETextureType usetype = ETextureType::MiscPatch, BITFIELD flags = TEXMAN_TryAny);
bool OkForLocalization(FTextureID texnum, const char *substitute);
void FlushAll();
//public:
FTextureID PalCheck(FTextureID tex);
enum
{
TEXMAN_TryAny = 1,
@ -569,7 +574,8 @@ public:
TEXMAN_ReturnFirst = 4,
TEXMAN_AllowSkins = 8,
TEXMAN_ShortNameOnly = 16,
TEXMAN_DontCreate = 32
TEXMAN_DontCreate = 32,
TEXMAN_Localize = 64
};
enum
@ -593,6 +599,7 @@ public:
void ParseTextureDef(int remapLump, FMultipatchTextureBuilder &build);
void SortTexturesByType(int start, int end);
bool AreTexturesCompatible (FTextureID picnum1, FTextureID picnum2);
void AddLocalizedVariants();
FTextureID CreateTexture (int lumpnum, ETextureType usetype=ETextureType::Any); // Also calls AddTexture
FTextureID AddTexture (FTexture *texture);
@ -658,9 +665,11 @@ private:
{
FTexture *Texture;
int HashNext;
bool HasLocalization;
};
enum { HASH_END = -1, HASH_SIZE = 1027 };
TArray<TextureHash> Textures;
TMap<uint64_t, int> LocalizedTextures;
TArray<int> Translation;
int HashFirst[HASH_SIZE];
FTextureID DefaultTexture;

View file

@ -759,6 +759,7 @@ void FWadCollection::RenameSprites ()
{
bool renameAll;
bool MNTRZfound = false;
const char *altbigfont = gameinfo.gametype == GAME_Strife? "SBIGFONT" : (gameinfo.gametype & GAME_Raven)? "HBIGFONT" : "DBIGFONT";
static const uint32_t HereticRenames[] =
{ MAKE_ID('H','E','A','D'), MAKE_ID('L','I','C','H'), // Ironlich
@ -889,6 +890,11 @@ void FWadCollection::RenameSprites ()
}
}
}
else if (LumpInfo[i].lump->Namespace == ns_global)
{
// Rename the game specific big font lumps so that the font manager does not have to do problematic special checks for them.
if (!strcmp(LumpInfo[i].lump->Name, altbigfont)) strcpy(LumpInfo[i].lump->Name, "BIGFONT");
}
}
}
@ -1260,6 +1266,63 @@ FResourceLump *FWadCollection::GetLumpRecord(int lump) const
return LumpInfo[lump].lump;
}
//==========================================================================
//
// GetLumpsInFolder
//
// Gets all lumps within a single folder in the hierarchy.
// If 'atomic' is set, it treats folders as atomic, i.e. only the
// content of the last found resource file having the given folder name gets used.
//
//==========================================================================
static int folderentrycmp(const void *a, const void *b)
{
auto A = (FolderEntry*)a;
auto B = (FolderEntry*)b;
return strcmp(A->name, B->name);
}
unsigned FWadCollection::GetLumpsInFolder(const char *inpath, TArray<FolderEntry> &result, bool atomic) const
{
FString path = inpath;
FixPathSeperator(path);
path.ToLower();
if (path[path.Len() - 1] != '/') path += '/';
result.Clear();
for (unsigned i = 0; i < LumpInfo.Size(); i++)
{
if (LumpInfo[i].lump->FullName.IndexOf(path) == 0)
{
// Only if it hasn't been replaced.
if ((unsigned)Wads.CheckNumForFullName(LumpInfo[i].lump->FullName) == i)
{
result.Push({ LumpInfo[i].lump->FullName.GetChars(), i });
}
}
}
if (result.Size())
{
int maxfile = -1;
if (atomic)
{
// Find the highest resource file having content in the given folder.
for (auto & entry : result)
{
int thisfile = Wads.GetLumpFile(entry.lumpnum);
if (thisfile > maxfile) maxfile = thisfile;
}
// Delete everything from older files.
for (int i = result.Size() - 1; i >= 0; i--)
{
if (Wads.GetLumpFile(result[i].lumpnum) != maxfile) result.Delete(i);
}
}
qsort(result.Data(), result.Size(), sizeof(FolderEntry), folderentrycmp);
}
return result.Size();
}
//==========================================================================
//
// W_ReadLump

View file

@ -101,6 +101,12 @@ private:
friend class FWadCollection;
};
struct FolderEntry
{
const char *name;
unsigned lumpnum;
};
class FWadCollection
{
public:
@ -173,6 +179,7 @@ public:
int GetLumpIndexNum (int lump) const; // Returns the RFF index number for this lump
FResourceLump *GetLumpRecord(int lump) const; // Returns the FResourceLump, in case the caller wants to have direct access to the lump cache.
bool CheckLumpName (int lump, const char *name) const; // [RH] Returns true if the names match
unsigned GetLumpsInFolder(const char *path, TArray<FolderEntry> &result, bool atomic) const;
bool IsEncryptedFile(int lump) const;

View file

@ -49,6 +49,7 @@
#include "menu/menu.h"
#include "d_net.h"
#include "g_levellocals.h"
#include "utf8.h"
FIntermissionDescriptorList IntermissionDescriptors;
@ -255,7 +256,7 @@ void DIntermissionScreenText::Init(FIntermissionAction *desc, bool first)
if (mTextX < 0) mTextX =gameinfo.TextScreenX;
mTextY = static_cast<FIntermissionActionTextscreen*>(desc)->mTextY;
if (mTextY < 0) mTextY =gameinfo.TextScreenY;
mTextLen = (int)strlen(mText);
mTextLen = mText.CharacterCount();
mTextDelay = static_cast<FIntermissionActionTextscreen*>(desc)->mTextDelay;
mTextColor = static_cast<FIntermissionActionTextscreen*>(desc)->mTextColor;
// For text screens, the duration only counts when the text is complete.
@ -285,7 +286,7 @@ void DIntermissionScreenText::Drawer ()
size_t count;
int c;
const FRemapTable *range;
const char *ch = mText;
const uint8_t *ch = (const uint8_t*)mText.GetChars();
const int kerning = SmallFont->GetDefaultKerning();
// Count number of rows in this text. Since it does not word-wrap, we just count
@ -327,7 +328,7 @@ void DIntermissionScreenText::Drawer ()
for ( ; count > 0 ; count-- )
{
c = *ch++;
c = GetCharFromString(ch);
if (!c)
break;
if (c == '\n')
@ -696,6 +697,7 @@ bool DIntermissionController::NextPage ()
// last page
return false;
}
bg.SetInvalid();
if (mScreen != NULL)
{

View file

@ -207,7 +207,7 @@ class DIntermissionScreenText : public DIntermissionScreen
{
DECLARE_CLASS (DIntermissionScreenText, DIntermissionScreen)
const char *mText;
FString mText;
int mTextSpeed;
int mTextX, mTextY;
int mTextCounter;

View file

@ -37,6 +37,7 @@
#include "intermission/intermission.h"
#include "g_level.h"
#include "w_wad.h"
#include "gstrings.h"
static void ReplaceIntermission(FName intname,FIntermissionDescriptor *desc)
@ -291,9 +292,23 @@ bool FIntermissionActionTextscreen::ParseKey(FScanner &sc)
sc.MustGetToken('=');
sc.MustGetToken(TK_StringConst);
int lump = Wads.CheckNumForFullName(sc.String, true);
bool done = false;
if (lump > 0)
{
mText = Wads.ReadLump(lump).GetString();
// Check if this comes from either Hexen.wad or Hexdd.wad and if so, map to the string table.
int fileno = Wads.GetLumpFile(lump);
auto fn = Wads.GetWadName(fileno);
if (fn && (!stricmp(fn, "HEXEN.WAD") || !stricmp(fn, "HEXDD.WAD")))
{
FStringf key("TXT_%.5s_%s", fn, sc.String);
if (GStrings.exists(key))
{
mText = "$" + key;
done = true;
}
}
if (!done)
mText = Wads.ReadLump(lump).GetString();
}
else
{

View file

@ -1,86 +0,0 @@
/*
** lumpconfigfile.cpp
** Reads an .ini file in a lump
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
** I don't remember what I was going to use this for, but it's small, so I'll
** leave it in for now.
*/
#include <string.h>
#include <stdio.h>
#include "doomtype.h"
#include "lumpconfigfile.h"
#include "w_wad.h"
#if 0
FLumpConfigFile::FLumpConfigFile (int lump)
{
State.lump = lump;
State.lumpdata = (char *)W_MapLumpNum (lump);
State.end = State.lumpdata + lumpinfo[lump].size;
State.pos = State.lumpdata;
}
FLumpConfigFile::~FLumpConfigFile ()
{
W_UnMapLump (State.lumpdata);
}
void FLumpConfigFile::LoadConfigFile ()
{
ReadConfig (&State);
}
char *FLumpConfigFile::ReadLine (char *string, int n, void *file) const
{
LumpState *state = (LumpState *)file;
char *pos = state->pos;
int len = 0;
n--;
if (state->pos + n > state->end)
n = (int)(state->end - state->pos);
while (len < n && pos[n] != '\n')
n++;
string[n] = 0;
if (n == 0)
{
return NULL;
}
memcpy (string, pos, n);
state->pos += n;
return string;
}
#endif

View file

@ -1,62 +0,0 @@
/*
** lumpconfigfile.h
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#ifndef __LUMPCONFIGFILE_H__
#define __LUMPCONFIGFILE_H__
#include "configfile.h"
class FLumpConfigFile : public FConfigFile
{
public:
FLumpConfigFile (int lump);
~FLumpConfigFile ();
void LoadConfigFile ();
protected:
virtual char *ReadLine (char *string, int n, void *file) const;
private:
struct LumpState
{
int lump;
char *lumpdata;
char *end;
char *pos;
};
LumpState State;
};
#endif //__LUMPCONFIGFILE_H__

View file

@ -3016,7 +3016,7 @@ void MapLoader::LoadLevel(MapData *map, const char *lumpname, int position)
LoadMapinfoACSLump();
P_LoadStrifeConversations(Level, map, lumpname);
LoadStrifeConversations(map, lumpname);
FMissingTextureTracker missingtex;

View file

@ -4,6 +4,9 @@
#include "g_levellocals.h"
class FileReader;
struct FStrifeDialogueNode;
struct FStrifeDialogueReply;
struct Response;
struct EDMapthing
{
@ -98,6 +101,7 @@ struct MapData;
class MapLoader
{
friend class UDMFParser;
friend class USDFParser;
void *level; // this is to hide the global variable and produce an error for referencing it.
public:
FLevelLocals *Level;
@ -176,6 +180,16 @@ private:
void ReportUnpairedMinisegs();
void CalcIndices();
// Strife dialogue
void LoadStrifeConversations (MapData *map, const char *mapname);
bool LoadScriptFile (const char *name, bool include, int type);
bool LoadScriptFile(const char *name, int lumpnum, FileReader &lump, int numnodes, bool include, int type);
FStrifeDialogueNode *ReadRetailNode (const char *name, FileReader &lump, uint32_t &prevSpeakerType);
FStrifeDialogueNode *ReadTeaserNode (const char *name, FileReader &lump, uint32_t &prevSpeakerType);
void ParseReplies (const char *name, int pos, FStrifeDialogueReply **replyptr, Response *responses);
bool ParseUSDF(int lumpnum, FileReader &lump, int lumplen);
// Specials
void SpawnSpecials();
void InitSectorSpecial(sector_t *sector, int special);

View file

@ -667,7 +667,7 @@ void MapLoader::FloodSectorStacks()
for (auto section : HandledSections)
{
if (section->sector != &sector)
Printf("Marked section of sector %d for ceiling portal\n", section->sector->Index());
DPrintf(DMSG_NOTIFY, "Marked section of sector %d for ceiling portal\n", section->sector->Index());
section->flags |= FSection::DONTRENDERCEILING;
}
}
@ -688,7 +688,7 @@ void MapLoader::FloodSectorStacks()
for (auto section : HandledSections)
{
if (section->sector != &sector)
Printf("Marked section of sector %d for floor portal\n", section->sector->Index());
DPrintf(DMSG_NOTIFY, "Marked section of sector %d for floor portal\n", section->sector->Index());
section->flags |= FSection::DONTRENDERFLOOR;
}
}

View file

@ -0,0 +1,560 @@
/*
** strifedialogue.cpp
** loads Strife style conversation dialogs
**
**---------------------------------------------------------------------------
** Copyright 2004-2008 Randy Heit
** Copyright 2006-2019 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include <assert.h>
#include "actor.h"
#include "p_conversation.h"
#include "w_wad.h"
#include "cmdlib.h"
#include "v_text.h"
#include "gi.h"
#include "a_keys.h"
#include "p_enemy.h"
#include "gstrings.h"
#include "p_setup.h"
#include "d_net.h"
#include "d_event.h"
#include "doomstat.h"
#include "c_console.h"
#include "g_levellocals.h"
#include "maploader.h"
// The conversations as they exist inside a SCRIPTxx lump.
struct Response
{
int32_t GiveType;
int32_t Item[3];
int32_t Count[3];
char Reply[32];
char Yes[80];
int32_t Link;
uint32_t Log;
char No[80];
};
struct Speech
{
uint32_t SpeakerType;
int32_t DropType;
int32_t ItemCheck[3];
int32_t Link;
char Name[16];
char Sound[8];
char Backdrop[8];
char Dialogue[320];
Response Responses[5];
};
// The Teaser version of the game uses an older version of the structure
struct TeaserSpeech
{
uint32_t SpeakerType;
int32_t DropType;
uint32_t VoiceNumber;
char Name[16];
char Dialogue[320];
Response Responses[5];
};
//============================================================================
//
// P_LoadStrifeConversations
//
// Loads the SCRIPT00 and SCRIPTxx files for a corresponding map.
//
//============================================================================
void MapLoader::LoadStrifeConversations (MapData *map, const char *mapname)
{
if (map->Size(ML_CONVERSATION) > 0)
{
LoadScriptFile (nullptr, map->lumpnum, map->Reader(ML_CONVERSATION), map->Size(ML_CONVERSATION), false, 0);
}
else
{
if (strnicmp (mapname, "MAP", 3) == 0)
{
char scriptname_b[9] = { 'S','C','R','I','P','T',mapname[3],mapname[4],0 };
char scriptname_t[9] = { 'D','I','A','L','O','G',mapname[3],mapname[4],0 };
if ( LoadScriptFile(scriptname_t, false, 2)
|| LoadScriptFile(scriptname_b, false, 1))
{
return;
}
}
if (gameinfo.Dialogue.IsNotEmpty())
{
if (LoadScriptFile(gameinfo.Dialogue, false, 0))
{
return;
}
}
LoadScriptFile("SCRIPT00", false, 1);
}
}
//============================================================================
//
// LoadScriptFile
//
// Loads a SCRIPTxx file and converts it into a more useful internal format.
//
//============================================================================
bool MapLoader::LoadScriptFile (const char *name, bool include, int type)
{
int lumpnum = Wads.CheckNumForName (name);
const bool found = lumpnum >= 0
|| (lumpnum = Wads.CheckNumForFullName (name)) >= 0;
if (!found)
{
if (type == 0)
{
Printf(TEXTCOLOR_RED "Could not find dialog file %s\n", name);
}
return false;
}
FileReader lump = Wads.ReopenLumpReader (lumpnum);
auto fn = Wads.GetLumpFile(lumpnum);
auto wadname = Wads.GetWadName(fn);
if (stricmp(wadname, "STRIFE0.WAD") && stricmp(wadname, "STRIFE1.WAD") && stricmp(wadname, "SVE.WAD")) name = nullptr; // Only localize IWAD content.
if (name && !stricmp(name, "SCRIPT00")) name = nullptr; // This only contains random string references which already use the string table.
bool res = LoadScriptFile(name, lumpnum, lump, Wads.LumpLength(lumpnum), include, type);
return res;
}
bool MapLoader::LoadScriptFile(const char *name, int lumpnum, FileReader &lump, int numnodes, bool include, int type)
{
int i;
uint32_t prevSpeakerType;
FStrifeDialogueNode *node;
char buffer[4];
lump.Read(buffer, 4);
lump.Seek(-4, FileReader::SeekCur);
// The binary format is so primitive that this check is enough to detect it.
bool isbinary = (buffer[0] == 0 || buffer[1] == 0 || buffer[2] == 0 || buffer[3] == 0);
if ((type == 1 && !isbinary) || (type == 2 && isbinary))
{
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
return false;
}
if (!isbinary)
{
ParseUSDF(lumpnum, lump, numnodes);
}
else
{
if (!include)
{
LoadScriptFile("SCRIPT00", true, 1);
}
if (!(gameinfo.flags & GI_SHAREWARE))
{
// Strife scripts are always a multiple of 1516 bytes because each entry
// is exactly 1516 bytes long.
if (numnodes % 1516 != 0)
{
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
return false;
}
numnodes /= 1516;
}
else
{
// And the teaser version has 1488-byte entries.
if (numnodes % 1488 != 0)
{
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
return false;
}
numnodes /= 1488;
}
prevSpeakerType = 0;
for (i = 0; i < numnodes; ++i)
{
if (!(gameinfo.flags & GI_SHAREWARE))
{
node = ReadRetailNode (name, lump, prevSpeakerType);
}
else
{
node = ReadTeaserNode (name, lump, prevSpeakerType);
}
node->ThisNodeNum = Level->StrifeDialogues.Push(node);
}
}
return true;
}
//============================================================================
//
// ReadRetailNode
//
// Converts a single dialogue node from the Retail version of Strife.
//
//============================================================================
static FString TokenFromString(const char *speech)
{
FString token = speech;
token.ToUpper();
token.ReplaceChars(".,-+!?'", ' ');
token.Substitute(" ", "");
token.Truncate(5);
return token;
}
FStrifeDialogueNode *MapLoader::ReadRetailNode (const char *name, FileReader &lump, uint32_t &prevSpeakerType)
{
FStrifeDialogueNode *node;
Speech speech;
char fullsound[16];
PClassActor *type;
int j;
node = new FStrifeDialogueNode;
auto pos = lump.Tell();
lump.Read (&speech, sizeof(speech));
// Byte swap all the ints in the original data
speech.SpeakerType = LittleLong(speech.SpeakerType);
speech.DropType = LittleLong(speech.DropType);
speech.Link = LittleLong(speech.Link);
// Assign the first instance of a conversation as the default for its
// actor, so newly spawned actors will use this conversation by default.
type = GetStrifeType (speech.SpeakerType);
node->SpeakerType = type;
if ((signed)(speech.SpeakerType) >= 0 && prevSpeakerType != speech.SpeakerType)
{
if (type != NULL)
{
Level->ClassRoots[type->TypeName] = Level->StrifeDialogues.Size();
}
Level->DialogueRoots[speech.SpeakerType] = Level->StrifeDialogues.Size();
prevSpeakerType = speech.SpeakerType;
}
// Convert the rest of the data to our own internal format.
if (name)
{
FStringf label("$TXT_DLG_%s_d%d_%s", name, int(pos), TokenFromString(speech.Dialogue).GetChars());
node->Dialogue = GStrings.exists(label.GetChars()+1)? label : FString(speech.Dialogue);
}
else
{
node->Dialogue = speech.Dialogue;
}
// The speaker's portrait, if any.
speech.Dialogue[0] = 0; //speech.Backdrop[8] = 0;
node->Backdrop = speech.Backdrop;
// The speaker's voice for this node, if any.
speech.Backdrop[0] = 0; //speech.Sound[8] = 0;
mysnprintf (fullsound, countof(fullsound), "svox/%s", speech.Sound);
node->SpeakerVoice = fullsound;
// The speaker's name, if any.
speech.Sound[0] = 0; //speech.Name[16] = 0;
if (name && speech.Name[0])
{
FString label = speech.Name;
label.ReplaceChars(' ', '_');
label.ReplaceChars('\'', '_');
node->SpeakerName.Format("$TXT_SPEAKER_%s", label.GetChars());
if (!GStrings.exists(node->SpeakerName.GetChars() + 1)) node->SpeakerName = speech.Name;
}
else
{
node->SpeakerName = speech.Name;
}
// The item the speaker should drop when killed.
node->DropType = GetStrifeType(speech.DropType);
// Items you need to have to make the speaker use a different node.
node->ItemCheck.Resize(3);
for (j = 0; j < 3; ++j)
{
auto inv = GetStrifeType(speech.ItemCheck[j]);
if (!inv->IsDescendantOf(NAME_Inventory)) inv = nullptr;
node->ItemCheck[j].Item = inv;
node->ItemCheck[j].Amount = -1;
}
node->ItemCheckNode = speech.Link;
node->Children = NULL;
ParseReplies (name, int(pos), &node->Children, &speech.Responses[0]);
return node;
}
//============================================================================
//
// ReadTeaserNode
//
// Converts a single dialogue node from the Teaser version of Strife.
//
//============================================================================
FStrifeDialogueNode *MapLoader::ReadTeaserNode (const char *name, FileReader &lump, uint32_t &prevSpeakerType)
{
FStrifeDialogueNode *node;
TeaserSpeech speech;
char fullsound[16];
PClassActor *type;
int j;
node = new FStrifeDialogueNode;
auto pos = lump.Tell() * 1516 / 1488;
lump.Read (&speech, sizeof(speech));
// Byte swap all the ints in the original data
speech.SpeakerType = LittleLong(speech.SpeakerType);
speech.DropType = LittleLong(speech.DropType);
// Assign the first instance of a conversation as the default for its
// actor, so newly spawned actors will use this conversation by default.
type = GetStrifeType(speech.SpeakerType);
node->SpeakerType = type;
if ((signed)speech.SpeakerType >= 0 && prevSpeakerType != speech.SpeakerType)
{
if (type != NULL)
{
Level->ClassRoots[type->TypeName] = Level->StrifeDialogues.Size();
}
Level->DialogueRoots[speech.SpeakerType] = Level->StrifeDialogues.Size();
prevSpeakerType = speech.SpeakerType;
}
// Convert the rest of the data to our own internal format.
if (name)
{
FStringf label("$TXT_DLG_%s_d%d_%s", name, pos, TokenFromString(speech.Dialogue).GetChars());
node->Dialogue = GStrings.exists(label.GetChars() + 1)? label : FString(speech.Dialogue);
}
else
{
node->Dialogue = speech.Dialogue;
}
// The Teaser version doesn't have portraits.
node->Backdrop = "";
// The speaker's voice for this node, if any.
if (speech.VoiceNumber != 0)
{
mysnprintf (fullsound, countof(fullsound), "svox/voc%u", speech.VoiceNumber);
node->SpeakerVoice = fullsound;
}
else
{
node->SpeakerVoice = 0;
}
// The speaker's name, if any.
speech.Dialogue[0] = 0; //speech.Name[16] = 0;
if (name && speech.Name[0])
{
FString label = speech.Name;
label.ReplaceChars(' ', '_');
label.ReplaceChars('\'', '_');
node->SpeakerName.Format("$TXT_SPEAKER_%s", label.GetChars());
if (!GStrings.exists(node->SpeakerName.GetChars() + 1)) node->SpeakerName = speech.Name;
}
else
{
node->SpeakerName = speech.Name;
}
// The item the speaker should drop when killed.
node->DropType = GetStrifeType (speech.DropType);
// Items you need to have to make the speaker use a different node.
node->ItemCheck.Resize(3);
for (j = 0; j < 3; ++j)
{
node->ItemCheck[j].Item = NULL;
node->ItemCheck[j].Amount = -1;
}
node->ItemCheckNode = 0;
node->Children = NULL;
ParseReplies (name, int(pos), &node->Children, &speech.Responses[0]);
return node;
}
//============================================================================
//
// ParseReplies
//
// Convert PC responses. Rather than being stored inside the main node, they
// hang off it as a singly-linked list, so no space is wasted on replies that
// don't even matter.
//
//============================================================================
void MapLoader::ParseReplies (const char *name, int pos, FStrifeDialogueReply **replyptr, Response *responses)
{
FStrifeDialogueReply *reply;
int j, k;
// Byte swap first.
for (j = 0; j < 5; ++j)
{
responses[j].GiveType = LittleLong(responses[j].GiveType);
responses[j].Link = LittleLong(responses[j].Link);
responses[j].Log = LittleLong(responses[j].Log);
for (k = 0; k < 3; ++k)
{
responses[j].Item[k] = LittleLong(responses[j].Item[k]);
responses[j].Count[k] = LittleLong(responses[j].Count[k]);
}
}
for (j = 0; j < 5; ++j)
{
Response *rsp = &responses[j];
// If the reply has no text and goes nowhere, then it doesn't
// need to be remembered.
if (rsp->Reply[0] == 0 && rsp->Link == 0)
{
continue;
}
reply = new FStrifeDialogueReply;
// The next node to use when this reply is chosen.
reply->NextNode = rsp->Link;
if (reply->NextNode < 0)
{
reply->NextNode *= -1;
reply->CloseDialog = false;
}
// The message to record in the log for this reply.
reply->LogNumber = rsp->Log;
reply->LogString = "";
// The item to receive when this reply is used.
reply->GiveType = GetStrifeType (rsp->GiveType);
reply->ActionSpecial = 0;
// Do you need anything special for this reply to succeed?
reply->ItemCheck.Resize(3);
for (k = 0; k < 3; ++k)
{
auto inv = GetStrifeType(rsp->Item[k]);
if (!inv->IsDescendantOf(NAME_Inventory)) inv = nullptr;
reply->ItemCheck[k].Item = inv;
reply->ItemCheck[k].Amount = rsp->Count[k];
}
reply->PrintAmount = reply->ItemCheck[0].Amount;
reply->ItemCheckRequire.Clear();
reply->ItemCheckExclude.Clear();
if (name)
{
FStringf label("$TXT_RPLY%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->Reply).GetChars());
reply->Reply = GStrings.exists(label.GetChars() + 1)? label : FString(rsp->Reply);
reply->Reply = label;
}
else
{
reply->Reply = rsp->Reply;
}
// If the first item check has a positive amount required, then
// add that to the reply string. Otherwise, use the reply as-is.
reply->NeedsGold = (rsp->Count[0] > 0);
// QuickYes messages are shown when you meet the item checks.
// QuickNo messages are shown when you don't.
// Note that empty nodes contain a '_' in retail Strife, a '.' in the teasers and an empty string in SVE.
if (((rsp->Yes[0] == '_' || rsp->Yes[0] == '.') && rsp->Yes[1] == 0) || rsp->Yes[0] == 0)
{
reply->QuickYes = "";
}
else
{
if (name)
{
FStringf label("$TXT_RYES%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->Yes).GetChars());
reply->QuickYes = GStrings.exists(label.GetChars() + 1)? label : FString(rsp->Yes);
}
else
{
reply->QuickYes = rsp->Yes;
}
}
if (reply->ItemCheck[0].Item != 0)
{
FStringf label("$TXT_RNO%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->No).GetChars());
reply->QuickNo = GStrings.exists(label.GetChars() + 1)? label : FString(rsp->No);
}
else
{
reply->QuickNo = "";
}
reply->Next = *replyptr;
*replyptr = reply;
replyptr = &reply->Next;
}
}

View file

@ -40,7 +40,7 @@
#include "gi.h"
#include "r_sky.h"
#include "g_level.h"
#include "p_udmf.h"
#include "udmf.h"
#include "r_state.h"
#include "w_wad.h"
#include "p_tags.h"

View file

@ -35,12 +35,13 @@
#include "p_setup.h"
#include "p_lnspec.h"
#include "p_conversation.h"
#include "p_udmf.h"
#include "udmf.h"
#include "doomerrors.h"
#include "actor.h"
#include "a_pickups.h"
#include "w_wad.h"
#include "g_levellocals.h"
#include "maploader.h"
#define Zd 1
#define St 2
@ -496,9 +497,9 @@ class USDFParser : public UDMFParserBase
//===========================================================================
public:
bool Parse(FLevelLocals *l, int lumpnum, FileReader &lump, int lumplen)
bool Parse(MapLoader *loader,int lumpnum, FileReader &lump, int lumplen)
{
Level = l;
Level = loader->Level;
sc.OpenMem(Wads.GetLumpFullName(lumpnum), lump.Read(lumplen));
sc.SetCMode(true);
// Namespace must be the first field because everything else depends on it.
@ -541,7 +542,7 @@ public:
{
sc.MustGetToken('=');
sc.MustGetToken(TK_StringConst);
LoadScriptFile(Level, sc.String, true);
loader->LoadScriptFile(sc.String, true, 0);
sc.MustGetToken(';');
}
else
@ -607,13 +608,13 @@ public:
bool P_ParseUSDF(FLevelLocals *l, int lumpnum, FileReader &lump, int lumplen)
bool MapLoader::ParseUSDF(int lumpnum, FileReader &lump, int lumplen)
{
USDFParser parse;
try
{
if (!parse.Parse(l, lumpnum, lump, lumplen))
if (!parse.Parse(this, lumpnum, lump, lumplen))
{
// clean up the incomplete dialogue structures here
return false;

View file

@ -185,14 +185,11 @@ void FSavegameManager::ReadSaveStrings()
if (arc.OpenReader((const char *)data, info->LumpSize))
{
int savever = 0;
FString engine;
FString iwad;
FString title;
arc("Save Version", savever);
arc("Engine", engine);
arc("Game WAD", iwad);
arc("Title", title);
FString engine = arc.GetString("Engine");
FString iwad = arc.GetString("Game WAD");
FString title = arc.GetString("Title");
if (engine.Compare(GAMESIG) != 0 || savever > SAVEVER)
{
@ -473,10 +470,10 @@ unsigned FSavegameManager::ExtractSaveData(int index)
FSerializer arc(nullptr);
if (arc.OpenReader((const char *)data, info->LumpSize))
{
FString time, pcomment, comment;
FString comment;
arc("Creation Time", time);
arc("Comment", pcomment);
FString time = arc.GetString("Creation Time");
FString pcomment = arc.GetString("Comment");
comment = time;
if (time.Len() > 0) comment += "\n";

View file

@ -52,6 +52,7 @@
#include "vm.h"
#include "events.h"
#include "v_video.h"
#include "i_system.h"
#include "scripting/types.h"
int DMenu::InMenu;
@ -404,11 +405,45 @@ DEFINE_ACTION_FUNCTION(DMenu, ActivateMenu)
//
//=============================================================================
EXTERN_CVAR(Int, cl_localizationmode)
void M_SetMenu(FName menu, int param)
{
// some menus need some special treatment
switch (menu)
{
case NAME_Mainmenu:
if (gameinfo.gametype & GAME_DoomStrifeChex) // Raven's games always used text based menus
{
if (gameinfo.forcetextinmenus) // If text is forced, this overrides any check.
{
menu = NAME_MainmenuTextOnly;
}
else
{
// For these games we must check up-front if they get localized because in that case another template must be used.
DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Mainmenu);
if (desc != nullptr)
{
if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
{
DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc);
if (ld->mFromEngine && cl_localizationmode != 0)
{
// This assumes that replacing one graphic will replace all of them.
// So this only checks the "New game" entry for localization capability.
FTextureID texid = TexMan.CheckForTexture("M_NGAME", ETextureType::MiscPatch);
if (!TexMan.OkForLocalization(texid, "$MNU_NEWGAME"))
{
menu = NAME_MainmenuTextOnly;
}
}
}
}
}
}
break;
case NAME_Episodemenu:
// sent from the player class menu
GameStartupInfo.Skill = -1;
@ -416,6 +451,7 @@ void M_SetMenu(FName menu, int param)
GameStartupInfo.PlayerClass =
param == -1000? nullptr :
param == -1? "Random" : GetPrintableDisplayName(PlayerClasses[param].Type).GetChars();
M_StartupEpisodeMenu(&GameStartupInfo); // needs player class name from class menu (later)
break;
case NAME_Skillmenu:

View file

@ -149,6 +149,7 @@ public:
EColorRange mFontColor;
EColorRange mFontColor2;
bool mCenter;
bool mFromEngine;
void Reset()
{
@ -164,6 +165,7 @@ public:
mFont = NULL;
mFontColor = CR_UNTRANSLATED;
mFontColor2 = CR_UNTRANSLATED;
mFromEngine = false;
}
size_t PropagateMark() override;
@ -339,6 +341,7 @@ void M_ActivateMenu(DMenu *menu);
void M_ClearMenus ();
void M_PreviousMenu ();
void M_ParseMenuDefs();
void M_StartupEpisodeMenu(FGameStartup *gs);
void M_StartupSkillMenu(FGameStartup *gs);
void M_StartControlPanel (bool makeSound);
void M_SetMenu(FName menu, int param = -1);

View file

@ -370,7 +370,7 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc)
if (cls != nullptr && cls->IsDescendantOf("ListMenuItem"))
{
auto func = dyn_cast<PFunction>(cls->FindSymbol("Init", true));
if (func != nullptr && !(func->Variants[0].Flags & (VARF_Protected | VARF_Private))) // skip internal classes which have a protexted init method.
if (func != nullptr && !(func->Variants[0].Flags & (VARF_Protected | VARF_Private))) // skip internal classes which have a protected init method.
{
auto &args = func->Variants[0].Proto->ArgumentTypes;
TArray<VMValue> params;
@ -404,7 +404,7 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc)
}
else if (args[i] == TypeFont)
{
auto f = FFont::FindFont(sc.String);
auto f = V_GetFont(sc.String);
if (f == nullptr)
{
sc.ScriptError("Unknown font %s", sc.String);
@ -645,6 +645,7 @@ static void ParseListMenu(FScanner &sc)
desc->mWLeft = 0;
desc->mWRight = 0;
desc->mCenter = false;
desc->mFromEngine = Wads.GetLumpFile(sc.LumpNum) == 0; // flags menu if the definition is from the IWAD.
ParseListMenuBody(sc, desc);
ReplaceMenu(sc, desc);
@ -1078,16 +1079,31 @@ void M_ParseMenuDefs()
//
//=============================================================================
static void BuildEpisodeMenu()
void M_StartupEpisodeMenu(FGameStartup *gs)
{
// Build episode menu
bool success = false;
bool isOld = false;
DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Episodemenu);
if (desc != nullptr)
{
if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
{
DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc);
// Delete previous contents
for(unsigned i=0; i<ld->mItems.Size(); i++)
{
FName n = ld->mItems[i]->mAction;
if (n == NAME_Skillmenu)
{
isOld = true;
ld->mItems.Resize(i);
break;
}
}
int posy = (int)ld->mYpos;
int topy = posy;
@ -1114,16 +1130,17 @@ static void BuildEpisodeMenu()
posy -= topdelta;
}
ld->mSelectedItem = ld->mItems.Size();
if (!isOld) ld->mSelectedItem = ld->mItems.Size();
for(unsigned i = 0; i < AllEpisodes.Size(); i++)
{
DMenuItemBase *it;
DMenuItemBase *it = nullptr;
if (AllEpisodes[i].mPicName.IsNotEmpty())
{
FTextureID tex = GetMenuTexture(AllEpisodes[i].mPicName);
it = CreateListMenuItemPatch(ld->mXpos, posy, ld->mLinespacing, AllEpisodes[i].mShortcut, tex, NAME_Skillmenu, i);
if (AllEpisodes[i].mEpisodeName.IsEmpty() || TexMan.OkForLocalization(tex, AllEpisodes[i].mEpisodeName))
it = CreateListMenuItemPatch(ld->mXpos, posy, ld->mLinespacing, AllEpisodes[i].mShortcut, tex, NAME_Skillmenu, i);
}
else
if (it == nullptr)
{
it = CreateListMenuItemText(ld->mXpos, posy, ld->mLinespacing, AllEpisodes[i].mShortcut,
AllEpisodes[i].mEpisodeName, ld->mFont, ld->mFontColor, ld->mFontColor2, NAME_Skillmenu, i);
@ -1142,6 +1159,7 @@ static void BuildEpisodeMenu()
}
}
}
else return; // do not recreate the option menu variant, because it is always text based.
}
if (!success)
{
@ -1186,6 +1204,7 @@ static void BuildPlayerclassMenu()
{
DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc);
// add player display
ld->mSelectedItem = ld->mItems.Size();
int posy = (int)ld->mYpos;
@ -1482,7 +1501,6 @@ static void InitKeySections()
void M_CreateMenus()
{
BuildEpisodeMenu();
BuildPlayerclassMenu();
InitCrosshairsList();
InitMusicMenus();
@ -1617,7 +1635,7 @@ void M_StartupSkillMenu(FGameStartup *gs)
for(unsigned int i = 0; i < MenuSkills.Size(); i++)
{
FSkillInfo &skill = *MenuSkills[i];
DMenuItemBase *li;
DMenuItemBase *li = nullptr;
// Using a different name for skills that must be confirmed makes handling this easier.
FName action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ?
NAME_StartgameConfirm : NAME_Startgame;
@ -1627,16 +1645,17 @@ void M_StartupSkillMenu(FGameStartup *gs)
pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass);
}
EColorRange color = (EColorRange)skill.GetTextColor();
if (color == CR_UNTRANSLATED) color = ld->mFontColor;
if (skill.PicName.Len() != 0 && pItemText == nullptr)
{
FTextureID tex = GetMenuTexture(skill.PicName);
li = CreateListMenuItemPatch(ld->mXpos, y, ld->mLinespacing, skill.Shortcut, tex, action, SkillIndices[i]);
if (skill.MenuName.IsEmpty() || TexMan.OkForLocalization(tex, skill.MenuName))
li = CreateListMenuItemPatch(ld->mXpos, y, ld->mLinespacing, skill.Shortcut, tex, action, SkillIndices[i]);
}
else
if (li == nullptr)
{
EColorRange color = (EColorRange)skill.GetTextColor();
if (color == CR_UNTRANSLATED) color = ld->mFontColor;
li = CreateListMenuItemText(x, y, ld->mLinespacing, skill.Shortcut,
li = CreateListMenuItemText(x, y, ld->mLinespacing, skill.Shortcut,
pItemText? *pItemText : skill.MenuName, ld->mFont, color,ld->mFontColor2, action, SkillIndices[i]);
}
ld->mItems.Push(li);

View file

@ -191,8 +191,8 @@ CCMD (quicksave)
S_Sound(CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE);
FString tempstring;
tempstring.Format(GStrings("QSPROMPT"), savegameManager.quickSaveSlot->SaveTitle.GetChars());
FString tempstring = GStrings("QSPROMPT");
tempstring.Substitute("%s", savegameManager.quickSaveSlot->SaveTitle.GetChars());
DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, false, NAME_None, []()
{
@ -234,8 +234,8 @@ CCMD (quickload)
G_LoadGame(savegameManager.quickSaveSlot->Filename.GetChars());
return;
}
FString tempstring;
tempstring.Format(GStrings("QLPROMPT"), savegameManager.quickSaveSlot->SaveTitle.GetChars());
FString tempstring = GStrings("QLPROMPT");
tempstring.Substitute("%s", savegameManager.quickSaveSlot->SaveTitle.GetChars());
M_StartControlPanel(true);

View file

@ -58,43 +58,6 @@
#include "v_video.h"
#include "actorinlines.h"
// The conversations as they exist inside a SCRIPTxx lump.
struct Response
{
int32_t GiveType;
int32_t Item[3];
int32_t Count[3];
char Reply[32];
char Yes[80];
int32_t Link;
uint32_t Log;
char No[80];
};
struct Speech
{
uint32_t SpeakerType;
int32_t DropType;
int32_t ItemCheck[3];
int32_t Link;
char Name[16];
char Sound[8];
char Backdrop[8];
char Dialogue[320];
Response Responses[5];
};
// The Teaser version of the game uses an older version of the structure
struct TeaserSpeech
{
uint32_t SpeakerType;
int32_t DropType;
uint32_t VoiceNumber;
char Name[16];
char Dialogue[320];
Response Responses[5];
};
static FRandom pr_randomspeech("RandomSpeech");
static int ConversationMenuY;
@ -103,10 +66,6 @@ static int ConversationMenuY;
static FStrifeDialogueNode *PrevNode;
static int StaticLastReply;
static bool LoadScriptFile(FLevelLocals *Level, const char *name, int lumpnum, FileReader &lump, int numnodes, bool include, int type);
static FStrifeDialogueNode *ReadRetailNode (FLevelLocals *Level, const char *name, FileReader &lump, uint32_t &prevSpeakerType);
static FStrifeDialogueNode *ReadTeaserNode (FLevelLocals *Level, const char *name, FileReader &lump, uint32_t &prevSpeakerType);
static void ParseReplies (const char *name, int pos, FStrifeDialogueReply **replyptr, Response *responses);
static bool DrawConversationMenu ();
static void PickConversationReply (int replyindex);
static void TerminalResponse (const char *str);
@ -153,458 +112,6 @@ int FLevelLocals::GetConversation(FName classname)
else return *pindex;
}
//============================================================================
//
// P_LoadStrifeConversations
//
// Loads the SCRIPT00 and SCRIPTxx files for a corresponding map.
//
//============================================================================
void P_LoadStrifeConversations (FLevelLocals *Level, MapData *map, const char *mapname)
{
if (map->Size(ML_CONVERSATION) > 0)
{
LoadScriptFile (Level, nullptr, map->lumpnum, map->Reader(ML_CONVERSATION), map->Size(ML_CONVERSATION), false, 0);
}
else
{
if (strnicmp (mapname, "MAP", 3) == 0)
{
char scriptname_b[9] = { 'S','C','R','I','P','T',mapname[3],mapname[4],0 };
char scriptname_t[9] = { 'D','I','A','L','O','G',mapname[3],mapname[4],0 };
if ( LoadScriptFile(Level, scriptname_t, false, 2)
|| LoadScriptFile(Level, scriptname_b, false, 1))
{
return;
}
}
if (gameinfo.Dialogue.IsNotEmpty())
{
if (LoadScriptFile(Level, gameinfo.Dialogue, false, 0))
{
return;
}
}
LoadScriptFile(Level, "SCRIPT00", false, 1);
}
}
//============================================================================
//
// LoadScriptFile
//
// Loads a SCRIPTxx file and converts it into a more useful internal format.
//
//============================================================================
bool LoadScriptFile (FLevelLocals *Level, const char *name, bool include, int type)
{
int lumpnum = Wads.CheckNumForName (name);
const bool found = lumpnum >= 0
|| (lumpnum = Wads.CheckNumForFullName (name)) >= 0;
if (!found)
{
if (type == 0)
{
Printf(TEXTCOLOR_RED "Could not find dialog file %s\n", name);
}
return false;
}
FileReader lump = Wads.ReopenLumpReader (lumpnum);
auto fn = Wads.GetLumpFile(lumpnum);
auto wadname = Wads.GetWadName(fn);
if (stricmp(wadname, "STRIFE0.WAD") && stricmp(wadname, "STRIFE1.WAD") && stricmp(wadname, "SVE.WAD")) name = nullptr; // Only localize IWAD content.
if (name && !stricmp(name, "SCRIPT00")) name = nullptr; // This only contains random string references which already use the string table.
bool res = LoadScriptFile(Level, name, lumpnum, lump, Wads.LumpLength(lumpnum), include, type);
return res;
}
static bool LoadScriptFile(FLevelLocals *Level, const char *name, int lumpnum, FileReader &lump, int numnodes, bool include, int type)
{
int i;
uint32_t prevSpeakerType;
FStrifeDialogueNode *node;
char buffer[4];
lump.Read(buffer, 4);
lump.Seek(-4, FileReader::SeekCur);
// The binary format is so primitive that this check is enough to detect it.
bool isbinary = (buffer[0] == 0 || buffer[1] == 0 || buffer[2] == 0 || buffer[3] == 0);
if ((type == 1 && !isbinary) || (type == 2 && isbinary))
{
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
return false;
}
if (!isbinary)
{
P_ParseUSDF(Level, lumpnum, lump, numnodes);
}
else
{
if (!include)
{
LoadScriptFile(Level, "SCRIPT00", true, 1);
}
if (!(gameinfo.flags & GI_SHAREWARE))
{
// Strife scripts are always a multiple of 1516 bytes because each entry
// is exactly 1516 bytes long.
if (numnodes % 1516 != 0)
{
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
return false;
}
numnodes /= 1516;
}
else
{
// And the teaser version has 1488-byte entries.
if (numnodes % 1488 != 0)
{
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
return false;
}
numnodes /= 1488;
}
prevSpeakerType = 0;
for (i = 0; i < numnodes; ++i)
{
if (!(gameinfo.flags & GI_SHAREWARE))
{
node = ReadRetailNode (Level, name, lump, prevSpeakerType);
}
else
{
node = ReadTeaserNode (Level, name, lump, prevSpeakerType);
}
node->ThisNodeNum = Level->StrifeDialogues.Push(node);
}
}
return true;
}
//============================================================================
//
// ReadRetailNode
//
// Converts a single dialogue node from the Retail version of Strife.
//
//============================================================================
static FString TokenFromString(const char *speech)
{
FString token = speech;
token.ToUpper();
token.ReplaceChars(".,-+!?'", ' ');
token.Substitute(" ", "");
token.Truncate(5);
return token;
}
static FStrifeDialogueNode *ReadRetailNode (FLevelLocals *Level, const char *name, FileReader &lump, uint32_t &prevSpeakerType)
{
FStrifeDialogueNode *node;
Speech speech;
char fullsound[16];
PClassActor *type;
int j;
node = new FStrifeDialogueNode;
auto pos = lump.Tell();
lump.Read (&speech, sizeof(speech));
// Byte swap all the ints in the original data
speech.SpeakerType = LittleLong(speech.SpeakerType);
speech.DropType = LittleLong(speech.DropType);
speech.Link = LittleLong(speech.Link);
// Assign the first instance of a conversation as the default for its
// actor, so newly spawned actors will use this conversation by default.
type = GetStrifeType (speech.SpeakerType);
node->SpeakerType = type;
if ((signed)(speech.SpeakerType) >= 0 && prevSpeakerType != speech.SpeakerType)
{
if (type != NULL)
{
Level->ClassRoots[type->TypeName] = Level->StrifeDialogues.Size();
}
Level->DialogueRoots[speech.SpeakerType] = Level->StrifeDialogues.Size();
prevSpeakerType = speech.SpeakerType;
}
// Convert the rest of the data to our own internal format.
if (name)
{
FStringf label("$TXT_DLG_%s_d%d_%s", name, int(pos), TokenFromString(speech.Dialogue).GetChars());
node->Dialogue = label;
}
else
{
node->Dialogue = speech.Dialogue;
}
// The speaker's portrait, if any.
speech.Dialogue[0] = 0; //speech.Backdrop[8] = 0;
node->Backdrop = speech.Backdrop;
// The speaker's voice for this node, if any.
speech.Backdrop[0] = 0; //speech.Sound[8] = 0;
mysnprintf (fullsound, countof(fullsound), "svox/%s", speech.Sound);
node->SpeakerVoice = fullsound;
// The speaker's name, if any.
speech.Sound[0] = 0; //speech.Name[16] = 0;
node->SpeakerName = speech.Name;
// The item the speaker should drop when killed.
node->DropType = GetStrifeType(speech.DropType);
// Items you need to have to make the speaker use a different node.
node->ItemCheck.Resize(3);
for (j = 0; j < 3; ++j)
{
auto inv = GetStrifeType(speech.ItemCheck[j]);
if (!inv->IsDescendantOf(NAME_Inventory)) inv = nullptr;
node->ItemCheck[j].Item = inv;
node->ItemCheck[j].Amount = -1;
}
node->ItemCheckNode = speech.Link;
node->Children = NULL;
ParseReplies (name, int(pos), &node->Children, &speech.Responses[0]);
return node;
}
//============================================================================
//
// ReadTeaserNode
//
// Converts a single dialogue node from the Teaser version of Strife.
//
//============================================================================
static FStrifeDialogueNode *ReadTeaserNode (FLevelLocals *Level, const char *name, FileReader &lump, uint32_t &prevSpeakerType)
{
FStrifeDialogueNode *node;
TeaserSpeech speech;
char fullsound[16];
PClassActor *type;
int j;
node = new FStrifeDialogueNode;
auto pos = lump.Tell() * 1516 / 1488;
lump.Read (&speech, sizeof(speech));
// Byte swap all the ints in the original data
speech.SpeakerType = LittleLong(speech.SpeakerType);
speech.DropType = LittleLong(speech.DropType);
// Assign the first instance of a conversation as the default for its
// actor, so newly spawned actors will use this conversation by default.
type = GetStrifeType(speech.SpeakerType);
node->SpeakerType = type;
if ((signed)speech.SpeakerType >= 0 && prevSpeakerType != speech.SpeakerType)
{
if (type != NULL)
{
Level->ClassRoots[type->TypeName] = Level->StrifeDialogues.Size();
}
Level->DialogueRoots[speech.SpeakerType] = Level->StrifeDialogues.Size();
prevSpeakerType = speech.SpeakerType;
}
// Convert the rest of the data to our own internal format.
if (name)
{
FStringf label("$TXT_DLG_%s_d%d_%s", name, pos, TokenFromString(speech.Dialogue).GetChars());
node->Dialogue = label;
}
else
{
node->Dialogue = speech.Dialogue;
}
// The Teaser version doesn't have portraits.
node->Backdrop = "";
// The speaker's voice for this node, if any.
if (speech.VoiceNumber != 0)
{
mysnprintf (fullsound, countof(fullsound), "svox/voc%u", speech.VoiceNumber);
node->SpeakerVoice = fullsound;
}
else
{
node->SpeakerVoice = 0;
}
// The speaker's name, if any.
speech.Dialogue[0] = 0; //speech.Name[16] = 0;
node->SpeakerName = speech.Name;
// The item the speaker should drop when killed.
node->DropType = GetStrifeType (speech.DropType);
// Items you need to have to make the speaker use a different node.
node->ItemCheck.Resize(3);
for (j = 0; j < 3; ++j)
{
node->ItemCheck[j].Item = NULL;
node->ItemCheck[j].Amount = -1;
}
node->ItemCheckNode = 0;
node->Children = NULL;
ParseReplies (name, int(pos), &node->Children, &speech.Responses[0]);
return node;
}
//============================================================================
//
// ParseReplies
//
// Convert PC responses. Rather than being stored inside the main node, they
// hang off it as a singly-linked list, so no space is wasted on replies that
// don't even matter.
//
//============================================================================
static void ParseReplies (const char *name, int pos, FStrifeDialogueReply **replyptr, Response *responses)
{
FStrifeDialogueReply *reply;
int j, k;
// Byte swap first.
for (j = 0; j < 5; ++j)
{
responses[j].GiveType = LittleLong(responses[j].GiveType);
responses[j].Link = LittleLong(responses[j].Link);
responses[j].Log = LittleLong(responses[j].Log);
for (k = 0; k < 3; ++k)
{
responses[j].Item[k] = LittleLong(responses[j].Item[k]);
responses[j].Count[k] = LittleLong(responses[j].Count[k]);
}
}
for (j = 0; j < 5; ++j)
{
Response *rsp = &responses[j];
// If the reply has no text and goes nowhere, then it doesn't
// need to be remembered.
if (rsp->Reply[0] == 0 && rsp->Link == 0)
{
continue;
}
reply = new FStrifeDialogueReply;
// The next node to use when this reply is chosen.
reply->NextNode = rsp->Link;
if (reply->NextNode < 0)
{
reply->NextNode *= -1;
reply->CloseDialog = false;
}
// The message to record in the log for this reply.
reply->LogNumber = rsp->Log;
reply->LogString = "";
// The item to receive when this reply is used.
reply->GiveType = GetStrifeType (rsp->GiveType);
reply->ActionSpecial = 0;
// Do you need anything special for this reply to succeed?
reply->ItemCheck.Resize(3);
for (k = 0; k < 3; ++k)
{
auto inv = GetStrifeType(rsp->Item[k]);
if (!inv->IsDescendantOf(NAME_Inventory)) inv = nullptr;
reply->ItemCheck[k].Item = inv;
reply->ItemCheck[k].Amount = rsp->Count[k];
}
reply->PrintAmount = reply->ItemCheck[0].Amount;
reply->ItemCheckRequire.Clear();
reply->ItemCheckExclude.Clear();
if (name)
{
FStringf label("$TXT_RPLY%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->Reply).GetChars());
reply->Reply = label;
}
else
{
reply->Reply = rsp->Reply;
}
// If the first item check has a positive amount required, then
// add that to the reply string. Otherwise, use the reply as-is.
reply->NeedsGold = (rsp->Count[0] > 0);
// QuickYes messages are shown when you meet the item checks.
// QuickNo messages are shown when you don't.
// Note that empty nodes contain a '_' in retail Strife, a '.' in the teasers and an empty string in SVE.
if (((rsp->Yes[0] == '_' || rsp->Yes[0] == '.') && rsp->Yes[1] == 0) || rsp->Yes[0] == 0)
{
reply->QuickYes = "";
}
else
{
if (name)
{
FStringf label("$TXT_RYES%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->Yes).GetChars());
reply->QuickYes = label;
}
else
{
reply->QuickYes = rsp->Yes;
}
}
if (reply->ItemCheck[0].Item != 0)
{
if (name && strncmp(rsp->No, "NO. ", 4)) // All 'no' nodes starting with 'NO.' won't ever be shown and they all contain broken text.
{
FStringf label("$TXT_RNO%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->No).GetChars());
reply->QuickNo = label;
}
else
{
reply->QuickNo = rsp->No;
}
}
else
{
reply->QuickNo = "";
}
reply->Next = *replyptr;
*replyptr = reply;
replyptr = &reply->Next;
}
}
//============================================================================
//
// FStrifeDialogueNode :: ~FStrifeDialogueNode

View file

@ -65,9 +65,6 @@ struct MapData;
PClassActor *GetStrifeType (int typenum);
bool LoadScriptFile (FLevelLocals *Level, const char *name, bool include, int type = 0);
void P_LoadStrifeConversations (FLevelLocals *Level, MapData *map, const char *mapname);
void P_FreeStrifeConversations ();
void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveangle);
@ -76,7 +73,6 @@ void P_ResumeConversation ();
void P_ConversationCommand (int netcode, int player, uint8_t **stream);
class FileReader;
bool P_ParseUSDF(FLevelLocals *Level, int lumpnum, FileReader &lump, int lumplen);
#endif

View file

@ -58,8 +58,11 @@
#include "events.h"
#include "p_destructible.h"
#include "r_sky.h"
#include "version.h"
#include "fragglescript/t_script.h"
EXTERN_CVAR(Bool, save_formatted)
//==========================================================================
//
//
@ -486,6 +489,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FPolyObj &poly, FPolyO
("blocked", poly.bBlocked)
("hasportals", poly.bHasPortals)
("specialdata", poly.specialdata)
("level", poly.Level)
.EndObject();
if (arc.isReading())
@ -526,46 +530,39 @@ FSerializer &Serialize(FSerializer &arc, const char *key, zone_t &z, zone_t *def
//
//==========================================================================
void P_SerializeSounds(FLevelLocals *Level, FSerializer &arc)
void FLevelLocals::SerializeSounds(FSerializer &arc)
{
S_SerializeSounds(arc);
const char *name = NULL;
uint8_t order;
float musvol = Level->MusicVolume;
if (arc.isWriting())
if (isPrimaryLevel())
{
order = S_GetMusic(&name);
}
arc.StringPtr("musicname", name)
("musicorder", order)
("musicvolume", musvol);
S_SerializeSounds(arc);
const char *name = NULL;
uint8_t order;
float musvol = MusicVolume;
if (arc.isReading())
{
if (!S_ChangeMusic(name, order))
Level->SetMusic();
Level->SetMusicVolume(musvol);
if (arc.isWriting())
{
order = S_GetMusic(&name);
}
arc.StringPtr("musicname", name)
("musicorder", order)
("musicvolume", musvol);
if (arc.isReading())
{
if (!S_ChangeMusic(name, order))
SetMusic();
SetMusicVolume(musvol);
}
}
}
//==========================================================================
//
//
//
//==========================================================================
void CopyPlayer(player_t *dst, player_t *src, const char *name);
static void ReadOnePlayer(FSerializer &arc, bool skipload);
static void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload);
//==========================================================================
//
// P_ArchivePlayers
//
//==========================================================================
void P_SerializePlayers(FLevelLocals *Level, FSerializer &arc, bool skipload)
void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload)
{
int numPlayers, numPlayersNow;
int i;
@ -573,7 +570,7 @@ void P_SerializePlayers(FLevelLocals *Level, FSerializer &arc, bool skipload)
// Count the number of players present right now.
for (numPlayersNow = 0, i = 0; i < MAXPLAYERS; ++i)
{
if (Level->PlayerInGame(i))
if (PlayerInGame(i))
{
++numPlayersNow;
}
@ -588,13 +585,13 @@ void P_SerializePlayers(FLevelLocals *Level, FSerializer &arc, bool skipload)
// Record each player's name, followed by their data.
for (i = 0; i < MAXPLAYERS; ++i)
{
if (Level->PlayerInGame(i))
if (PlayerInGame(i))
{
if (arc.BeginObject(nullptr))
{
const char *n = Level->Players[i]->userinfo.GetName();
const char *n = Players[i]->userinfo.GetName();
arc.StringPtr("playername", n);
Level->Players[i]->Serialize(arc);
Players[i]->Serialize(arc);
arc.EndObject();
}
}
@ -622,10 +619,10 @@ void P_SerializePlayers(FLevelLocals *Level, FSerializer &arc, bool skipload)
}
if (!skipload && numPlayersNow > numPlayers)
{
Level->SpawnExtraPlayers();
SpawnExtraPlayers();
}
// Redo pitch limits, since the spawned player has them at 0.
auto p = Level->GetConsolePlayer();
auto p = GetConsolePlayer();
if (p) p->SendPitchLimits();
}
}
@ -636,7 +633,7 @@ void P_SerializePlayers(FLevelLocals *Level, FSerializer &arc, bool skipload)
//
//==========================================================================
static void ReadOnePlayer(FSerializer &arc, bool skipload)
void FLevelLocals::ReadOnePlayer(FSerializer &arc, bool skipload)
{
int i;
const char *name = NULL;
@ -663,20 +660,20 @@ static void ReadOnePlayer(FSerializer &arc, bool skipload)
// via a net command, but that won't be processed in time for a screen
// wipe, so we need something here.
playerTemp.MaxPitch = playerTemp.MinPitch = playerTemp.mo->Angles.Pitch;
CopyPlayer(&players[i], &playerTemp, name);
CopyPlayer(Players[i], &playerTemp, name);
}
else
{
// we need the player actor, so that G_FinishTravel can destroy it later.
players[i].mo = playerTemp.mo;
Players[i]->mo = playerTemp.mo;
}
}
else
{
if (players[i].mo != NULL)
if (Players[i]->mo != NULL)
{
players[i].mo->Destroy();
players[i].mo = NULL;
Players[i]->mo->Destroy();
Players[i]->mo = NULL;
}
}
}
@ -691,7 +688,7 @@ static void ReadOnePlayer(FSerializer &arc, bool skipload)
//
//==========================================================================
static void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload)
void FLevelLocals::ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload)
{
// For two or more players, read each player into a temporary array.
int i, j;
@ -729,7 +726,7 @@ static void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayers
if (playerUsed[j] == 0 && stricmp(players[j].userinfo.GetName(), nametemp[i]) == 0)
{ // Found a match, so copy our temp player to the real player
Printf("Found player %d (%s) at %d\n", i, nametemp[i], j);
CopyPlayer(&players[j], &playertemp[i], nametemp[i]);
CopyPlayer(Players[j], &playertemp[i], nametemp[i]);
playerUsed[j] = 1;
tempPlayerUsed[i] = 1;
break;
@ -802,7 +799,7 @@ static void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayers
//
//==========================================================================
void CopyPlayer(player_t *dst, player_t *src, const char *name)
void FLevelLocals::CopyPlayer(player_t *dst, player_t *src, const char *name)
{
// The userinfo needs to be saved for real players, but it
// needs to come from the save for bots.
@ -823,7 +820,7 @@ void CopyPlayer(player_t *dst, player_t *src, const char *name)
if (dst->Bot != nullptr)
{
botinfo_t *thebot = src->mo->Level->BotInfo.botinfo;
botinfo_t *thebot = BotInfo.botinfo;
while (thebot && stricmp(name, thebot->name))
{
thebot = thebot->next;
@ -832,14 +829,14 @@ void CopyPlayer(player_t *dst, player_t *src, const char *name)
{
thebot->inuse = BOTINUSE_Yes;
}
src->mo->Level->BotInfo.botnum++;
BotInfo.botnum++;
dst->userinfo.TransferFrom(uibackup2);
}
else
{
dst->userinfo.TransferFrom(uibackup);
// The player class must come from the save, so that the menu reflects the currently playing one.
dst->userinfo.PlayerClassChanged(src->mo->GetInfo()->DisplayName);
dst->userinfo.PlayerClassChanged(src->mo->GetInfo()->DisplayName);
}
// Validate the skin
@ -868,6 +865,7 @@ void CopyPlayer(player_t *dst, player_t *src, const char *name)
dst->usedown = usedown;
}
//==========================================================================
//
//
@ -1006,8 +1004,8 @@ void FLevelLocals::Serialize(FSerializer &arc, bool hubload)
StatusBar->SerializeMessages(arc);
FRemapTable::StaticSerializeTranslations(arc);
canvasTextureInfo.Serialize(arc);
P_SerializePlayers(this, arc, hubload);
P_SerializeSounds(this, arc);
SerializePlayers(arc, hubload);
SerializeSounds(arc);
// Regenerate some data that wasn't saved
if (arc.isReading())
@ -1036,3 +1034,87 @@ void FLevelLocals::Serialize(FSerializer &arc, bool hubload)
}
}
//==========================================================================
//
// Archives the current level
//
//==========================================================================
void FLevelLocals::SnapshotLevel()
{
info->Snapshot.Clean();
if (info->isValid())
{
FSerializer arc(this);
if (arc.OpenWriter(save_formatted))
{
SaveVersion = SAVEVER;
Serialize(arc, false);
info->Snapshot = arc.GetCompressedOutput();
}
}
}
//==========================================================================
//
// Unarchives the current level based on its snapshot
// The level should have already been loaded and setup.
//
//==========================================================================
void FLevelLocals::UnSnapshotLevel(bool hubLoad)
{
if (info->Snapshot.mBuffer == nullptr)
return;
if (info->isValid())
{
FSerializer arc(this);
if (!arc.OpenReader(&info->Snapshot))
{
I_Error("Failed to load savegame");
return;
}
Serialize(arc, hubLoad);
FromSnapshot = true;
auto it = GetThinkerIterator<AActor>(NAME_PlayerPawn);
AActor *pawn, *next;
next = it.Next();
while ((pawn = next) != 0)
{
next = it.Next();
if (pawn->player == nullptr || pawn->player->mo == nullptr || !PlayerInGame(pawn->player))
{
int i;
// If this isn't the unmorphed original copy of a player, destroy it, because it's extra.
for (i = 0; i < MAXPLAYERS; ++i)
{
if (PlayerInGame(i) && Players[i]->morphTics && Players[i]->mo->alternative == pawn)
{
break;
}
}
if (i == MAXPLAYERS)
{
pawn->Destroy();
}
}
}
arc.Close();
}
// No reason to keep the snapshot around once the level's been entered.
info->Snapshot.Clean();
if (hubLoad)
{
// Unlock ACS global strings that were locked when the snapshot was made.
Behaviors.UnlockLevelVarStrings(levelnum);
}
}

View file

@ -260,8 +260,7 @@ void FLevelLocals::ClearLevelData()
ClearAllSubsectorLinks(); // can't be done as part of the polyobj deletion process.
total_monsters = total_items = total_secrets =
killed_monsters = found_items = found_secrets =
wminfo.maxfrags = 0;
killed_monsters = found_items = found_secrets = 0;
for (int i = 0; i < 4; i++)
{
@ -375,7 +374,6 @@ void P_SetupLevel(FLevelLocals *Level, int position, bool newGame)
// This is motivated as follows:
Level->maptype = MAPTYPE_UNKNOWN;
wminfo.partime = 180;
if (!savegamerestore)
{

View file

@ -371,18 +371,37 @@ size_t player_t::PropagateMark()
void player_t::SetLogNumber (int num)
{
char lumpname[16];
char lumpname[26];
int lumpnum;
// First look up TXT_LOGTEXT%d in the string table
mysnprintf(lumpname, countof(lumpname), "$TXT_LOGTEXT%d", num);
auto text = GStrings[lumpname+1];
if (text)
{
SetLogText(lumpname); // set the label, not the content, so that a language change can be picked up.
return;
}
mysnprintf (lumpname, countof(lumpname), "LOG%d", num);
lumpnum = Wads.CheckNumForName (lumpname);
if (lumpnum == -1)
{
// Leave the log message alone if this one doesn't exist.
//SetLogText (lumpname);
}
else
if (lumpnum != -1)
{
auto fn = Wads.GetLumpFile(lumpnum);
auto wadname = Wads.GetWadName(fn);
if (!stricmp(wadname, "STRIFE0.WAD") || !stricmp(wadname, "STRIFE1.WAD") || !stricmp(wadname, "SVE.WAD"))
{
// If this is an original IWAD text, try looking up its lower priority string version first.
mysnprintf(lumpname, countof(lumpname), "$TXT_ILOG%d", num);
auto text = GStrings[lumpname + 1];
if (text)
{
SetLogText(lumpname); // set the label, not the content, so that a language change can be picked up.
return;
}
}
auto lump = Wads.ReadLump(lumpnum);
SetLogText (lump.GetString());
}

View file

@ -1,99 +0,0 @@
/*
** critsec.cpp
**
**---------------------------------------------------------------------------
** Copyright 2014 Alexey Lysiuk
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "critsec.h"
#include <pthread.h>
class FInternalCriticalSection
{
public:
FInternalCriticalSection();
~FInternalCriticalSection();
void Enter();
void Leave();
private:
pthread_mutex_t m_mutex;
};
// TODO: add error handling
FInternalCriticalSection::FInternalCriticalSection()
{
pthread_mutexattr_t attributes;
pthread_mutexattr_init(&attributes);
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&m_mutex, &attributes);
pthread_mutexattr_destroy(&attributes);
}
FInternalCriticalSection::~FInternalCriticalSection()
{
pthread_mutex_destroy(&m_mutex);
}
void FInternalCriticalSection::Enter()
{
pthread_mutex_lock(&m_mutex);
}
void FInternalCriticalSection::Leave()
{
pthread_mutex_unlock(&m_mutex);
}
FInternalCriticalSection *CreateCriticalSection()
{
return new FInternalCriticalSection();
}
void DeleteCriticalSection(FInternalCriticalSection *c)
{
delete c;
}
void EnterCriticalSection(FInternalCriticalSection *c)
{
c->Enter();
}
void LeaveCriticalSection(FInternalCriticalSection *c)
{
c->Leave();
}

View file

@ -371,7 +371,7 @@ NSStringEncoding GetEncodingForUnicodeCharacter(const unichar character)
return NSWindowsCP1252StringEncoding;
}
unsigned char GetCharacterFromNSEvent(NSEvent* theEvent)
unsigned char GetCharacterFromNSEvent(NSEvent* theEvent, unichar *realchar)
{
const NSString* unicodeCharacters = [theEvent characters];
@ -400,6 +400,7 @@ unsigned char GetCharacterFromNSEvent(NSEvent* theEvent)
: '\0';
}
*realchar = unicodeCharacter;
return character;
}
@ -407,9 +408,10 @@ void ProcessKeyboardEventInMenu(NSEvent* theEvent)
{
event_t event = {};
unichar realchar;
event.type = EV_GUI_Event;
event.subtype = NSKeyDown == [theEvent type] ? EV_GUI_KeyDown : EV_GUI_KeyUp;
event.data2 = GetCharacterFromNSEvent(theEvent);
event.data2 = GetCharacterFromNSEvent(theEvent, &realchar);
event.data3 = ModifierFlagsToGUIKeyModifiers(theEvent);
if (EV_GUI_KeyDown == event.subtype && [theEvent isARepeat])
@ -462,7 +464,7 @@ void ProcessKeyboardEventInMenu(NSEvent* theEvent)
&& ShouldGenerateGUICharEvent(theEvent))
{
event.subtype = EV_GUI_Char;
event.data1 = event.data2;
event.data1 = realchar;
event.data2 = event.data3 & GKM_ALT;
D_PostEvent(&event);

View file

@ -1,95 +0,0 @@
/*
** critsec.cpp
**
**---------------------------------------------------------------------------
** Copyright 2006-2016 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
** Wraps an SDL mutex object. (A critical section is a Windows synchronization
** object similar to a mutex but optimized for access by threads belonging to
** only one process, hence the class name.)
*/
#include "SDL.h"
#include "i_system.h"
#include "doomerrors.h"
class FInternalCriticalSection
{
public:
FInternalCriticalSection()
{
CritSec = SDL_CreateMutex();
if (CritSec == NULL)
{
I_FatalError("Failed to create a critical section mutex.");
}
}
~FInternalCriticalSection()
{
if (CritSec != NULL)
{
SDL_DestroyMutex(CritSec);
}
}
void Enter()
{
if (SDL_mutexP(CritSec) != 0)
{
I_FatalError("Failed entering a critical section.");
}
}
void Leave()
{
if (SDL_mutexV(CritSec) != 0)
{
I_FatalError("Failed to leave a critical section.");
}
}
private:
SDL_mutex *CritSec;
};
FInternalCriticalSection *CreateCriticalSection()
{
return new FInternalCriticalSection();
}
void DeleteCriticalSection(FInternalCriticalSection *c)
{
delete c;
}
void EnterCriticalSection(FInternalCriticalSection *c)
{
c->Enter();
}
void LeaveCriticalSection(FInternalCriticalSection *c)
{
c->Leave();
}

View file

@ -46,6 +46,7 @@
#include "events.h"
#include "g_game.h"
#include "g_levellocals.h"
#include "utf8.h"
static void I_CheckGUICapture ();
@ -476,11 +477,17 @@ void MessagePump (const SDL_Event &sev)
case SDL_TEXTINPUT:
if (GUICapture)
{
event.type = EV_GUI_Event;
event.subtype = EV_GUI_Char;
event.data1 = sev.text.text[0];
event.data2 = !!(SDL_GetModState() & KMOD_ALT);
D_PostEvent (&event);
int size;
int unichar = utf8_decode((const uint8_t*)sev.text.text, &size);
if (size != 4)
{
event.type = EV_GUI_Event;
event.subtype = EV_GUI_Char;
event.data1 = (int16_t)unichar;
event.data2 = !!(SDL_GetModState() & KMOD_ALT);
D_PostEvent (&event);
}
}
break;

View file

@ -42,85 +42,124 @@
#include "v_video.h"
#include "w_wad.h"
#include "image.h"
#include "textures/formats/multipatchtexture.h"
#include "gstrings.h"
#include "vm.h"
#include "serializer.h"
int ListGetInt(VMVa_List &tags);
//==========================================================================
//
// reads one character from the string.
// This can handle both ISO 8859-1 and UTF-8, as well as mixed strings
// between both encodings, which may happen if inconsistent encoding is
// used between different files in a mod.
// Create a texture from a text in a given font.
//
//==========================================================================
int GetCharFromString(const uint8_t *&string)
FTexture * BuildTextTexture(FFont *font, const char *string, int textcolor)
{
int z, y, x;
int w;
const uint8_t *ch;
int cx;
int cy;
FRemapTable *range;
int kerning;
FTexture *pic;
z = *string++;
kerning = font->GetDefaultKerning();
if (z < 192)
ch = (const uint8_t *)string;
cx = 0;
cy = 0;
IntRect box;
while (auto c = GetCharFromString(ch))
{
return z;
if (c == TEXTCOLOR_ESCAPE)
{
// Here we only want to measure the texture so just parse over the color.
V_ParseFontColor(ch, 0, 0);
continue;
}
if (c == '\n')
{
cx = 0;
cy += font->GetHeight();
continue;
}
if (nullptr != (pic = font->GetChar(c, CR_UNTRANSLATED, &w, nullptr)))
{
auto img = pic->GetImage();
auto offsets = img->GetOffsets();
int x = cx - offsets.first;
int y = cy - offsets.second;
int ww = img->GetWidth();
int h = img->GetHeight();
box.AddToRect(x, y);
box.AddToRect(x + ww, y + h);
}
cx += (w + kerning);
}
else if (z <= 223)
cx = -box.left;
cy = -box.top;
TArray<TexPart> part(strlen(string));
while (auto c = GetCharFromString(ch))
{
y = *string++;
if (y < 128 || y >= 192)
if (c == TEXTCOLOR_ESCAPE)
{
// not an UTF-8 sequence so return the first byte unchanged
string--;
}
else
{
z = (z - 192) * 64 + (y - 128);
}
}
else if (z >= 224 && z <= 239)
{
y = *string++;
if (y < 128 || y >= 192)
{
// not an UTF-8 sequence so return the first byte unchanged
string--;
}
else
{
x = *string++;
if (x < 128 || x >= 192)
EColorRange newcolor = V_ParseFontColor(ch, textcolor, textcolor);
if (newcolor != CR_UNDEFINED)
{
// not an UTF-8 sequence so return the first byte unchanged
string -= 2;
}
else
{
z = (z - 224) * 4096 + (y - 128) * 64 + (x - 128);
range = font->GetColorTranslation(newcolor);
textcolor = newcolor;
}
continue;
}
}
else if (z >= 240)
{
y = *string++;
if (y < 128 || y >= 192)
if (c == '\n')
{
// not an UTF-8 sequence so return the first byte unchanged
string--;
cx = 0;
cy += font->GetHeight();
continue;
}
else
if (nullptr != (pic = font->GetChar(c, textcolor, &w, nullptr)))
{
// we do not support 4-Byte UTF-8 here
string += 2;
return '?';
auto img = pic->GetImage();
auto offsets = img->GetOffsets();
int x = cx - offsets.first;
int y = cy - offsets.second;
auto &tp = part[part.Reserve(1)];
tp.OriginX = x;
tp.OriginY = y;
tp.Image = img;
tp.Translation = range;
}
cx += (w + kerning);
}
return z;
FMultiPatchTexture *image = new FMultiPatchTexture(box.width, box.height, part, false, false);
image->SetOffsets(-box.left, -box.top);
FImageTexture *tex = new FImageTexture(image, "");
tex->SetUseType(ETextureType::MiscPatch);
TexMan.AddTexture(tex);
return tex;
}
//==========================================================================
//
// DrawChar
@ -335,192 +374,3 @@ DEFINE_ACTION_FUNCTION(_Screen, DrawText)
return 0;
}
//==========================================================================
//
// Break long lines of text into multiple lines no longer than maxwidth pixels
//
//==========================================================================
static void breakit (FBrokenLines *line, FFont *font, const uint8_t *start, const uint8_t *stop, FString &linecolor)
{
if (!linecolor.IsEmpty())
{
line->Text = TEXTCOLOR_ESCAPE;
line->Text += linecolor;
}
line->Text.AppendCStrPart ((const char *)start, stop - start);
line->Width = font->StringWidth (line->Text);
}
TArray<FBrokenLines> V_BreakLines (FFont *font, int maxwidth, const uint8_t *string, bool preservecolor)
{
TArray<FBrokenLines> Lines(128);
const uint8_t *space = NULL, *start = string;
int c, w, nw;
FString lastcolor, linecolor;
bool lastWasSpace = false;
int kerning = font->GetDefaultKerning ();
w = 0;
while ( (c = GetCharFromString(string)) )
{
if (c == TEXTCOLOR_ESCAPE)
{
if (*string)
{
if (*string == '[')
{
const uint8_t *start = string;
while (*string != ']' && *string != '\0')
{
string++;
}
if (*string != '\0')
{
string++;
}
lastcolor = FString((const char *)start, string - start);
}
else
{
lastcolor = *string++;
}
}
continue;
}
if (iswspace(c))
{
if (!lastWasSpace)
{
space = string - 1;
lastWasSpace = true;
}
}
else
{
lastWasSpace = false;
}
nw = font->GetCharWidth (c);
if ((w > 0 && w + nw > maxwidth) || c == '\n')
{ // Time to break the line
if (!space)
space = string - 1;
auto index = Lines.Reserve(1);
breakit (&Lines[index], font, start, space, linecolor);
if (c == '\n' && !preservecolor)
{
lastcolor = ""; // Why, oh why, did I do it like this?
}
linecolor = lastcolor;
w = 0;
lastWasSpace = false;
start = space;
space = NULL;
while (*start && iswspace (*start) && *start != '\n')
start++;
if (*start == '\n')
start++;
else
while (*start && iswspace (*start))
start++;
string = start;
}
else
{
w += nw + kerning;
}
}
// String here is pointing one character after the '\0'
if (--string - start >= 1)
{
const uint8_t *s = start;
while (s < string)
{
// If there is any non-white space in the remainder of the string, add it.
if (!iswspace (*s++))
{
auto i = Lines.Reserve(1);
breakit (&Lines[i], font, start, string, linecolor);
break;
}
}
}
return Lines;
}
FSerializer &Serialize(FSerializer &arc, const char *key, FBrokenLines& g, FBrokenLines *def)
{
if (arc.BeginObject(key))
{
arc("text", g.Text)
("width", g.Width)
.EndObject();
}
return arc;
}
class DBrokenLines : public DObject
{
DECLARE_CLASS(DBrokenLines, DObject)
public:
TArray<FBrokenLines> mBroken;
DBrokenLines() = default;
DBrokenLines(TArray<FBrokenLines> &broken)
{
mBroken = std::move(broken);
}
void Serialize(FSerializer &arc) override
{
arc("lines", mBroken);
}
};
IMPLEMENT_CLASS(DBrokenLines, false, false);
DEFINE_ACTION_FUNCTION(DBrokenLines, Count)
{
PARAM_SELF_PROLOGUE(DBrokenLines);
ACTION_RETURN_INT(self->mBroken.Size());
}
DEFINE_ACTION_FUNCTION(DBrokenLines, StringWidth)
{
PARAM_SELF_PROLOGUE(DBrokenLines);
PARAM_INT(index);
ACTION_RETURN_INT((unsigned)index >= self->mBroken.Size()? -1 : self->mBroken[index].Width);
}
DEFINE_ACTION_FUNCTION(DBrokenLines, StringAt)
{
PARAM_SELF_PROLOGUE(DBrokenLines);
PARAM_INT(index);
ACTION_RETURN_STRING((unsigned)index >= self->mBroken.Size() ? -1 : self->mBroken[index].Text);
}
DEFINE_ACTION_FUNCTION(FFont, BreakLines)
{
PARAM_SELF_STRUCT_PROLOGUE(FFont);
PARAM_STRING(text);
PARAM_INT(maxwidth);
auto broken = V_BreakLines(self, maxwidth, text, true);
ACTION_RETURN_OBJECT(Create<DBrokenLines>(broken));
}

View file

@ -117,7 +117,7 @@ public:
tex.Filter = filter;
tex.Wrap = wrap;
tex.Type = type;
tex.Texture = {};
tex.Texture = "";
}
void SetOutputTexture(PPTextureName texture)
@ -129,19 +129,19 @@ public:
void SetOutputCurrent()
{
Output.Type = PPTextureType::CurrentPipelineTexture;
Output.Texture = {};
Output.Texture = "";
}
void SetOutputNext()
{
Output.Type = PPTextureType::NextPipelineTexture;
Output.Texture = {};
Output.Texture = "";
}
void SetOutputSceneColor()
{
Output.Type = PPTextureType::SceneColor;
Output.Texture = {};
Output.Texture = "";
}
void SetNoBlend()

View file

@ -68,3 +68,7 @@ EXTERN_CVAR(Int, gl_enhanced_nv_stealth)
EXTERN_CVAR(Int, gl_fuzztype)
EXTERN_CVAR(Int, gl_shadowmap_filter)
EXTERN_CVAR(Bool, gl_brightfog)
EXTERN_CVAR(Bool, gl_lightadditivesurfaces)
EXTERN_CVAR(Bool, gl_notexturefill)

View file

@ -49,10 +49,10 @@
#include "doomstat.h"
#include "g_levellocals.h"
#include "v_video.h"
#include "utf8.h"
extern FRandom pr_exrandom;
FMemArena FxAlloc(65536);
int utf8_decode(const char *src, int *size);
struct FLOP
{
@ -318,19 +318,13 @@ static FxExpression *StringConstToChar(FxExpression *basex)
// This serves as workaround for not being able to use single quoted literals because those are taken for names.
ExpVal constval = static_cast<FxConstant *>(basex)->GetValue();
FString str = constval.GetString();
if (str.Len() == 1)
int position = 0;
int chr = str.GetNextCharacter(position);
// Only succeed if the full string is consumed, i.e. it contains only one code point.
if (position == (int)str.Len())
{
return new FxConstant(str[0], basex->ScriptPosition);
}
else if (str.Len() > 1)
{
// If the string is UTF-8, allow a single character UTF-8 sequence.
int size;
int c = utf8_decode(str.GetChars(), &size);
if (c >= 0 && size_t(size) == str.Len())
{
return new FxConstant(c, basex->ScriptPosition);
}
return new FxConstant(chr, basex->ScriptPosition);
}
return nullptr;
}
@ -8864,10 +8858,17 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
CallingFunction = ctx.Function;
if (ArgList.Size() > 0)
{
if (argtypes.Size() == 0)
{
ScriptPosition.Message(MSG_ERROR, "Too many arguments in call to %s", Function->SymbolName.GetChars());
delete this;
return nullptr;
}
bool foundvarargs = false;
PType * type = nullptr;
int flag = 0;
if (argtypes.Last() != nullptr && ArgList.Size() + implicit > argtypes.Size())
if (argtypes.Size() > 0 && argtypes.Last() != nullptr && ArgList.Size() + implicit > argtypes.Size())
{
ScriptPosition.Message(MSG_ERROR, "Too many arguments in call to %s", Function->SymbolName.GetChars());
delete this;

View file

@ -987,10 +987,11 @@ FString FStringFormat(VM_ARGS, int offset)
ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
FString argnumstr = fmt_current.Mid(1);
if (!argnumstr.IsInt()) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for argument number, got '%s'.", argnumstr.GetChars());
argnum = argnumstr.ToLong();
if (argnum < 1 || argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum, numparam);
auto argnum64 = argnumstr.ToLong();
if (argnum64 < 1 || argnum64 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum64, numparam);
fmt_current = "%";
haveargnums = true;
argnum = int(argnum64);
}
else
{
@ -1157,3 +1158,17 @@ DEFINE_ACTION_FUNCTION(FStringStruct, AppendFormat)
return 0;
}
DEFINE_ACTION_FUNCTION(FStringStruct, AppendCharacter)
{
PARAM_SELF_STRUCT_PROLOGUE(FString);
PARAM_INT(c);
self->AppendCharacter(c);
return 0;
}
DEFINE_ACTION_FUNCTION(FStringStruct, DeleteLastCharacter)
{
PARAM_SELF_STRUCT_PROLOGUE(FString);
self->DeleteLastCharacter();
return 0;
}

View file

@ -44,7 +44,7 @@ static void CastF2S(FString *a, double b) { a->Format("%.5f", b); }
static void CastV22S(FString *a, double b, double b1) { a->Format("(%.5f, %.5f)", b, b1); }
static void CastV32S(FString *a, double b, double b1, double b2) { a->Format("(%.5f, %.5f, %.5f)", b, b1, b2); }
static void CastP2S(FString *a, void *b) { if (b == nullptr) *a = "null"; else a->Format("%p", b); }
static int CastS2I(FString *b) { return (VM_SWORD)b->ToLong(); }
static int CastS2I(FString *b) { return (int)b->ToLong(); }
static double CastS2F(FString *b) { return b->ToDouble(); }
static int CastS2N(FString *b) { return b->Len() == 0 ? FName(NAME_None) : FName(*b); }
static void CastN2S(FString *a, int b) { FName name = FName(ENamedName(b)); *a = name.IsValidName() ? name.GetChars() : ""; }

View file

@ -369,7 +369,7 @@ struct VMValue
}
if (Type == REGT_STRING)
{
return s().ToLong();
return (int)s().ToLong();
}
// FIXME
return 0;

View file

@ -49,6 +49,8 @@
#include "i_music.h"
#include "am_map.h"
#include "v_video.h"
#include "gi.h"
#include "intermission/intermission.h"
DVector2 AM_GetPosition();
int Net_GetLatency(int *ld, int *ad);
@ -257,7 +259,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, ToInt, StringToInt)
{
PARAM_SELF_STRUCT_PROLOGUE(FString);
PARAM_INT(base);
ACTION_RETURN_INT(self->ToLong(base));
ACTION_RETURN_INT((int)self->ToLong(base));
}
static double StringToDbl(FString *self)
@ -1627,6 +1629,76 @@ DEFINE_ACTION_FUNCTION_NATIVE(_Sector, SetXOffset, SetXOffset)
//
//=====================================================================================
static int IsJumpingAllowed(FLevelLocals *self)
{
return self->IsJumpingAllowed();
}
DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, IsJumpingAllowed, IsJumpingAllowed)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
ACTION_RETURN_BOOL(self->IsJumpingAllowed());
}
//==========================================================================
//
//
//==========================================================================
static int IsCrouchingAllowed(FLevelLocals *self)
{
return self->IsCrouchingAllowed();
}
DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, IsCrouchingAllowed, IsCrouchingAllowed)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
ACTION_RETURN_BOOL(self->IsCrouchingAllowed());
}
//==========================================================================
//
//
//==========================================================================
static int IsFreelookAllowed(FLevelLocals *self)
{
return self->IsFreelookAllowed();
}
DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, IsFreelookAllowed, IsFreelookAllowed)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
ACTION_RETURN_BOOL(self->IsFreelookAllowed());
}
//==========================================================================
//
// ZScript counterpart to ACS ChangeSky, uses TextureIDs
//
//==========================================================================
DEFINE_ACTION_FUNCTION(FLevelLocals, ChangeSky)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
PARAM_INT(sky1);
PARAM_INT(sky2);
self->skytexture1 = FSetTextureID(sky1);
self->skytexture2 = FSetTextureID(sky2);
InitSkyMap(self);
return 0;
}
DEFINE_ACTION_FUNCTION(FLevelLocals, StartIntermission)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
PARAM_NAME(seq);
PARAM_INT(state);
F_StartIntermission(seq, (uint8_t)state);
return 0;
}
// This is needed to convert the strings to char pointers.
static void ReplaceTextures(FLevelLocals *self, const FString &from, const FString &to, int flags)
{

View file

@ -59,114 +59,31 @@
#include "v_text.h"
#include "cmdlib.h"
#include "g_levellocals.h"
#include "utf8.h"
char nulspace[1024 * 1024 * 4];
bool save_full = false; // for testing. Should be removed afterward.
int utf8_encode(int32_t codepoint, char *buffer, int *size)
{
if (codepoint < 0)
return -1;
else if (codepoint < 0x80)
{
buffer[0] = (char)codepoint;
*size = 1;
}
else if (codepoint < 0x800)
{
buffer[0] = 0xC0 + ((codepoint & 0x7C0) >> 6);
buffer[1] = 0x80 + ((codepoint & 0x03F));
*size = 2;
}
else if (codepoint < 0x10000)
{
buffer[0] = 0xE0 + ((codepoint & 0xF000) >> 12);
buffer[1] = 0x80 + ((codepoint & 0x0FC0) >> 6);
buffer[2] = 0x80 + ((codepoint & 0x003F));
*size = 3;
}
else if (codepoint <= 0x10FFFF)
{
buffer[0] = 0xF0 + ((codepoint & 0x1C0000) >> 18);
buffer[1] = 0x80 + ((codepoint & 0x03F000) >> 12);
buffer[2] = 0x80 + ((codepoint & 0x000FC0) >> 6);
buffer[3] = 0x80 + ((codepoint & 0x00003F));
*size = 4;
}
else
return -1;
return 0;
}
int utf8_decode(const char *src, int *size)
{
int c = src[0] & 255;
int r;
*size = 1;
if ((c & 0x80) == 0)
{
return c;
}
int c1 = src[1] & 255;
if ((c & 0xE0) == 0xC0)
{
r = ((c & 0x1F) << 6) | c1;
if (r >= 128)
{
*size = 2;
return r;
}
return -1;
}
int c2 = src[2] & 255;
if ((c & 0xF0) == 0xE0)
{
r = ((c & 0x0F) << 12) | (c1 << 6) | c2;
if (r >= 2048 && (r < 55296 || r > 57343))
{
*size = 3;
return r;
}
return -1;
}
int c3 = src[3] & 255;
if ((c & 0xF8) == 0xF0)
{
r = ((c & 0x07) << 18) | (c1 << 12) | (c2 << 6) | c3;
if (r >= 65536 && r <= 1114111)
{
*size = 4;
return r;
}
}
return -1;
}
//==========================================================================
//
// This will double-encode already existing UTF-8 content.
// The reason for this behavior is to preserve any original data coming through here, no matter what it is.
// If these are script-based strings, exact preservation in the serializer is very important.
//
//==========================================================================
static TArray<char> out;
static const char *StringToUnicode(const char *cc, int size = -1)
{
int ch;
const char *c = cc;
const uint8_t *c = (const uint8_t*)cc;
int count = 0;
int count1 = 0;
out.Clear();
while ((ch = (*c++) & 255))
{
count1++;
if (ch >= 128)
{
if (ch < 0x800) count += 2;
else count += 3;
// The source cannot contain 4-byte chars.
}
if (ch >= 128) count += 2;
else count++;
if (count1 == size && size > 0) break;
}
@ -174,11 +91,11 @@ static const char *StringToUnicode(const char *cc, int size = -1)
// we need to convert
out.Resize(count + 1);
out.Last() = 0;
c = cc;
c = (const uint8_t*)cc;
int i = 0;
while ((ch = (*c++) & 255))
while ((ch = (*c++)))
{
utf8_encode(ch, &out[i], &count1);
utf8_encode(ch, (uint8_t*)&out[i], &count1);
i += count1;
}
return &out[0];
@ -191,8 +108,8 @@ static const char *UnicodeToString(const char *cc)
while (*cc != 0)
{
int size;
int c = utf8_decode(cc, &size);
if (c < 0 || c > 255) c = '?';
int c = utf8_decode((const uint8_t*)cc, &size);
if (c < 0 || c > 255) c = '?'; // This should never happen because all content was encoded with StringToUnicode which only produces code points 0-255.
out[ndx++] = c;
cc += size;
}
@ -304,6 +221,13 @@ struct FWriter
else if (mWriter2) mWriter2->Null();
}
void StringU(const char *k, bool encode)
{
if (encode) k = StringToUnicode(k);
if (mWriter1) mWriter1->String(k);
else if (mWriter2) mWriter2->String(k);
}
void String(const char *k)
{
k = StringToUnicode(k);
@ -896,7 +820,7 @@ FSerializer &FSerializer::StringPtr(const char *key, const char *&charptr)
//==========================================================================
//
//
// Adds a string literal. This won't get double encoded, like a serialized string.
//
//==========================================================================
@ -905,11 +829,33 @@ FSerializer &FSerializer::AddString(const char *key, const char *charptr)
if (isWriting())
{
WriteKey(key);
w->String(charptr);
w->StringU(MakeUTF8(charptr), false);
}
return *this;
}
//==========================================================================
//
// Reads back a string without any processing.
//
//==========================================================================
const char *FSerializer::GetString(const char *key)
{
auto val = r->FindKey(key);
if (val != nullptr)
{
if (val->IsString())
{
return val->GetString();
}
else
{
}
}
return nullptr;
}
//==========================================================================
//
//

View file

@ -100,6 +100,7 @@ public:
FSerializer &Sprite(const char *key, int32_t &spritenum, int32_t *def);
FSerializer &StringPtr(const char *key, const char *&charptr); // This only retrieves the address but creates no permanent copy of the string unlike the regular char* serializer.
FSerializer &AddString(const char *key, const char *charptr);
const char *GetString(const char *key);
FSerializer &ScriptNum(const char *key, int &num);
bool isReading() const
{

View file

@ -1,4 +1,5 @@
#include <mutex>
#include "oplsynth/opl_mus_player.h"
#include "c_cvars.h"
#include "mus2midi.h"
@ -113,7 +114,7 @@ public:
bool Pause(bool paused);
protected:
FCriticalSection CritSec;
std::mutex CritSec;
SoundStream *Stream;
double Tempo;
double Division;

View file

@ -644,7 +644,7 @@ FString FluidSynthMIDIDevice::GetStats()
}
FString out;
CritSec.Enter();
std::lock_guard<std::mutex> lock(CritSec);
int polyphony = fluid_synth_get_polyphony(FluidSynth);
int voices = fluid_synth_get_active_voice_count(FluidSynth);
double load = fluid_synth_get_cpu_load(FluidSynth);
@ -652,7 +652,6 @@ FString FluidSynthMIDIDevice::GetStats()
fluid_settings_getint(FluidSettings, "synth.chorus.active", &chorus);
fluid_settings_getint(FluidSettings, "synth.reverb.active", &reverb);
fluid_settings_getint(FluidSettings, "synth.polyphony", &maxpoly);
CritSec.Leave();
out.Format("Voices: " TEXTCOLOR_YELLOW "%3d" TEXTCOLOR_NORMAL "/" TEXTCOLOR_ORANGE "%3d" TEXTCOLOR_NORMAL "(" TEXTCOLOR_RED "%3d" TEXTCOLOR_NORMAL ")"
TEXTCOLOR_YELLOW "%6.2f" TEXTCOLOR_NORMAL "%% CPU "

View file

@ -237,9 +237,8 @@ void SoftSynthMIDIDevice::Stop()
int SoftSynthMIDIDevice::StreamOutSync(MidiHeader *header)
{
CritSec.Enter();
std::lock_guard<std::mutex> lock(CritSec);
StreamOut(header);
CritSec.Leave();
return 0;
}
@ -392,7 +391,7 @@ bool SoftSynthMIDIDevice::ServiceStream (void *buff, int numbytes)
samples1 = samples;
memset(buff, 0, numbytes);
CritSec.Enter();
std::lock_guard<std::mutex> lock(CritSec);
while (Events != NULL && numsamples > 0)
{
double ticky = NextTickIn;
@ -434,7 +433,6 @@ bool SoftSynthMIDIDevice::ServiceStream (void *buff, int numbytes)
{
res = false;
}
CritSec.Leave();
return res;
}

View file

@ -167,7 +167,7 @@ FString TimidityMIDIDevice::GetStats()
FString out;
int i, used;
CritSec.Enter();
std::lock_guard<std::mutex> lock(CritSec);
for (i = used = 0; i < Renderer->voices; ++i)
{
int status = Renderer->voice[i].status;
@ -205,7 +205,7 @@ FString TimidityMIDIDevice::GetStats()
}
}
}
CritSec.Leave();
CritSec.unlock();
out.Format(TEXTCOLOR_YELLOW"%3d/%3d ", used, Renderer->voices);
out += dots;
if (Renderer->cut_notes | Renderer->lost_notes)

View file

@ -664,9 +664,9 @@ void CALLBACK WinMIDIDevice::CallbackFunc(HMIDIOUT hOut, UINT uMsg, DWORD_PTR dw
static bool IgnoreMIDIVolume(UINT id)
{
MIDIOUTCAPS caps;
MIDIOUTCAPSA caps;
if (MMSYSERR_NOERROR == midiOutGetDevCaps(id, &caps, sizeof(caps)))
if (MMSYSERR_NOERROR == midiOutGetDevCapsA(id, &caps, sizeof(caps)))
{
if (caps.wTechnology == MIDIDEV_MAPPER)
{

View file

@ -207,15 +207,18 @@ CCMD (snd_listmididevices)
{
for (id = 0; id < nummididevices; ++id)
{
FString text;
res = midiOutGetDevCaps (id, &caps, sizeof(caps));
if (res == MMSYSERR_NODRIVER)
strcpy (caps.szPname, "<Driver not installed>");
text = "<Driver not installed>";
else if (res == MMSYSERR_NOMEM)
strcpy (caps.szPname, "<No memory for description>");
else if (res != MMSYSERR_NOERROR)
text = "<No memory for description>";
else if (res == MMSYSERR_NOERROR)
text = caps.szPname;
else
continue;
PrintMidiDevice (id, caps.szPname, caps.wTechnology, caps.dwSupport);
PrintMidiDevice (id, text, caps.wTechnology, caps.dwSupport);
}
}
}

View file

@ -36,6 +36,7 @@
// HEADER FILES ------------------------------------------------------------
#include <math.h>
#include <mutex>
#include "i_musicinterns.h"
@ -73,7 +74,7 @@ protected:
size_t written;
DUH *duh;
DUH_SIGRENDERER *sr;
FCriticalSection crit_sec;
std::mutex crit_sec;
bool open2(long pos);
long render(double volume, double delta, long samples, sample_t **buffer);
@ -957,18 +958,16 @@ bool input_mod::read(SoundStream *stream, void *buffer, int sizebytes, void *use
memset(buffer, 0, sizebytes);
return false;
}
state->crit_sec.Enter();
std::lock_guard<std::mutex> lock(state->crit_sec);
while (sizebytes > 0)
{
int written = state->decode_run(buffer, sizebytes / 8);
if (written < 0)
{
state->crit_sec.Leave();
return false;
}
if (written == 0)
{
state->crit_sec.Leave();
memset(buffer, 0, sizebytes);
return true;
}
@ -983,7 +982,6 @@ bool input_mod::read(SoundStream *stream, void *buffer, int sizebytes, void *use
buffer = (uint8_t *)buffer + written * 8;
sizebytes -= written * 8;
}
state->crit_sec.Leave();
return true;
}
@ -1063,18 +1061,16 @@ bool input_mod::SetSubsong(int order)
{
return false;
}
crit_sec.Enter();
std::lock_guard<std::mutex> lock(crit_sec);
DUH_SIGRENDERER *oldsr = sr;
sr = NULL;
start_order = order;
if (!open2(0))
{
sr = oldsr;
crit_sec.Leave();
return false;
}
duh_end_sigrenderer(oldsr);
crit_sec.Leave();
return true;
}

Some files were not shown because too many files have changed in this diff Show more