mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-11-10 06:41:41 +00:00
Merge branch 'master' into vulkan2
This commit is contained in:
commit
413412f603
1198 changed files with 12011 additions and 8785 deletions
|
@ -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} )
|
||||
|
|
|
@ -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/.+")
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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--;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
120
src/ct_chat.cpp
120
src/ct_chat.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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("=");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -151,8 +151,6 @@ enum
|
|||
extern AActor *WP_NOCHANGE;
|
||||
|
||||
|
||||
#define MAXPLAYERNAME 15
|
||||
|
||||
// [GRB] Custom player classes
|
||||
enum
|
||||
{
|
||||
|
|
|
@ -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...?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
269
src/g_level.cpp
269
src/g_level.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
980
src/gamedata/fonts/font.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
44
src/gamedata/fonts/fontinternals.h
Normal file
44
src/gamedata/fonts/fontinternals.h
Normal 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);
|
653
src/gamedata/fonts/singlelumpfont.cpp
Normal file
653
src/gamedata/fonts/singlelumpfont.cpp
Normal 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);
|
||||
}
|
127
src/gamedata/fonts/singlepicfont.cpp
Normal file
127
src/gamedata/fonts/singlepicfont.cpp
Normal 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);
|
||||
}
|
221
src/gamedata/fonts/specialfont.cpp
Normal file
221
src/gamedata/fonts/specialfont.cpp
Normal 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);
|
||||
}
|
1499
src/gamedata/fonts/v_font.cpp
Normal file
1499
src/gamedata/fonts/v_font.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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__
|
237
src/gamedata/fonts/v_text.cpp
Normal file
237
src/gamedata/fonts/v_text.cpp
Normal 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));
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -113,6 +113,12 @@ public:
|
|||
{
|
||||
return std::make_pair(LeftOffset, TopOffset);
|
||||
}
|
||||
|
||||
void SetOffsets(int x, int y)
|
||||
{
|
||||
LeftOffset = x;
|
||||
TopOffset = y;
|
||||
}
|
||||
|
||||
int LumpNum() const
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -207,7 +207,7 @@ class DIntermissionScreenText : public DIntermissionScreen
|
|||
{
|
||||
DECLARE_CLASS (DIntermissionScreenText, DIntermissionScreen)
|
||||
|
||||
const char *mText;
|
||||
FString mText;
|
||||
int mTextSpeed;
|
||||
int mTextX, mTextY;
|
||||
int mTextCounter;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
|
@ -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__
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -667,7 +667,7 @@ void MapLoader::FloodSectorStacks()
|
|||
for (auto section : HandledSections)
|
||||
{
|
||||
if (section->sector != §or)
|
||||
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 != §or)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
560
src/maploader/strifedialogue.cpp
Normal file
560
src/maploader/strifedialogue.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
|
@ -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";
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
178
src/p_saveg.cpp
178
src/p_saveg.cpp
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() : ""; }
|
||||
|
|
|
@ -369,7 +369,7 @@ struct VMValue
|
|||
}
|
||||
if (Type == REGT_STRING)
|
||||
{
|
||||
return s().ToLong();
|
||||
return (int)s().ToLong();
|
||||
}
|
||||
// FIXME
|
||||
return 0;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue