Compare commits

...

44 commits

Author SHA1 Message Date
Peppersawce
cb322b563d Haiku patch for Raze 2025-04-05 20:48:53 -04:00
Christoph Oelckers
940e53af6f avoid type punning in TVector XY() and XYZ() methods. 2025-04-03 08:38:16 +02:00
Christoph Oelckers
7e1fe48ff2 RR: fix placement of time display on alt. HUD. 2025-03-30 10:54:49 +02:00
Rachael Alexanderson
2aa1e22002
1.12pre 2025-02-22 12:38:13 -05:00
Rachael Alexanderson
3f55154b64
- language update 2025-02-22 12:03:27 -05:00
Rachael Alexanderson
41c1a62b82
- gzdoom no longer uses vcpkg, raze should follow suit 2025-02-16 01:38:55 -05:00
Rachael Alexanderson
19f986cfe1
- language update 2025-02-16 00:30:09 -05:00
Christoph Oelckers
1f0f5a413a copy vid_dontdowait from GZDoom 2025-02-01 13:26:48 +01:00
Christoph Oelckers
3590de2f2a backend update from GZDoom. 2025-01-20 15:13:04 +01:00
Christoph Oelckers
b0e8aae59f Revert "Backend update from GZDoom."
Undoing the file system refactor.
2025-01-20 15:13:04 +01:00
Rachael Alexanderson
e3a0ec65f7
- move appimage workflow to an unused folder 2024-12-27 09:53:57 -05:00
Rachael Alexanderson
d1ab23d321
- language update 2024-12-17 14:25:45 -05:00
Christoph Oelckers
5eaa53dcc9 Backend update from GZDoom.
Mainly filesystem cleanup.
2024-11-24 17:22:47 +01:00
Christoph Oelckers
51ebaf9296 fixed use of wrong file lookup function in map patching code. 2024-11-23 21:19:40 +01:00
Christoph Oelckers
0fa0e3da23 Blood: fix placement of powerup counters on alt HUD 2024-10-27 07:23:32 +01:00
Christoph Oelckers
c6f1990495 SW: fix shadow size math. 2024-10-20 08:37:22 +02:00
Rachael Alexanderson
e48911dcc9
- language update 2024-10-12 07:07:02 -04:00
Avinash Reddy
3cb173ac6f (2/2) [shadow warrior] widescreen menu background
updated def file
2024-10-12 10:14:25 +02:00
Avinash Reddy
259d739209 (1/2) [shadow warrior] widescreen menu background
upload tile image
2024-10-12 10:14:25 +02:00
Christoph Oelckers
0a80f70ace SW: fix sector object setup.
When moving the SO out of the way to get the sector, this only works for rotatable SO's. For non-rotating ones the internally used functions never did what was expected, letting the SO end up in invalid space.
To do this properly the SOBJ_DONT_ROTATE flag must be cleared temporarily.
2024-10-11 11:50:56 +02:00
Rachael Alexanderson
8bc21963e6
- don't spam the console log if there's math errors - mostly affects Linux/MacOS because there is no distinction from console apps or GUI apps so they put all the data in the console which can cause lag 2024-10-10 07:29:53 -04:00
alexey.lysiuk
0347d0d9af - updated github actions workflow
* fix macos jobs
* fix warnings
2024-10-10 13:29:46 +03:00
alexey.lysiuk
0374bbc4e3 - fixed compilation of linux build
source/common/platform/posix/sdl/i_system.cpp:311:79: error: non-const lvalue reference to type 'FString' cannot bind to a temporary of type 'FString *'
2024-10-10 13:29:24 +03:00
Christoph Oelckers
db38028dda SW: removed several unused sound functions. 2024-10-10 09:18:10 +02:00
Christoph Oelckers
5782206a91 Validate the file reader in DoOpenResourceFile. 2024-10-10 08:46:40 +02:00
Rachael Alexanderson
e6fa87cd22
- ci: update zmusic 2024-10-09 11:08:44 -04:00
Christoph Oelckers
b3a2523099 Backend update from GZDoom. 2024-10-09 15:28:22 +02:00
Christoph Oelckers
27dc590931 avoid floats in integer math. 2024-10-09 14:07:53 +02:00
Christoph Oelckers
1dedb4319a Duke: add range checks for accessing the moves array
CON can easily produce bad indices due to sloppy design.
In ZScript always throw an exception if such an index is out of bounds.
2024-10-09 14:07:02 +02:00
Christoph Oelckers
48d4545067 account for 'amess96.voc' being renamed in the World Tour re-recordings. 2024-10-09 12:10:56 +02:00
Christoph Oelckers
053a6875bf SW: fixed use of sound attribute array.
This was caused by incompletely reverting a code change.
2024-10-09 12:00:01 +02:00
Christoph Oelckers
f9e4ce89fa Blood: fixed messed up math in podAttack. 2024-10-09 09:56:38 +02:00
Christoph Oelckers
f5d08416c6 Duke: fixed animated static monitors. 2024-10-09 07:34:44 +02:00
Christoph Oelckers
97a71bad5d Duke: lower the coordinate precision for calculating the sector's center for ST09 to original Build.
This is needed so that some maps with not quite correct setups still work. Found in Roch3.
2024-10-08 13:30:13 +02:00
Rachael Alexanderson
f3cad8426c
- duplicate effects of gzdoom commit d9f03863bf 2024-05-07 08:38:27 -04:00
Rachael Alexanderson
73c4c46d23
- mirror changes with ZWidget, remove SDL_Wait failure message 2024-04-28 03:35:26 -04:00
Rachael Alexanderson
5414cae7c6
- demote SDL_Wait errors to console printouts 2024-04-24 07:25:58 -04:00
Rachael Alexanderson
54b274ab07
- hide additional parameters entry for now 2024-04-21 09:14:26 -04:00
Christoph Oelckers
d8bd9189f8
optimized last commit to not retrieve the function repeatedly if it has already failed. 2024-04-21 08:39:18 -04:00
Rachael Alexanderson
0da6e76a53
- dynamically import GetDpiForWindow from USER32.dll, else return a default value 2024-04-21 08:39:12 -04:00
Christoph Oelckers
3c2d0ede8f
ZWidgets: disabled SetClientFrame.
On Win32 this function is unused, but it contains two API calls that only exist in Windows 10 or later.
2024-04-21 08:37:13 -04:00
Christoph Oelckers
92baaafe33
ZWidgets: disabled SetClientFrame.
On Win32 this function is unused, but it contains two API calls that only exist in Windows 10 or later.
2024-04-21 06:28:14 -04:00
Rachael Alexanderson
df3b193250
- create stubs required for https://github.com/ZDoom/gzdoom/pull/2514 2024-04-20 19:14:04 -04:00
Rachael Alexanderson
accab2aa6a
1.11pre 2024-04-20 04:51:35 -04:00
186 changed files with 4542 additions and 3050 deletions

View file

@ -12,28 +12,25 @@ jobs:
config: config:
- name: Visual Studio 2022 - name: Visual Studio 2022
os: windows-2022 os: windows-2022
extra_options: -DCMAKE_TOOLCHAIN_FILE=build/vcpkg/scripts/buildsystems/vcpkg.cmake
build_type: Release build_type: Release
- name: Visual Studio 2022 - name: Visual Studio 2022
os: windows-2022 os: windows-2022
extra_options: -DCMAKE_TOOLCHAIN_FILE=build/vcpkg/scripts/buildsystems/vcpkg.cmake
build_type: Debug build_type: Debug
- name: Visual Studio 2019 - name: Visual Studio 2019
os: windows-2019 os: windows-2019
extra_options: -DCMAKE_TOOLCHAIN_FILE=build/vcpkg/scripts/buildsystems/vcpkg.cmake
build_type: Release build_type: Release
- name: macOS - name: macOS
os: macos-12 os: macos-14
deps_cmdline: brew install libvpx webp deps_cmdline: brew install libvpx
build_type: Release build_type: Release
- name: macOS - name: macOS
os: macos-12 os: macos-14
extra_options: -G Xcode -DDYN_OPENAL=OFF extra_options: -G Xcode -DDYN_OPENAL=OFF
deps_cmdline: brew install libvpx webp deps_cmdline: brew install libvpx
build_type: Debug build_type: Debug
- name: Linux GCC 9 - name: Linux GCC 9
@ -71,9 +68,9 @@ jobs:
fi fi
mkdir build mkdir build
if [[ "${{ runner.os }}" == 'macOS' ]]; then if [[ "${{ runner.os }}" == 'macOS' ]]; then
export ZMUSIC_PACKAGE=zmusic-1.1.9-macos.tar.xz export ZMUSIC_PACKAGE=zmusic-1.1.14-macos-arm.tar.xz
elif [[ "${{ runner.os }}" == 'Linux' ]]; then elif [[ "${{ runner.os }}" == 'Linux' ]]; then
export ZMUSIC_PACKAGE=zmusic-1.1.9-linux.tar.xz export ZMUSIC_PACKAGE=zmusic-1.1.14-linux.tar.xz
fi fi
if [[ -n "${ZMUSIC_PACKAGE}" ]]; then if [[ -n "${ZMUSIC_PACKAGE}" ]]; then
cd build cd build
@ -81,13 +78,6 @@ jobs:
tar -xf "${ZMUSIC_PACKAGE}" tar -xf "${ZMUSIC_PACKAGE}"
fi fi
- name: Setup vcpkg
uses: lukka/run-vcpkg@v11
if: runner.os == 'Windows'
with:
vcpkgDirectory: '${{ github.workspace }}/build/vcpkg'
vcpkgGitCommitId: '2c401863dd54a640aeb26ed736c55489c079323b'
- name: Configure - name: Configure
shell: bash shell: bash
run: | run: |
@ -115,7 +105,7 @@ jobs:
- name: Upload Package - name: Upload Package
if: runner.os == 'Windows' # Remove to store packages of all targets if: runner.os == 'Windows' # Remove to store packages of all targets
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
path: build/package path: build/package
name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} name: ${{ matrix.config.name }} ${{ matrix.config.build_type }}

View file

@ -321,7 +321,7 @@ else()
if ( UNIX ) if ( UNIX )
include(CheckSymbolExists) include(CheckSymbolExists)
check_symbol_exists( "fts_set" "fts.h" HAVE_FTS ) check_symbol_exists( "fts_set" "sys/types.h;sys/stat.h;fts.h" HAVE_FTS )
if ( NOT HAVE_FTS ) if ( NOT HAVE_FTS )
include ( FindPkgConfig ) include ( FindPkgConfig )
pkg_check_modules( MUSL_FTS musl-fts ) pkg_check_modules( MUSL_FTS musl-fts )
@ -343,7 +343,7 @@ else()
# If we're compiling with a custom GCC on the Mac (which we know since g++-4.2 doesn't support C++11) statically link libgcc. # If we're compiling with a custom GCC on the Mac (which we know since g++-4.2 doesn't support C++11) statically link libgcc.
set( ALL_C_FLAGS "-static-libgcc" ) set( ALL_C_FLAGS "-static-libgcc" )
endif() endif()
elseif( NOT MINGW ) elseif( NOT MINGW AND NOT HAIKU )
# Generic GCC/Clang requires position independent executable to be enabled explicitly # Generic GCC/Clang requires position independent executable to be enabled explicitly
set( ALL_C_FLAGS "${ALL_C_FLAGS} -fPIE" ) set( ALL_C_FLAGS "${ALL_C_FLAGS} -fPIE" )
set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie" ) set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie" )

View file

@ -148,9 +148,10 @@ typedef enum EIntConfigKey_
zmusic_mod_autochip_scan_threshold, zmusic_mod_autochip_scan_threshold,
zmusic_snd_streambuffersize, zmusic_snd_streambuffersize,
zmusic_snd_mididevice, zmusic_snd_mididevice,
zmusic_snd_outputrate, zmusic_snd_outputrate,
zmusic_mod_preferredplayer,
NUM_ZMUSIC_INT_CONFIGS NUM_ZMUSIC_INT_CONFIGS
} EIntConfigKey; } EIntConfigKey;
@ -226,7 +227,7 @@ typedef struct ZMusicCallbacks_
{ {
// Callbacks the client can install to capture messages from the backends // Callbacks the client can install to capture messages from the backends
// or to provide sound font data. // or to provide sound font data.
void (*MessageFunc)(int severity, const char* msg); void (*MessageFunc)(int severity, const char* msg);
// The message callbacks are optional, without them the output goes to stdout. // The message callbacks are optional, without them the output goes to stdout.

View file

@ -188,7 +188,11 @@ if(WIN32)
add_definitions(-DUNICODE -D_UNICODE) add_definitions(-DUNICODE -D_UNICODE)
else() else()
set(ZVULKAN_SOURCES ${ZVULKAN_SOURCES} ${ZVULKAN_UNIX_SOURCES}) set(ZVULKAN_SOURCES ${ZVULKAN_SOURCES} ${ZVULKAN_UNIX_SOURCES})
set(ZVULKAN_LIBS ${CMAKE_DL_LIBS} -ldl) if(NOT HAIKU)
set(ZVULKAN_LIBS ${CMAKE_DL_LIBS} -ldl)
else()
set(ZVULKAN_LIBS ${CMAKE_DL_LIBS})
endif()
add_definitions(-DUNIX -D_UNIX) add_definitions(-DUNIX -D_UNIX)
add_link_options(-pthread) add_link_options(-pthread)
endif() endif()

View file

@ -41,6 +41,7 @@
#include <cassert> #include <cassert>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstdint>
#include <list> #include <list>
#include <map> #include <map>
#include <set> #include <set>

View file

@ -120,6 +120,9 @@ include_directories(include include/zwidget src)
if(WIN32) if(WIN32)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_WIN32_SOURCES}) set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_WIN32_SOURCES})
add_definitions(-DUNICODE -D_UNICODE) add_definitions(-DUNICODE -D_UNICODE)
if(MINGW)
add_definitions(-DMINGW)
endif()
elseif(APPLE) elseif(APPLE)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_COCOA_SOURCES}) set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_COCOA_SOURCES})
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl) set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl)
@ -127,7 +130,11 @@ elseif(APPLE)
add_link_options(-pthread) add_link_options(-pthread)
else() else()
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES}) set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES})
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl) if(NOT HAIKU)
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl)
else()
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS})
endif()
add_definitions(-DUNIX -D_UNIX) add_definitions(-DUNIX -D_UNIX)
add_link_options(-pthread) add_link_options(-pthread)
endif() endif()

View file

@ -1,4 +1,4 @@
#include <algorithm>
#include "widgets/lineedit/lineedit.h" #include "widgets/lineedit/lineedit.h"
#include "core/utf8reader.h" #include "core/utf8reader.h"
#include "core/colorf.h" #include "core/colorf.h"
@ -18,6 +18,8 @@ LineEdit::LineEdit(Widget* parent) : Widget(parent)
LineEdit::~LineEdit() LineEdit::~LineEdit()
{ {
delete timer;
delete scroll_timer;
} }
bool LineEdit::IsReadOnly() const bool LineEdit::IsReadOnly() const

View file

@ -288,9 +288,8 @@ void SDL2DisplayWindow::RunLoop()
{ {
SDL_Event event; SDL_Event event;
int result = SDL_WaitEvent(&event); int result = SDL_WaitEvent(&event);
if (result == 0) if (result == 1)
throw std::runtime_error(std::string("SDL_WaitEvent failed:") + SDL_GetError()); DispatchEvent(event);
DispatchEvent(event);
} }
} }

View file

@ -28,6 +28,26 @@
#define RIDEV_INPUTSINK (0x100) #define RIDEV_INPUTSINK (0x100)
#endif #endif
#ifdef MINGW
// MinGW's library doesn't contain a thunk for DwmDefWindowProc, so we need to create our own
BOOL DwmDefWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *plResult )
{
typedef BOOL(* dwmdwp)(HWND, UINT, WPARAM, LPARAM, LRESULT* );
BOOL result(FALSE);
HMODULE module = LoadLibrary( _T( "dwmapi.dll" ) );
if( module ) {
dwmdwp proc = reinterpret_cast<dwmdwp>( GetProcAddress( module, "DwmDefWindowProc" ) );
if( proc ) {
result = proc( hWnd, msg, wParam, lParam, plResult );
}
FreeLibrary(module);
}
return result;
}
#endif
static std::string from_utf16(const std::wstring& str) static std::string from_utf16(const std::wstring& str)
{ {
if (str.empty()) return {}; if (str.empty()) return {};
@ -61,7 +81,7 @@ Win32Window::Win32Window(DisplayWindowHost* windowHost) : WindowHost(windowHost)
Windows.push_front(this); Windows.push_front(this);
WindowsIterator = Windows.begin(); WindowsIterator = Windows.begin();
WNDCLASSEX classdesc = {}; WNDCLASSEXW classdesc = {};
classdesc.cbSize = sizeof(WNDCLASSEX); classdesc.cbSize = sizeof(WNDCLASSEX);
classdesc.hInstance = GetModuleHandle(0); classdesc.hInstance = GetModuleHandle(0);
classdesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS; classdesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
@ -74,7 +94,7 @@ Win32Window::Win32Window(DisplayWindowHost* windowHost) : WindowHost(windowHost)
// WS_CAPTION shows the caption (yay! actually a flag that does what it says it does!) // WS_CAPTION shows the caption (yay! actually a flag that does what it says it does!)
// WS_SYSMENU shows the min/max/close buttons // WS_SYSMENU shows the min/max/close buttons
// WS_THICKFRAME makes the window resizable // WS_THICKFRAME makes the window resizable
CreateWindowEx(WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME, L"ZWidgetWindow", L"", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, 0, 0, 100, 100, 0, 0, GetModuleHandle(0), this); CreateWindowExW(WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME, L"ZWidgetWindow", L"", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, 0, 0, 100, 100, 0, 0, GetModuleHandle(0), this);
/* /*
RAWINPUTDEVICE rid; RAWINPUTDEVICE rid;
@ -128,6 +148,8 @@ void Win32Window::SetWindowFrame(const Rect& box)
void Win32Window::SetClientFrame(const Rect& box) void Win32Window::SetClientFrame(const Rect& box)
{ {
// This function is currently unused but needs to be disabled because it contains Windows API calls that were only added in Windows 10.
#if 0
double dpiscale = GetDpiScale(); double dpiscale = GetDpiScale();
RECT rect = {}; RECT rect = {};
@ -141,6 +163,7 @@ void Win32Window::SetClientFrame(const Rect& box)
AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle)); AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle));
SetWindowPos(WindowHandle, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER); SetWindowPos(WindowHandle, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
#endif
} }
void Win32Window::Show() void Win32Window::Show()
@ -268,9 +291,22 @@ int Win32Window::GetPixelHeight() const
return box.bottom; return box.bottom;
} }
typedef UINT(WINAPI* GetDpiForWindow_t)(HWND);
double Win32Window::GetDpiScale() const double Win32Window::GetDpiScale() const
{ {
return GetDpiForWindow(WindowHandle) / 96.0; static GetDpiForWindow_t pGetDpiForWindow = nullptr;
static bool done = false;
if (!done)
{
HMODULE hMod = GetModuleHandleA("User32.dll");
if (hMod != nullptr) pGetDpiForWindow = reinterpret_cast<GetDpiForWindow_t>(GetProcAddress(hMod, "GetDpiForWindow"));
done = true;
}
if (pGetDpiForWindow)
return pGetDpiForWindow(WindowHandle) / 96.0;
else
return 1.0;
} }
std::string Win32Window::GetClipboardText() std::string Win32Window::GetClipboardText()
@ -371,6 +407,7 @@ void Win32Window::PresentBitmap(int width, int height, const uint32_t* pixels)
LRESULT Win32Window::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam) LRESULT Win32Window::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam)
{ {
LPARAM result = 0; LPARAM result = 0;
if (DwmDefWindowProc(WindowHandle, msg, wparam, lparam, &result)) if (DwmDefWindowProc(WindowHandle, msg, wparam, lparam, &result))
return result; return result;

View file

@ -2,7 +2,7 @@
#include "window/window.h" #include "window/window.h"
#include <stdexcept> #include <stdexcept>
#ifdef WIN32 #ifdef _WIN32
#include "win32/win32window.h" #include "win32/win32window.h"

View file

@ -13,4 +13,5 @@ add_library( bz2 STATIC
decompress.c decompress.c
huffman.c huffman.c
randtable.c ) randtable.c )
link_libraries("-static")
target_link_libraries( bz2 ) target_link_libraries( bz2 )

View file

@ -1067,6 +1067,7 @@ set (PCH_SOURCES
common/textures/multipatchtexturebuilder.cpp common/textures/multipatchtexturebuilder.cpp
common/textures/skyboxtexture.cpp common/textures/skyboxtexture.cpp
common/textures/animtexture.cpp common/textures/animtexture.cpp
common/textures/firetexture.cpp
common/textures/v_collection.cpp common/textures/v_collection.cpp
common/textures/formats/automaptexture.cpp common/textures/formats/automaptexture.cpp
common/textures/formats/brightmaptexture.cpp common/textures/formats/brightmaptexture.cpp

View file

@ -592,6 +592,10 @@ void DShape2D::OnDestroy() {
void F2DDrawer::AddShape(FGameTexture* img, DShape2D* shape, DrawParms& parms) void F2DDrawer::AddShape(FGameTexture* img, DShape2D* shape, DrawParms& parms)
{ {
// bail if shape is null (shouldn't happen but it might)
if (!shape)
ThrowAbortException(X_OTHER, "shape is null");
// [MK] bail out if vertex/coord array sizes are mismatched // [MK] bail out if vertex/coord array sizes are mismatched
if ( shape->mVertices.Size() != shape->mCoords.Size() ) if ( shape->mVertices.Size() != shape->mCoords.Size() )
ThrowAbortException(X_OTHER, "Mismatch in vertex/coord count: %u != %u", shape->mVertices.Size(), shape->mCoords.Size()); ThrowAbortException(X_OTHER, "Mismatch in vertex/coord count: %u != %u", shape->mVertices.Size(), shape->mCoords.Size());

View file

@ -380,7 +380,7 @@ void DrawText(F2DDrawer *drawer, FFont* font, int normalcolor, double x, double
{ {
return; return;
} }
const char *txt = (parms.localize && string[0] == '$') ? GStrings(&string[1]) : string; const char *txt = (parms.localize && string[0] == '$') ? GStrings.GetString(&string[1]) : string;
DrawTextCommon(drawer, font, normalcolor, x, y, (const uint8_t*)string, parms); DrawTextCommon(drawer, font, normalcolor, x, y, (const uint8_t*)string, parms);
} }
@ -419,7 +419,7 @@ void DrawText(F2DDrawer *drawer, FFont *font, int normalcolor, double x, double
{ {
return; return;
} }
const char *txt = (parms.localize && string[0] == '$') ? GStrings(&string[1]) : string.GetChars(); const char *txt = (parms.localize && string[0] == '$') ? GStrings.GetString(&string[1]) : string.GetChars();
DrawTextCommon(drawer, font, normalcolor, x, y, (uint8_t*)txt, parms); DrawTextCommon(drawer, font, normalcolor, x, y, (uint8_t*)txt, parms);
} }

View file

@ -43,6 +43,8 @@
#include "s_soundinternal.h" #include "s_soundinternal.h"
#include "i_time.h" #include "i_time.h"
EXTERN_CVAR(Bool, cl_capfps)
class FBurnTexture : public FTexture class FBurnTexture : public FTexture
{ {
TArray<uint32_t> WorkBuffer; TArray<uint32_t> WorkBuffer;
@ -163,6 +165,8 @@ protected:
public: public:
virtual ~Wiper(); virtual ~Wiper();
virtual bool Run(int ticks) = 0; virtual bool Run(int ticks) = 0;
virtual bool RunInterpolated(double ticks) { return true; };
virtual bool Interpolatable() { return false; }
virtual void SetTextures(FGameTexture* startscreen, FGameTexture* endscreen) virtual void SetTextures(FGameTexture* startscreen, FGameTexture* endscreen)
{ {
startScreen = startscreen; startScreen = startscreen;
@ -177,9 +181,11 @@ class Wiper_Crossfade : public Wiper
{ {
public: public:
bool Run(int ticks) override; bool Run(int ticks) override;
bool RunInterpolated(double ticks) override;
bool Interpolatable() override { return true; }
private: private:
int Clock = 0; float Clock = 0;
}; };
class Wiper_Melt : public Wiper class Wiper_Melt : public Wiper
@ -187,10 +193,12 @@ class Wiper_Melt : public Wiper
public: public:
Wiper_Melt(); Wiper_Melt();
bool Run(int ticks) override; bool Run(int ticks) override;
bool RunInterpolated(double ticks) override;
bool Interpolatable() override { return true; }
private: private:
enum { WIDTH = 320, HEIGHT = 200 }; enum { WIDTH = 320, HEIGHT = 200 };
int y[WIDTH]; double y[WIDTH];
}; };
class Wiper_Burn : public Wiper class Wiper_Burn : public Wiper
@ -268,7 +276,23 @@ bool Wiper_Crossfade::Run(int ticks)
Clock += ticks; Clock += ticks;
DrawTexture(twod, startScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE); DrawTexture(twod, startScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE);
DrawTexture(twod, endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, DTA_Alpha, clamp(Clock / 32.f, 0.f, 1.f), TAG_DONE); DrawTexture(twod, endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, DTA_Alpha, clamp(Clock / 32.f, 0.f, 1.f), TAG_DONE);
return Clock >= 32; return Clock >= 32.;
}
//==========================================================================
//
// OpenGLFrameBuffer :: Wiper_Crossfade :: Run
//
// Fades the old screen into the new one over 32 ticks.
//
//==========================================================================
bool Wiper_Crossfade::RunInterpolated(double ticks)
{
Clock += ticks;
DrawTexture(twod, startScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE);
DrawTexture(twod, endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, DTA_Alpha, clamp(Clock / 32.f, 0.f, 1.f), TAG_DONE);
return Clock >= 32.;
} }
//========================================================================== //==========================================================================
@ -282,7 +306,7 @@ Wiper_Melt::Wiper_Melt()
y[0] = -(M_Random() & 15); y[0] = -(M_Random() & 15);
for (int i = 1; i < WIDTH; ++i) for (int i = 1; i < WIDTH; ++i)
{ {
y[i] = clamp(y[i-1] + (M_Random() % 3) - 1, -15, 0); y[i] = clamp(y[i-1] + (double)(M_Random() % 3) - 1., -15., 0.);
} }
} }
@ -307,25 +331,25 @@ bool Wiper_Melt::Run(int ticks)
{ {
if (y[i] < HEIGHT) if (y[i] < HEIGHT)
{ {
if (y[i] < 0) if (y[i] < 0.)
y[i]++; y[i] = y[i] + 1.;
else if (y[i] < 16) else if (y[i] < 16.)
y[i] += y[i] + 1; y[i] += y[i] + 1.;
else else
y[i] = min<int>(y[i] + 8, HEIGHT); y[i] = min<double>(y[i] + 8., HEIGHT);
done = false; done = false;
} }
if (ticks == 0) if (ticks == 0)
{ {
struct { struct {
int32_t x; int32_t x;
int32_t y; double y;
} dpt; } dpt;
struct { struct {
int32_t left; int32_t left;
int32_t top; double top;
int32_t right; int32_t right;
int32_t bottom; double bottom;
} rect; } rect;
// Only draw for the final tick. // Only draw for the final tick.
@ -333,7 +357,7 @@ bool Wiper_Melt::Run(int ticks)
int w = startScreen->GetTexelWidth(); int w = startScreen->GetTexelWidth();
int h = startScreen->GetTexelHeight(); int h = startScreen->GetTexelHeight();
dpt.x = i * w / WIDTH; dpt.x = i * w / WIDTH;
dpt.y = max(0, y[i] * h / HEIGHT); dpt.y = max(0., y[i] * (double)h / (double)HEIGHT);
rect.left = dpt.x; rect.left = dpt.x;
rect.top = 0; rect.top = 0;
rect.right = (i + 1) * w / WIDTH; rect.right = (i + 1) * w / WIDTH;
@ -348,6 +372,77 @@ bool Wiper_Melt::Run(int ticks)
return done; return done;
} }
//==========================================================================
//
// Wiper_Melt :: RunInterpolated
//
// Melts the old screen into the new one over 32 ticks (interpolated).
//
//==========================================================================
bool Wiper_Melt::RunInterpolated(double ticks)
{
bool done = false;
DrawTexture(twod, endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE);
// Copy the old screen in vertical strips on top of the new one.
while (ticks > 0.)
{
done = true;
for (int i = 0; i < WIDTH; i++)
{
if (y[i] < (double)HEIGHT)
{
if (ticks > 0. && ticks < 1.)
{
if (y[i] < 0)
y[i] += ticks;
else if (y[i] < 16)
y[i] += (y[i] + 1) * ticks;
else
y[i] = min<double>(y[i] + (8 * ticks), (double)HEIGHT);
}
else if (y[i] < 0.)
y[i] = y[i] + 1.;
else if (y[i] < 16.)
y[i] += y[i] + 1.;
else
y[i] = min<double>(y[i] + 8., HEIGHT);
done = false;
}
}
ticks -= 1.;
}
for (int i = 0; i < WIDTH; i++)
{
struct {
int32_t x;
double y;
} dpt;
struct {
int32_t left;
double top;
int32_t right;
double bottom;
} rect;
// Only draw for the final tick.
int w = startScreen->GetTexelWidth();
double h = startScreen->GetTexelHeight();
dpt.x = i * w / WIDTH;
dpt.y = max(0., y[i] * (double)h / (double)HEIGHT);
rect.left = dpt.x;
rect.top = 0;
rect.right = (i + 1) * w / WIDTH;
rect.bottom = h - dpt.y;
if (rect.bottom > rect.top)
{
DrawTexture(twod, startScreen, 0, dpt.y, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_ClipLeft, rect.left, DTA_ClipRight, rect.right, DTA_Masked, false, TAG_DONE);
}
}
return done;
}
//========================================================================== //==========================================================================
// //
// OpenGLFrameBuffer :: Wiper_Burn Constructor // OpenGLFrameBuffer :: Wiper_Burn Constructor
@ -423,6 +518,7 @@ void PerformWipe(FTexture* startimg, FTexture* endimg, int wipe_type, bool stops
{ {
// wipe update // wipe update
uint64_t wipestart, nowtime, diff; uint64_t wipestart, nowtime, diff;
double diff_frac;
bool done; bool done;
GSnd->SetSfxPaused(true, 1); GSnd->SetSfxPaused(true, 1);
@ -438,20 +534,34 @@ void PerformWipe(FTexture* startimg, FTexture* endimg, int wipe_type, bool stops
do do
{ {
do if (wiper->Interpolatable() && !cl_capfps)
{ {
I_WaitVBL(2);
nowtime = I_msTime(); nowtime = I_msTime();
diff = (nowtime - wipestart) * 40 / 1000; // Using 35 here feels too slow. diff_frac = (nowtime - wipestart) * 40. / 1000.; // Using 35 here feels too slow.
} while (diff < 1); wipestart = nowtime;
wipestart = nowtime; twod->Begin(screen->GetWidth(), screen->GetHeight());
twod->Begin(screen->GetWidth(), screen->GetHeight()); done = wiper->RunInterpolated(diff_frac);
done = wiper->Run(1); if (overlaydrawer) overlaydrawer();
if (overlaydrawer) overlaydrawer(); twod->End();
twod->End(); screen->Update();
screen->Update(); twod->OnFrameDone();
twod->OnFrameDone(); }
else
{
do
{
I_WaitVBL(2);
nowtime = I_msTime();
diff = (nowtime - wipestart) * 40 / 1000; // Using 35 here feels too slow.
} while (diff < 1);
wipestart = nowtime;
twod->Begin(screen->GetWidth(), screen->GetHeight());
done = wiper->Run(1);
if (overlaydrawer) overlaydrawer();
twod->End();
screen->Update();
twod->OnFrameDone();
}
} while (!done); } while (!done);
delete wiper; delete wiper;
I_FreezeTime(false); I_FreezeTime(false);

View file

@ -74,6 +74,7 @@ float relative_volume = 1.f;
float saved_relative_volume = 1.0f; // this could be used to implement an ACS FadeMusic function float saved_relative_volume = 1.0f; // this could be used to implement an ACS FadeMusic function
MusicVolumeMap MusicVolumes; MusicVolumeMap MusicVolumes;
MidiDeviceMap MidiDevices; MidiDeviceMap MidiDevices;
TMap<int, int> ModPlayers;
static int DefaultFindMusic(const char* fn) static int DefaultFindMusic(const char* fn)
{ {
@ -93,6 +94,7 @@ EXTERN_CVAR(Float, fluid_gain)
CVAR(Bool, mus_calcgain, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // changing this will only take effect for the next song. CVAR(Bool, mus_calcgain, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // changing this will only take effect for the next song.
CVAR(Bool, mus_usereplaygain, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // changing this will only take effect for the next song. CVAR(Bool, mus_usereplaygain, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // changing this will only take effect for the next song.
CVAR(Int, mod_preferred_player, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)// toggle between libXMP and Dumb. Unlike other sound CVARs this is not directly mapped to ZMusic's config.
// CODE -------------------------------------------------------------------- // CODE --------------------------------------------------------------------
@ -130,6 +132,24 @@ static FileReader OpenMusic(const char* musicname)
return reader; return reader;
} }
bool MusicExists(const char* music_name)
{
if (music_name == nullptr)
return false;
if (FileExists(music_name))
return true;
else
{
int lumpnum;
lumpnum = mus_cb.FindMusic(music_name);
if (lumpnum == -1) lumpnum = fileSystem.CheckNumForName(music_name, FileSys::ns_music);
if (lumpnum != -1 && fileSystem.FileLength(lumpnum) != 0)
return true;
}
return false;
}
void S_SetMusicCallbacks(MusicCallbacks* cb) void S_SetMusicCallbacks(MusicCallbacks* cb)
{ {
mus_cb = *cb; mus_cb = *cb;
@ -751,6 +771,7 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force)
{ {
int lumpnum = mus_cb.FindMusic(musicname); int lumpnum = mus_cb.FindMusic(musicname);
MidiDeviceSetting* devp = MidiDevices.CheckKey(lumpnum); MidiDeviceSetting* devp = MidiDevices.CheckKey(lumpnum);
int* mplay = ModPlayers.CheckKey(lumpnum);
auto volp = MusicVolumes.CheckKey(lumpnum); auto volp = MusicVolumes.CheckKey(lumpnum);
if (volp) if (volp)
@ -763,6 +784,12 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force)
CheckReplayGain(musicname, devp ? (EMidiDevice)devp->device : MDEV_DEFAULT, devp ? devp->args.GetChars() : ""); CheckReplayGain(musicname, devp ? (EMidiDevice)devp->device : MDEV_DEFAULT, devp ? devp->args.GetChars() : "");
} }
auto mreader = GetMusicReader(reader); // this passes the file reader to the newly created wrapper. auto mreader = GetMusicReader(reader); // this passes the file reader to the newly created wrapper.
int mod_player = mplay? *mplay : *mod_preferred_player;
int scratch;
// This config var is only effective when opening a music stream so there's no need for active synchronization. Setting it here is sufficient.
// Ideally this should have been a parameter to ZMusic_OpenSong, but that would have necessitated an API break.
ChangeMusicSettingInt(zmusic_mod_preferredplayer, mus_playing.handle, mod_player, &scratch);
mus_playing.handle = ZMusic_OpenSong(mreader, devp ? (EMidiDevice)devp->device : MDEV_DEFAULT, devp ? devp->args.GetChars() : ""); mus_playing.handle = ZMusic_OpenSong(mreader, devp ? (EMidiDevice)devp->device : MDEV_DEFAULT, devp ? devp->args.GetChars() : "");
if (mus_playing.handle == nullptr) if (mus_playing.handle == nullptr)
{ {

View file

@ -45,6 +45,9 @@ bool S_StartMusic (const char *music_name);
// Start music using <music_name>, and set whether looping // Start music using <music_name>, and set whether looping
bool S_ChangeMusic (const char *music_name, int order=0, bool looping=true, bool force=false); bool S_ChangeMusic (const char *music_name, int order=0, bool looping=true, bool force=false);
// Check if <music_name> exists
bool MusicExists(const char* music_name);
void S_RestartMusic (); void S_RestartMusic ();
void S_MIDIDeviceChanged(int newdev); void S_MIDIDeviceChanged(int newdev);
@ -71,6 +74,7 @@ struct MidiDeviceSetting
typedef TMap<int, MidiDeviceSetting> MidiDeviceMap; typedef TMap<int, MidiDeviceSetting> MidiDeviceMap;
typedef TMap<int, float> MusicVolumeMap; typedef TMap<int, float> MusicVolumeMap;
extern TMap<int, int> ModPlayers;
extern MidiDeviceMap MidiDevices; extern MidiDeviceMap MidiDevices;
extern MusicVolumeMap MusicVolumes; extern MusicVolumeMap MusicVolumes;
extern MusicCallbacks mus_cb; extern MusicCallbacks mus_cb;

View file

@ -598,6 +598,7 @@ OpenALSoundRenderer::OpenALSoundRenderer()
ALC.EXT_disconnect = !!alcIsExtensionPresent(Device, "ALC_EXT_disconnect"); ALC.EXT_disconnect = !!alcIsExtensionPresent(Device, "ALC_EXT_disconnect");
ALC.SOFT_HRTF = !!alcIsExtensionPresent(Device, "ALC_SOFT_HRTF"); ALC.SOFT_HRTF = !!alcIsExtensionPresent(Device, "ALC_SOFT_HRTF");
ALC.SOFT_pause_device = !!alcIsExtensionPresent(Device, "ALC_SOFT_pause_device"); ALC.SOFT_pause_device = !!alcIsExtensionPresent(Device, "ALC_SOFT_pause_device");
ALC.SOFT_output_limiter = !!alcIsExtensionPresent(Device, "ALC_SOFT_output_limiter");
const ALCchar *current = NULL; const ALCchar *current = NULL;
if(alcIsExtensionPresent(Device, "ALC_ENUMERATE_ALL_EXT")) if(alcIsExtensionPresent(Device, "ALC_ENUMERATE_ALL_EXT"))
@ -634,6 +635,11 @@ OpenALSoundRenderer::OpenALSoundRenderer()
else else
attribs.Push(ALC_DONT_CARE_SOFT); attribs.Push(ALC_DONT_CARE_SOFT);
} }
if(ALC.SOFT_output_limiter)
{
attribs.Push(ALC_OUTPUT_LIMITER_SOFT);
attribs.Push(ALC_TRUE);
}
// Other attribs..? // Other attribs..?
attribs.Push(0); attribs.Push(0);

View file

@ -61,7 +61,7 @@ enum
{ {
DEFAULT_PITCH = 128, DEFAULT_PITCH = 128,
}; };
static FRandom pr_soundpitch ("SoundPitch"); static FCRandom pr_soundpitch ("SoundPitch");
SoundEngine* soundEngine; SoundEngine* soundEngine;
//========================================================================== //==========================================================================
@ -749,9 +749,8 @@ sfxinfo_t *SoundEngine::LoadSound(sfxinfo_t *sfx)
{ {
auto sfxp = sfxdata.data(); auto sfxp = sfxdata.data();
int32_t dmxlen = LittleLong(((int32_t *)sfxp)[1]); int32_t dmxlen = LittleLong(((int32_t *)sfxp)[1]);
// If the sound is voc, use the custom loader. // If the sound is voc, use the custom loader.
if (memcmp (sfxp, "Creative Voice File", 19) == 0) if (size > 19 && memcmp (sfxp, "Creative Voice File", 19) == 0)
{ {
sfx->data = GSnd->LoadSoundVoc(sfxp, size); sfx->data = GSnd->LoadSoundVoc(sfxp, size);
} }

View file

@ -193,7 +193,7 @@ int FCommandLine::argc ()
return _argc; return _argc;
} }
char *FCommandLine::operator[] (int i) const char *FCommandLine::operator[] (int i)
{ {
if (_argv == NULL) if (_argv == NULL)
{ {

View file

@ -44,7 +44,7 @@ public:
FCommandLine (const char *commandline, bool no_escapes = false); FCommandLine (const char *commandline, bool no_escapes = false);
~FCommandLine (); ~FCommandLine ();
int argc (); int argc ();
char *operator[] (int i); const char *operator[] (int i);
const char *args () { return cmd; } const char *args () { return cmd; }
void Shift(); void Shift();

View file

@ -240,6 +240,16 @@ void* FBaseCVar::GetExtraDataPointer()
return m_ExtraDataPointer; return m_ExtraDataPointer;
} }
void FBaseCVar::SetExtraDataPointer2(void *pointer)
{
m_ExtraDataPointer2 = pointer;
}
void* FBaseCVar::GetExtraDataPointer2()
{
return m_ExtraDataPointer2;
}
const char *FBaseCVar::GetHumanString(int precision) const const char *FBaseCVar::GetHumanString(int precision) const
{ {
return GetGenericRep(CVAR_String).String; return GetGenericRep(CVAR_String).String;

View file

@ -222,8 +222,10 @@ public:
void ClearCallback(); void ClearCallback();
void SetExtraDataPointer(void *pointer); void SetExtraDataPointer(void *pointer);
void SetExtraDataPointer2(void *pointer);
void* GetExtraDataPointer(); void* GetExtraDataPointer();
void* GetExtraDataPointer2();
int pnum = -1; int pnum = -1;
FName userinfoName; FName userinfoName;
@ -259,7 +261,8 @@ private:
static inline bool m_UseCallback = false; static inline bool m_UseCallback = false;
static inline bool m_DoNoSet = false; static inline bool m_DoNoSet = false;
void *m_ExtraDataPointer; void *m_ExtraDataPointer = nullptr;
void *m_ExtraDataPointer2 = nullptr;
// These need to go away! // These need to go away!
friend FString C_GetMassCVarString (uint32_t filter, bool compact); friend FString C_GetMassCVarString (uint32_t filter, bool compact);
@ -275,6 +278,8 @@ private:
friend void FilterCompactCVars (TArray<FBaseCVar *> &cvars, uint32_t filter); friend void FilterCompactCVars (TArray<FBaseCVar *> &cvars, uint32_t filter);
friend void C_DeinitConsole(); friend void C_DeinitConsole();
friend void C_ListCVarsWithoutDescription(); friend void C_ListCVarsWithoutDescription();
friend class GLDefsParser;
}; };
// Returns a string with all cvars whose flags match filter. In compact mode, // Returns a string with all cvars whose flags match filter. In compact mode,

View file

@ -80,7 +80,7 @@ CCMD (print)
Printf ("print <name>: Print a string from the string table\n"); Printf ("print <name>: Print a string from the string table\n");
return; return;
} }
const char *str = GStrings[argv[1]]; const char *str = GStrings.CheckString(argv[1]);
if (str == NULL) if (str == NULL)
{ {
Printf ("%s unknown\n", argv[1]); Printf ("%s unknown\n", argv[1]);
@ -304,7 +304,7 @@ CCMD(printlocalized)
return; return;
} }
} }
Printf("%s\n", GStrings(argv[1])); Printf("%s\n", GStrings.GetString(argv[1]));
} }
} }

View file

@ -4,8 +4,15 @@
#include "startupinfo.h" #include "startupinfo.h"
#include "c_cvars.h" #include "c_cvars.h"
#include "gstrings.h" #include "gstrings.h"
#include "version.h"
static_assert(sizeof(void*) == 8, "32 builds are not supported"); static_assert(sizeof(void*) == 8,
"Only LP64/LLP64 builds are officially supported. "
"Please do not attempt to build for other platforms; "
"even if the program succeeds in a MAP01 smoke test, "
"there are e.g. known visual artifacts "
"<https://forum.zdoom.org/viewtopic.php?f=7&t=75673> "
"that lead to a bad user experience.");
// Some global engine variables taken out of the backend code. // Some global engine variables taken out of the backend code.
FStartupScreen* StartWindow; FStartupScreen* StartWindow;
@ -23,7 +30,7 @@ bool pauseext;
FStartupInfo GameStartupInfo; FStartupInfo GameStartupInfo;
CVAR(Bool, queryiwad, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR(Bool, queryiwad, QUERYIWADDEFAULT, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
CVAR(String, defaultiwad, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR(String, defaultiwad, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
CVAR(Bool, vid_fps, false, 0) CVAR(Bool, vid_fps, false, 0)

View file

@ -31,7 +31,6 @@ struct SystemCallbacks
FString(*GetPlayerName)(int i); FString(*GetPlayerName)(int i);
bool (*DispatchEvent)(event_t* ev); bool (*DispatchEvent)(event_t* ev);
bool (*CheckGame)(const char* nm); bool (*CheckGame)(const char* nm);
int (*GetGender)();
void (*MenuClosed)(); void (*MenuClosed)();
bool (*CheckMenudefOption)(const char* opt); bool (*CheckMenudefOption)(const char* opt);
void (*ConsoleToggled)(int state); void (*ConsoleToggled)(int state);

View file

@ -132,7 +132,8 @@ enum
PRE_CONACK, // Sent from host to guest to acknowledge PRE_CONNECT receipt PRE_CONACK, // Sent from host to guest to acknowledge PRE_CONNECT receipt
PRE_ALLFULL, // Sent from host to an unwanted guest PRE_ALLFULL, // Sent from host to an unwanted guest
PRE_ALLHEREACK, // Sent from guest to host to acknowledge PRE_ALLHEREACK receipt PRE_ALLHEREACK, // Sent from guest to host to acknowledge PRE_ALLHEREACK receipt
PRE_GO // Sent from host to guest to continue game startup PRE_GO, // Sent from host to guest to continue game startup
PRE_IN_PROGRESS, // Sent from host to guest if the game has already started
}; };
// Set PreGamePacket.fake to this so that the game rejects any pregame packets // Set PreGamePacket.fake to this so that the game rejects any pregame packets
@ -269,6 +270,8 @@ void PacketSend (void)
// I_Error ("SendPacket error: %s",strerror(errno)); // I_Error ("SendPacket error: %s",strerror(errno));
} }
void PreSend(const void* buffer, int bufferlen, const sockaddr_in* to);
void SendConAck(int num_connected, int num_needed);
// //
// PacketGet // PacketGet
@ -303,7 +306,7 @@ void PacketGet (void)
GetPlayerName(node).GetChars()); GetPlayerName(node).GetChars());
} }
doomcom.data[0] = 0x80; // NCMD_EXIT doomcom.data[0] = NCMD_EXIT;
c = 1; c = 1;
} }
else if (err != WSAEWOULDBLOCK) else if (err != WSAEWOULDBLOCK)
@ -341,10 +344,11 @@ void PacketGet (void)
} }
else if (c > 0) else if (c > 0)
{ //The packet is not from any in-game node, so we might as well discard it. { //The packet is not from any in-game node, so we might as well discard it.
// Don't show the message for disconnect notifications. if (TransmitBuffer[0] == PRE_FAKE)
if (c != 2 || TransmitBuffer[0] != PRE_FAKE || TransmitBuffer[1] != PRE_DISCONNECT)
{ {
DPrintf(DMSG_WARNING, "Dropped packet: Unknown host (%s:%d)\n", inet_ntoa(fromaddress.sin_addr), fromaddress.sin_port); // If it's someone waiting in the lobby, let them know the game already started
uint8_t msg[] = { PRE_FAKE, PRE_IN_PROGRESS };
PreSend(msg, 2, &fromaddress);
} }
doomcom.remotenode = -1; doomcom.remotenode = -1;
return; return;
@ -369,7 +373,22 @@ sockaddr_in *PreGet (void *buffer, int bufferlen, bool noabort)
int err = WSAGetLastError(); int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK || (noabort && err == WSAECONNRESET)) if (err == WSAEWOULDBLOCK || (noabort && err == WSAECONNRESET))
return NULL; // no packet return NULL; // no packet
I_Error ("PreGet: %s", neterror ());
if (doomcom.consoleplayer == 0)
{
int node = FindNode(&fromaddress);
I_NetMessage("Got unexpected disconnect.");
doomcom.numnodes--;
for (; node < doomcom.numnodes; ++node)
sendaddress[node] = sendaddress[node + 1];
// Let remaining guests know that somebody left.
SendConAck(doomcom.numnodes, doomcom.numplayers);
}
else
{
I_NetError("The host disbanded the game unexpectedly");
}
} }
return &fromaddress; return &fromaddress;
} }
@ -499,7 +518,7 @@ void SendAbort (void)
} }
} }
static void SendConAck (int num_connected, int num_needed) void SendConAck (int num_connected, int num_needed)
{ {
PreGamePacket packet; PreGamePacket packet;
@ -708,7 +727,7 @@ bool HostGame (int i)
doomcom.numnodes = 1; doomcom.numnodes = 1;
I_NetInit ("Waiting for players", numplayers); I_NetInit ("Hosting game", numplayers);
// Wait for numplayers-1 different connections // Wait for numplayers-1 different connections
if (!I_NetLoop (Host_CheckForConnects, (void *)(intptr_t)numplayers)) if (!I_NetLoop (Host_CheckForConnects, (void *)(intptr_t)numplayers))
@ -783,13 +802,15 @@ bool Guest_ContactHost (void *userdata)
} }
else if (packet.Message == PRE_DISCONNECT) else if (packet.Message == PRE_DISCONNECT)
{ {
doomcom.numnodes = 0; I_NetError("The host cancelled the game.");
I_FatalError ("The host cancelled the game.");
} }
else if (packet.Message == PRE_ALLFULL) else if (packet.Message == PRE_ALLFULL)
{ {
doomcom.numnodes = 0; I_NetError("The game is full.");
I_FatalError ("The game is full."); }
else if (packet.Message == PRE_IN_PROGRESS)
{
I_NetError("The game was already started.");
} }
} }
} }
@ -850,7 +871,7 @@ bool Guest_WaitForOthers (void *userdata)
return true; return true;
case PRE_DISCONNECT: case PRE_DISCONNECT:
I_FatalError ("The host cancelled the game."); I_NetError("The host cancelled the game.");
break; break;
} }
} }
@ -875,6 +896,7 @@ bool JoinGame (int i)
BuildAddress (&sendaddress[1], Args->GetArg(i+1)); BuildAddress (&sendaddress[1], Args->GetArg(i+1));
sendplayer[1] = 0; sendplayer[1] = 0;
doomcom.numnodes = 2; doomcom.numnodes = 2;
doomcom.consoleplayer = -1;
// Let host know we are here // Let host know we are here
@ -903,11 +925,11 @@ bool JoinGame (int i)
static int PrivateNetOf(in_addr in) static int PrivateNetOf(in_addr in)
{ {
int addr = ntohl(in.s_addr); int addr = ntohl(in.s_addr);
if ((addr & 0xFFFF0000) == 0xC0A80000) // 192.168.0.0 if ((addr & 0xFFFF0000) == 0xC0A80000) // 192.168.0.0
{ {
return 0xC0A80000; return 0xC0A80000;
} }
else if ((addr & 0xFFF00000) == 0xAC100000) // 172.16.0.0 else if ((addr & 0xFFFF0000) >= 0xAC100000 && (addr & 0xFFFF0000) <= 0xAC1F0000) // 172.16.0.0 - 172.31.0.0
{ {
return 0xAC100000; return 0xAC100000;
} }
@ -1046,6 +1068,13 @@ void I_NetMessage(const char* text, ...)
#endif #endif
} }
void I_NetError(const char* error)
{
doomcom.numnodes = 0;
StartWindow->NetClose();
I_FatalError("%s", error);
}
// todo: later these must be dispatched by the main menu, not the start screen. // todo: later these must be dispatched by the main menu, not the start screen.
void I_NetProgress(int val) void I_NetProgress(int val)
{ {

View file

@ -7,6 +7,7 @@
int I_InitNetwork (void); int I_InitNetwork (void);
void I_NetCmd (void); void I_NetCmd (void);
void I_NetMessage(const char*, ...); void I_NetMessage(const char*, ...);
void I_NetError(const char* error);
void I_NetProgress(int val); void I_NetProgress(int val);
void I_NetInit(const char* msg, int num); void I_NetInit(const char* msg, int num);
bool I_NetLoop(bool (*timer_callback)(void*), void* userdata); bool I_NetLoop(bool (*timer_callback)(void*), void* userdata);

View file

@ -2,7 +2,7 @@
#include "zstring.h" #include "zstring.h"
#ifdef __unix__ #if defined(__unix__) || defined(__HAIKU__)
FString GetUserFile (const char *path); FString GetUserFile (const char *path);
#endif #endif
FString M_GetAppDataPath(bool create); FString M_GetAppDataPath(bool create);

View file

@ -120,16 +120,30 @@ bool M_LoadJoystickConfig(IJoystickConfig *joy)
{ {
return false; return false;
} }
assert(GameConfig);
value = GameConfig->GetValueForKey("Enabled"); value = GameConfig->GetValueForKey("Enabled");
if (value != NULL) if (value)
{ {
joy->SetEnabled((bool)atoi(value)); joy->SetEnabled((bool)atoi(value));
} }
if(joy->AllowsEnabledInBackground())
{
value = GameConfig->GetValueForKey("EnabledInBackground");
if (value)
{
joy->SetEnabledInBackground((bool)atoi(value));
}
}
value = GameConfig->GetValueForKey("Sensitivity"); value = GameConfig->GetValueForKey("Sensitivity");
if (value != NULL) if (value)
{ {
joy->SetSensitivity((float)atof(value)); joy->SetSensitivity((float)atof(value));
} }
numaxes = joy->GetNumAxes(); numaxes = joy->GetNumAxes();
for (int i = 0; i < numaxes; ++i) for (int i = 0; i < numaxes; ++i)
{ {
@ -137,21 +151,21 @@ bool M_LoadJoystickConfig(IJoystickConfig *joy)
mysnprintf(key + axislen, countof(key) - axislen, "deadzone"); mysnprintf(key + axislen, countof(key) - axislen, "deadzone");
value = GameConfig->GetValueForKey(key); value = GameConfig->GetValueForKey(key);
if (value != NULL) if (value)
{ {
joy->SetAxisDeadZone(i, (float)atof(value)); joy->SetAxisDeadZone(i, (float)atof(value));
} }
mysnprintf(key + axislen, countof(key) - axislen, "scale"); mysnprintf(key + axislen, countof(key) - axislen, "scale");
value = GameConfig->GetValueForKey(key); value = GameConfig->GetValueForKey(key);
if (value != NULL) if (value)
{ {
joy->SetAxisScale(i, (float)atof(value)); joy->SetAxisScale(i, (float)atof(value));
} }
mysnprintf(key + axislen, countof(key) - axislen, "map"); mysnprintf(key + axislen, countof(key) - axislen, "map");
value = GameConfig->GetValueForKey(key); value = GameConfig->GetValueForKey(key);
if (value != NULL) if (value)
{ {
EJoyAxis gameaxis = (EJoyAxis)atoi(value); EJoyAxis gameaxis = (EJoyAxis)atoi(value);
if (gameaxis < JOYAXIS_None || gameaxis >= NUM_JOYAXIS) if (gameaxis < JOYAXIS_None || gameaxis >= NUM_JOYAXIS)
@ -185,6 +199,12 @@ void M_SaveJoystickConfig(IJoystickConfig *joy)
{ {
GameConfig->SetValueForKey("Enabled", "0"); GameConfig->SetValueForKey("Enabled", "0");
} }
if (!joy->AllowsEnabledInBackground() && joy->GetEnabledInBackground())
{
GameConfig->SetValueForKey("EnabledInBackground", "1");
}
if (!joy->IsSensitivityDefault()) if (!joy->IsSensitivityDefault())
{ {
mysnprintf(value, countof(value), "%g", joy->GetSensitivity()); mysnprintf(value, countof(value), "%g", joy->GetSensitivity());

View file

@ -39,6 +39,10 @@ struct IJoystickConfig
virtual bool GetEnabled() = 0; virtual bool GetEnabled() = 0;
virtual void SetEnabled(bool enabled) = 0; virtual void SetEnabled(bool enabled) = 0;
virtual bool AllowsEnabledInBackground() = 0;
virtual bool GetEnabledInBackground() = 0;
virtual void SetEnabledInBackground(bool enabled) = 0;
// Used by the saver to not save properties that are at their defaults. // Used by the saver to not save properties that are at their defaults.
virtual bool IsSensitivityDefault() = 0; virtual bool IsSensitivityDefault() = 0;
virtual bool IsAxisDeadZoneDefault(int axis) = 0; virtual bool IsAxisDeadZoneDefault(int axis) = 0;

View file

@ -83,7 +83,7 @@ FRandom pr_exrandom("EX_Random");
// PUBLIC DATA DEFINITIONS ------------------------------------------------- // PUBLIC DATA DEFINITIONS -------------------------------------------------
FRandom M_Random; FCRandom M_Random;
// Global seed. This is modified predictably to initialize every RNG. // Global seed. This is modified predictably to initialize every RNG.
uint32_t rngseed; uint32_t rngseed;
@ -126,8 +126,8 @@ CCMD(rngseed)
// PRIVATE DATA DEFINITIONS ------------------------------------------------ // PRIVATE DATA DEFINITIONS ------------------------------------------------
FRandom *FRandom::RNGList; FRandom *FRandom::RNGList, *FRandom::CRNGList;
static TDeletingArray<FRandom *> NewRNGs; static TDeletingArray<FRandom *> NewRNGs, NewCRNGs;
// CODE -------------------------------------------------------------------- // CODE --------------------------------------------------------------------
@ -139,14 +139,22 @@ static TDeletingArray<FRandom *> NewRNGs;
// //
//========================================================================== //==========================================================================
FRandom::FRandom () FRandom::FRandom (bool client)
: NameCRC (0) : NameCRC (0), bClient(client)
{ {
#ifndef NDEBUG #ifndef NDEBUG
Name = NULL; Name = NULL;
#endif #endif
Next = RNGList; if (bClient)
RNGList = this; {
Next = CRNGList;
CRNGList = this;
}
else
{
Next = RNGList;
RNGList = this;
}
Init(0); Init(0);
} }
@ -158,7 +166,7 @@ FRandom::FRandom ()
// //
//========================================================================== //==========================================================================
FRandom::FRandom (const char *name) FRandom::FRandom (const char *name, bool client) : bClient(client)
{ {
NameCRC = CalcCRC32 ((const uint8_t *)name, (unsigned int)strlen (name)); NameCRC = CalcCRC32 ((const uint8_t *)name, (unsigned int)strlen (name));
#ifndef NDEBUG #ifndef NDEBUG
@ -170,7 +178,7 @@ FRandom::FRandom (const char *name)
#endif #endif
// Insert the RNG in the list, sorted by CRC // Insert the RNG in the list, sorted by CRC
FRandom **prev = &RNGList, *probe = RNGList; FRandom **prev = (bClient ? &CRNGList : &RNGList), * probe = (bClient ? CRNGList : RNGList);
while (probe != NULL && probe->NameCRC < NameCRC) while (probe != NULL && probe->NameCRC < NameCRC)
{ {
@ -205,8 +213,8 @@ FRandom::~FRandom ()
FRandom *last = NULL; FRandom *last = NULL;
prev = &RNGList; prev = bClient ? &CRNGList : &RNGList;
rng = RNGList; rng = bClient ? CRNGList : RNGList;
while (rng != NULL && rng != this) while (rng != NULL && rng != this)
{ {
@ -237,6 +245,11 @@ void FRandom::StaticClearRandom ()
{ {
rng->Init(rngseed); rng->Init(rngseed);
} }
for (FRandom* rng = FRandom::CRNGList; rng != NULL; rng = rng->Next)
{
rng->Init(rngseed);
}
} }
//========================================================================== //==========================================================================
@ -345,15 +358,15 @@ void FRandom::StaticReadRNGState(FSerializer &arc)
// //
//========================================================================== //==========================================================================
FRandom *FRandom::StaticFindRNG (const char *name) FRandom *FRandom::StaticFindRNG (const char *name, bool client)
{ {
uint32_t NameCRC = CalcCRC32 ((const uint8_t *)name, (unsigned int)strlen (name)); uint32_t NameCRC = CalcCRC32 ((const uint8_t *)name, (unsigned int)strlen (name));
// Use the default RNG if this one happens to have a CRC of 0. // Use the default RNG if this one happens to have a CRC of 0.
if (NameCRC == 0) return &pr_exrandom; if (NameCRC == 0) return client ? &M_Random : &pr_exrandom;
// Find the RNG in the list, sorted by CRC // Find the RNG in the list, sorted by CRC
FRandom **prev = &RNGList, *probe = RNGList; FRandom **prev = (client ? &CRNGList : &RNGList), *probe = (client ? CRNGList : RNGList);
while (probe != NULL && probe->NameCRC < NameCRC) while (probe != NULL && probe->NameCRC < NameCRC)
{ {
@ -364,14 +377,32 @@ FRandom *FRandom::StaticFindRNG (const char *name)
if (probe == NULL || probe->NameCRC != NameCRC) if (probe == NULL || probe->NameCRC != NameCRC)
{ {
// A matching RNG doesn't exist yet so create it. // A matching RNG doesn't exist yet so create it.
probe = new FRandom(name); probe = new FRandom(name, client);
// Store the new RNG for destruction when ZDoom quits. // Store the new RNG for destruction when ZDoom quits.
NewRNGs.Push(probe); if (client)
NewCRNGs.Push(probe);
else
NewRNGs.Push(probe);
} }
return probe; return probe;
} }
void FRandom::SaveRNGState(TArray<FRandom>& backups)
{
for (auto cur = RNGList; cur != nullptr; cur = cur->Next)
backups.Push(*cur);
}
void FRandom::RestoreRNGState(TArray<FRandom>& backups)
{
unsigned int i = 0u;
for (auto cur = RNGList; cur != nullptr; cur = cur->Next)
*cur = backups[i++];
backups.Clear();
}
//========================================================================== //==========================================================================
// //
// FRandom :: StaticPrintSeeds // FRandom :: StaticPrintSeeds

View file

@ -44,9 +44,9 @@ class FSerializer;
class FRandom : public SFMTObj class FRandom : public SFMTObj
{ {
public: public:
FRandom (); FRandom() : FRandom(false) {}
FRandom (const char *name); FRandom(const char* name) : FRandom(name, false) {}
~FRandom (); ~FRandom();
int Seed() const int Seed() const
{ {
@ -170,20 +170,34 @@ public:
static void StaticClearRandom (); static void StaticClearRandom ();
static void StaticReadRNGState (FSerializer &arc); static void StaticReadRNGState (FSerializer &arc);
static void StaticWriteRNGState (FSerializer &file); static void StaticWriteRNGState (FSerializer &file);
static FRandom *StaticFindRNG(const char *name); static FRandom *StaticFindRNG(const char *name, bool client);
static void SaveRNGState(TArray<FRandom>& backups);
static void RestoreRNGState(TArray<FRandom>& backups);
#ifndef NDEBUG #ifndef NDEBUG
static void StaticPrintSeeds (); static void StaticPrintSeeds ();
#endif #endif
protected:
FRandom(bool client);
FRandom(const char* name, bool client);
private: private:
#ifndef NDEBUG #ifndef NDEBUG
const char *Name; const char *Name;
#endif #endif
FRandom *Next; FRandom *Next;
uint32_t NameCRC; uint32_t NameCRC;
bool bClient;
static FRandom *RNGList; static FRandom *RNGList, *CRNGList;
};
class FCRandom : public FRandom
{
public:
FCRandom() : FRandom(true) {}
FCRandom(const char* name) : FRandom(name, true) {}
}; };
extern uint32_t rngseed; // The starting seed (not part of state) extern uint32_t rngseed; // The starting seed (not part of state)
@ -193,6 +207,6 @@ extern bool use_staticrng;
// M_Random can be used for numbers that do not affect gameplay // M_Random can be used for numbers that do not affect gameplay
extern FRandom M_Random; extern FCRandom M_Random;
#endif #endif

View file

@ -41,6 +41,12 @@ xx(Random2)
xx(RandomPick) xx(RandomPick)
xx(FRandomPick) xx(FRandomPick)
xx(SetRandomSeed) xx(SetRandomSeed)
xx(CRandom)
xx(CFRandom)
xx(CRandom2)
xx(CRandomPick)
xx(CFRandomPick)
xx(CSetRandomSeed)
xx(BuiltinRandomSeed) xx(BuiltinRandomSeed)
xx(BuiltinNew) xx(BuiltinNew)
xx(GetClass) xx(GetClass)
@ -190,9 +196,15 @@ xx(TranslationID)
xx(Overlay) xx(Overlay)
xx(IsValid) xx(IsValid)
xx(IsNull) xx(IsNull)
xx(IsEmpty)
xx(IsFixed)
xx(IsKeep)
xx(Exists) xx(Exists)
xx(SetInvalid) xx(SetInvalid)
xx(SetNull) xx(SetNull)
xx(SetEmpty)
xx(SetFixed)
xx(SetKeep)
xx(Key) xx(Key)
xx(Index) xx(Index)
xx(Find) xx(Find)
@ -278,6 +290,8 @@ xx(BuiltinNameToClass)
xx(BuiltinClassCast) xx(BuiltinClassCast)
xx(BuiltinFunctionPtrCast) xx(BuiltinFunctionPtrCast)
xx(BuiltinFindTranslation) xx(BuiltinFindTranslation)
xx(HandleDeprecatedFlags)
xx(CheckDeprecatedFlags)
xx(ScreenJobRunner) xx(ScreenJobRunner)
xx(Action) xx(Action)

View file

@ -197,7 +197,10 @@ void FSerializer::Close()
} }
if (mErrors > 0) if (mErrors > 0)
{ {
I_Error("%d errors parsing JSON", mErrors); if (mLumpName.IsNotEmpty())
I_Error("%d errors parsing JSON lump %s", mErrors, mLumpName.GetChars());
else
I_Error("%d errors parsing JSON", mErrors);
} }
} }
@ -331,6 +334,28 @@ bool FSerializer::HasObject(const char* name)
// //
//========================================================================== //==========================================================================
bool FSerializer::IsKeyNull(const char* name)
{
if (isReading())
{
auto val = r->FindKey(name);
if (val != nullptr)
{
if (val->IsNull())
{
return true;
}
}
}
return false;
}
//==========================================================================
//
//
//
//==========================================================================
void FSerializer::EndObject() void FSerializer::EndObject()
{ {
if (isWriting()) if (isWriting())

View file

@ -9,6 +9,7 @@
#include "palentry.h" #include "palentry.h"
#include "name.h" #include "name.h"
#include "dictionary.h" #include "dictionary.h"
#include "bonecomponents.h"
extern bool save_full; extern bool save_full;
@ -93,6 +94,7 @@ public:
void EndObject(); void EndObject();
bool HasKey(const char* name); bool HasKey(const char* name);
bool HasObject(const char* name); bool HasObject(const char* name);
bool IsKeyNull(const char* name);
bool BeginArray(const char *name); bool BeginArray(const char *name);
void EndArray(); void EndArray();
unsigned GetSize(const char *group); unsigned GetSize(const char *group);
@ -224,6 +226,7 @@ public:
int mErrors = 0; int mErrors = 0;
int mObjectErrors = 0; int mObjectErrors = 0;
FString mLumpName;
}; };
FSerializer& Serialize(FSerializer& arc, const char* key, char& value, char* defval); FSerializer& Serialize(FSerializer& arc, const char* key, char& value, char* defval);
@ -247,8 +250,9 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FString &sid, FString
FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def); FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def);
FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelOverride &mo, struct ModelOverride *def); FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelOverride &mo, struct ModelOverride *def);
FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimModelOverride &mo, struct AnimModelOverride *def); FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimModelOverride &mo, struct AnimModelOverride *def);
FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimOverride &ao, struct AnimOverride *def); FSerializer &Serialize(FSerializer &arc, const char *key, ModelAnim &ao, ModelAnim *def);
FSerializer& Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval); FSerializer &Serialize(FSerializer &arc, const char *key, ModelAnimFrame &ao, ModelAnimFrame *def);
FSerializer &Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval);
void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointerValue *&p); void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointerValue *&p);

View file

@ -55,6 +55,7 @@ public:
virtual void NetInit(const char *message, int num_players) {} virtual void NetInit(const char *message, int num_players) {}
virtual void NetProgress(int count) {} virtual void NetProgress(int count) {}
virtual void NetDone() {} virtual void NetDone() {}
virtual void NetClose() {}
virtual bool NetLoop(bool (*timer_callback)(void *), void *userdata) { return false; } virtual bool NetLoop(bool (*timer_callback)(void *), void *userdata) { return false; }
virtual void AppendStatusLine(const char* status) {} virtual void AppendStatusLine(const char* status) {}
virtual void LoadingStatus(const char* message, int colors) {} virtual void LoadingStatus(const char* message, int colors) {}
@ -74,6 +75,7 @@ public:
void NetProgress(int count); void NetProgress(int count);
void NetMessage(const char* format, ...); // cover for printf void NetMessage(const char* format, ...); // cover for printf
void NetDone(); void NetDone();
void NetClose();
bool NetLoop(bool (*timer_callback)(void*), void* userdata); bool NetLoop(bool (*timer_callback)(void*), void* userdata);
protected: protected:
int NetMaxPos, NetCurPos; int NetMaxPos, NetCurPos;

View file

@ -47,29 +47,29 @@
// //
//========================================================================== //==========================================================================
void FStringTable::LoadStrings (FileSys::FileSystem& fileSystem_, const char *language) void FStringTable::LoadStrings (FileSys::FileSystem& fileSystem, const char *language)
{ {
int lastlump, lump; int lastlump, lump;
fileSystem = &fileSystem_;
allStrings.Clear(); allStrings.Clear();
lastlump = 0; lastlump = 0;
while ((lump = fileSystem->FindLump("LMACROS", &lastlump)) != -1) while ((lump = fileSystem.FindLump("LMACROS", &lastlump)) != -1)
{ {
readMacros(lump); auto lumpdata = fileSystem.ReadFile(lump);
readMacros(lumpdata.string(), lumpdata.size());
} }
lastlump = 0; lastlump = 0;
while ((lump = fileSystem->FindLump ("LANGUAGE", &lastlump)) != -1) while ((lump = fileSystem.FindLump ("LANGUAGE", &lastlump)) != -1)
{ {
auto lumpdata = fileSystem->ReadFile(lump); auto lumpdata = fileSystem.ReadFile(lump);
auto filenum = fileSystem.GetFileContainer(lump);
if (!ParseLanguageCSV(lump, lumpdata.string(), lumpdata.size())) if (!ParseLanguageCSV(filenum, lumpdata.string(), lumpdata.size()))
LoadLanguage (lump, lumpdata.string(), lumpdata.size()); LoadLanguage (filenum, lumpdata.string(), lumpdata.size());
} }
UpdateLanguage(language); UpdateLanguage(language);
allMacros.Clear(); allMacros.Clear();
fileSystem = nullptr;
} }
@ -159,10 +159,9 @@ TArray<TArray<FString>> FStringTable::parseCSV(const char* buffer, size_t size)
// //
//========================================================================== //==========================================================================
bool FStringTable::readMacros(int lumpnum) bool FStringTable::readMacros(const char* buffer, size_t size)
{ {
auto lumpdata = fileSystem->ReadFile(lumpnum); auto data = parseCSV(buffer, size);
auto data = parseCSV(lumpdata.string(), lumpdata.size());
allMacros.Clear(); allMacros.Clear();
for (unsigned i = 1; i < data.Size(); i++) for (unsigned i = 1; i < data.Size(); i++)
@ -187,7 +186,7 @@ bool FStringTable::readMacros(int lumpnum)
// //
//========================================================================== //==========================================================================
bool FStringTable::ParseLanguageCSV(int lumpnum, const char* buffer, size_t size) bool FStringTable::ParseLanguageCSV(int filenum, const char* buffer, size_t size)
{ {
if (size < 11) return false; if (size < 11) return false;
if (strnicmp(buffer, "default,", 8) && strnicmp(buffer, "identifier,", 11 )) return false; if (strnicmp(buffer, "default,", 8) && strnicmp(buffer, "identifier,", 11 )) return false;
@ -255,17 +254,18 @@ bool FStringTable::ParseLanguageCSV(int lumpnum, const char* buffer, size_t size
} }
} }
row[labelcol].StripLeftRight();
FName strName = row[labelcol].GetChars(); FName strName = row[labelcol].GetChars();
if (hasDefaultEntry) if (hasDefaultEntry)
{ {
DeleteForLabel(lumpnum, strName); DeleteForLabel(filenum, strName);
} }
for (auto &langentry : langrows) for (auto &langentry : langrows)
{ {
auto str = row[langentry.first]; auto str = row[langentry.first];
if (str.Len() > 0) if (str.Len() > 0)
{ {
InsertString(lumpnum, langentry.second, strName, str); InsertString(filenum, langentry.second, strName, str);
} }
else else
{ {
@ -409,11 +409,10 @@ void FStringTable::DeleteString(int langid, FName label)
// //
//========================================================================== //==========================================================================
void FStringTable::DeleteForLabel(int lumpnum, FName label) void FStringTable::DeleteForLabel(int filenum, FName label)
{ {
decltype(allStrings)::Iterator it(allStrings); decltype(allStrings)::Iterator it(allStrings);
decltype(allStrings)::Pair *pair; decltype(allStrings)::Pair *pair;
auto filenum = fileSystem->GetFileContainer(lumpnum);
while (it.NextPair(pair)) while (it.NextPair(pair))
{ {
@ -432,10 +431,10 @@ void FStringTable::DeleteForLabel(int lumpnum, FName label)
// //
//========================================================================== //==========================================================================
void FStringTable::InsertString(int lumpnum, int langid, FName label, const FString &string) void FStringTable::InsertString(int filenum, int langid, FName label, const FString &string)
{ {
const char *strlangid = (const char *)&langid; const char *strlangid = (const char *)&langid;
TableElement te = { fileSystem->GetFileContainer(lumpnum), { string, string, string, string } }; TableElement te = { filenum, { string, string, string, string } };
ptrdiff_t index; ptrdiff_t index;
while ((index = te.strings[0].IndexOf("@[")) >= 0) while ((index = te.strings[0].IndexOf("@[")) >= 0)
{ {
@ -579,26 +578,33 @@ bool FStringTable::exists(const char *name)
// //
//========================================================================== //==========================================================================
const char *FStringTable::GetString(const char *name, uint32_t *langtable, int gender) const const char *FStringTable::CheckString(const char *name, uint32_t *langtable, int gender) const
{ {
if (name == nullptr || *name == 0) if (name == nullptr || *name == 0)
{ {
return nullptr; return nullptr;
} }
if (gender == -1 && sysCallbacks.GetGender) gender = sysCallbacks.GetGender(); if (gender == -1) gender = defaultgender;
if (gender < 0 || gender > 3) gender = 0; if (gender < 0 || gender > 3) gender = 0;
FName nm(name, true); FName nm(name, true);
if (nm != NAME_None) if (nm != NAME_None)
{ {
TableElement* bestItem = nullptr;
for (auto map : currentLanguageSet) for (auto map : currentLanguageSet)
{ {
auto item = map.second->CheckKey(nm); auto item = map.second->CheckKey(nm);
if (item) if (item)
{ {
if (bestItem && bestItem->filenum > item->filenum)
{
// prioritize content from later files, even if the language doesn't fully match.
// This is mainly for Dehacked content.
continue;
}
if (langtable) *langtable = map.first; if (langtable) *langtable = map.first;
auto c = item->strings[gender].GetChars(); auto c = item->strings[gender].GetChars();
if (c && *c == '$' && c[1] == '$') if (c && *c == '$' && c[1] == '$')
return GetString(c + 2, langtable, gender); c = CheckString(c + 2, langtable, gender);
return c; return c;
} }
} }
@ -608,7 +614,7 @@ const char *FStringTable::GetString(const char *name, uint32_t *langtable, int g
//========================================================================== //==========================================================================
// //
// Finds a string by name in a given language // Finds a string by name in a given language without attempting any substitution
// //
//========================================================================== //==========================================================================
@ -618,7 +624,7 @@ const char *FStringTable::GetLanguageString(const char *name, uint32_t langtable
{ {
return nullptr; return nullptr;
} }
if (gender == -1 && sysCallbacks.GetGender) gender = sysCallbacks.GetGender(); if (gender == -1) gender = defaultgender;
if (gender < 0 || gender > 3) gender = 0; if (gender < 0 || gender > 3) gender = 0;
FName nm(name, true); FName nm(name, true);
if (nm != NAME_None) if (nm != NAME_None)
@ -655,9 +661,9 @@ bool FStringTable::MatchDefaultString(const char *name, const char *content) con
// //
//========================================================================== //==========================================================================
const char *FStringTable::operator() (const char *name) const const char *FStringTable::GetString(const char *name) const
{ {
const char *str = operator[] (name); const char *str = CheckString(name, nullptr);
return str ? str : name; return str ? str : name;
} }

View file

@ -95,32 +95,29 @@ public:
const char *GetLanguageString(const char *name, uint32_t langtable, int gender = -1) const; const char *GetLanguageString(const char *name, uint32_t langtable, int gender = -1) const;
bool MatchDefaultString(const char *name, const char *content) const; bool MatchDefaultString(const char *name, const char *content) const;
const char *GetString(const char *name, uint32_t *langtable, int gender = -1) const; const char *CheckString(const char *name, uint32_t *langtable = nullptr, int gender = -1) const;
const char *operator() (const char *name) const; // Never returns NULL const char* GetString(const char* name) const;
const char* operator() (const FString& name) const { return operator()(name.GetChars()); } const char* GetString(const FString& name) const { return GetString(name.GetChars()); }
const char *operator[] (const char *name) const
{
return GetString(name, nullptr);
}
bool exists(const char *name); bool exists(const char *name);
void InsertString(int lumpnum, int langid, FName label, const FString& string); void InsertString(int filenum, int langid, FName label, const FString& string);
void SetDefaultGender(int gender) { defaultgender = gender; }
private: private:
FileSys::FileSystem* fileSystem;
FString activeLanguage; FString activeLanguage;
StringMacroMap allMacros; StringMacroMap allMacros;
LangMap allStrings; LangMap allStrings;
TArray<std::pair<uint32_t, StringMap*>> currentLanguageSet; TArray<std::pair<uint32_t, StringMap*>> currentLanguageSet;
int defaultgender = 0;
void LoadLanguage (int lumpnum, const char* buffer, size_t size); void LoadLanguage (int lumpnum, const char* buffer, size_t size);
TArray<TArray<FString>> parseCSV(const char* buffer, size_t size); TArray<TArray<FString>> parseCSV(const char* buffer, size_t size);
bool ParseLanguageCSV(int lumpnum, const char* buffer, size_t size); bool ParseLanguageCSV(int filenum, const char* buffer, size_t size);
bool readMacros(int lumpnum); bool readMacros(const char* buffer, size_t size);
void DeleteString(int langid, FName label); void DeleteString(int langid, FName label);
void DeleteForLabel(int lumpnum, FName label); void DeleteForLabel(int filenum, FName label);
static size_t ProcessEscapes (char *str); static size_t ProcessEscapes (char *str);
public: public:
@ -138,7 +135,7 @@ public:
const char* localize(const char* str) const char* localize(const char* str)
{ {
return *str == '$' ? operator()(str + 1) : str; return *str == '$' ? GetString(str + 1) : str;
} }
}; };

View file

@ -42,8 +42,8 @@ public:
void SetMaxIwadNum(int x) { MaxIwadIndex = x; } void SetMaxIwadNum(int x) { MaxIwadIndex = x; }
bool InitSingleFile(const char *filename, FileSystemMessageFunc Printf = nullptr); bool InitSingleFile(const char *filename, FileSystemMessageFunc Printf = nullptr);
bool InitMultipleFiles (std::vector<std::string>& filenames, LumpFilterInfo* filter = nullptr, FileSystemMessageFunc Printf = nullptr, bool allowduplicates = false, FILE* hashfile = nullptr); bool InitMultipleFiles (std::vector<std::string>& filenames, LumpFilterInfo* filter = nullptr, FileSystemMessageFunc Printf = nullptr, bool allowduplicates = false);
void AddFile (const char *filename, FileReader *wadinfo, LumpFilterInfo* filter, FileSystemMessageFunc Printf, FILE* hashfile); void AddFile (const char *filename, FileReader *wadinfo, LumpFilterInfo* filter, FileSystemMessageFunc Printf);
int CheckIfResourceFileLoaded (const char *name) noexcept; int CheckIfResourceFileLoaded (const char *name) noexcept;
void AddAdditionalFile(const char* filename, FileReader* wadinfo = NULL) {} void AddAdditionalFile(const char* filename, FileReader* wadinfo = NULL) {}

View file

@ -31,8 +31,6 @@
** **
*/ */
namespace FileSys {
#ifdef _WIN32 #ifdef _WIN32
#ifndef _WINNT_ #ifndef _WINNT_
@ -40,6 +38,8 @@ namespace FileSys {
#include <windows.h> #include <windows.h>
#endif #endif
namespace FileSys {
class FInternalCriticalSection class FInternalCriticalSection
{ {
public: public:
@ -82,6 +82,8 @@ void LeaveCriticalSection(FInternalCriticalSection *c)
#include <pthread.h> #include <pthread.h>
namespace FileSys {
class FInternalCriticalSection class FInternalCriticalSection
{ {
public: public:

View file

@ -106,7 +106,7 @@ int FDirectory::AddDirectory(const char *dirpath, LumpFilterInfo* filter, FileSy
if (mBasePath == nullptr) if (mBasePath == nullptr)
{ {
// extract the base path from the first entry to cover changes made in ScanDirectory. // extract the base path from the first entry to cover changes made in ScanDirectory.
auto full = entry.FilePath.find(entry.FilePathRel); auto full = entry.FilePath.rfind(entry.FilePathRel);
std::string path(entry.FilePath, 0, full); std::string path(entry.FilePath, 0, full);
mBasePath = stringpool->Strdup(path.c_str()); mBasePath = stringpool->Strdup(path.c_str());
} }
@ -132,7 +132,7 @@ int FDirectory::AddDirectory(const char *dirpath, LumpFilterInfo* filter, FileSy
// On Linux this is important because its file system is case sensitive, // On Linux this is important because its file system is case sensitive,
// but even on Windows the Unicode normalization is destructive // but even on Windows the Unicode normalization is destructive
// for some characters and cannot be used for file names. // for some characters and cannot be used for file names.
// Examples for this are the Turkish 'i's or the German Ăź. // Examples for this are the Turkish 'i's or the German Ăź.
SystemFilePath[count] = stringpool->Strdup(entry.FilePathRel.c_str()); SystemFilePath[count] = stringpool->Strdup(entry.FilePathRel.c_str());
// for internal access we use the normalized form of the relative path. // for internal access we use the normalized form of the relative path.
// this is fine because the paths that get compared against this will also be normalized. // this is fine because the paths that get compared against this will also be normalized.

View file

@ -238,7 +238,7 @@ bool FileSystem::InitSingleFile(const char* filename, FileSystemMessageFunc Prin
return InitMultipleFiles(filenames, nullptr, Printf); return InitMultipleFiles(filenames, nullptr, Printf);
} }
bool FileSystem::InitMultipleFiles (std::vector<std::string>& filenames, LumpFilterInfo* filter, FileSystemMessageFunc Printf, bool allowduplicates, FILE* hashfile) bool FileSystem::InitMultipleFiles (std::vector<std::string>& filenames, LumpFilterInfo* filter, FileSystemMessageFunc Printf, bool allowduplicates)
{ {
int numfiles; int numfiles;
@ -269,7 +269,7 @@ bool FileSystem::InitMultipleFiles (std::vector<std::string>& filenames, LumpFil
for(size_t i=0;i<filenames.size(); i++) for(size_t i=0;i<filenames.size(); i++)
{ {
AddFile(filenames[i].c_str(), nullptr, filter, Printf, hashfile); AddFile(filenames[i].c_str(), nullptr, filter, Printf);
if (i == (unsigned)MaxIwadIndex) MoveLumpsInFolder("after_iwad/"); if (i == (unsigned)MaxIwadIndex) MoveLumpsInFolder("after_iwad/");
std::string path = "filter/%s"; std::string path = "filter/%s";
@ -327,7 +327,7 @@ int FileSystem::AddFromBuffer(const char* name, char* data, int size, int id, in
// [RH] Removed reload hack // [RH] Removed reload hack
//========================================================================== //==========================================================================
void FileSystem::AddFile (const char *filename, FileReader *filer, LumpFilterInfo* filter, FileSystemMessageFunc Printf, FILE* hashfile) void FileSystem::AddFile (const char *filename, FileReader *filer, LumpFilterInfo* filter, FileSystemMessageFunc Printf)
{ {
int startlump; int startlump;
bool isdir = false; bool isdir = false;
@ -396,49 +396,10 @@ void FileSystem::AddFile (const char *filename, FileReader *filer, LumpFilterInf
path += ':'; path += ':';
path += resfile->getName(i); path += resfile->getName(i);
auto embedded = resfile->GetEntryReader(i, READER_CACHED); auto embedded = resfile->GetEntryReader(i, READER_CACHED);
AddFile(path.c_str(), &embedded, filter, Printf, hashfile); AddFile(path.c_str(), &embedded, filter, Printf);
} }
} }
if (hashfile)
{
uint8_t cksum[16];
char cksumout[33];
memset(cksumout, 0, sizeof(cksumout));
if (filereader.isOpen())
{
filereader.Seek(0, FileReader::SeekSet);
md5Hash(filereader, cksum);
for (size_t j = 0; j < sizeof(cksum); ++j)
{
snprintf(cksumout + (j * 2), 3, "%02X", cksum[j]);
}
fprintf(hashfile, "file: %s, hash: %s, size: %td\n", filename, cksumout, filereader.GetLength());
}
else
fprintf(hashfile, "file: %s, Directory structure\n", filename);
for (int i = 0; i < resfile->EntryCount(); i++)
{
int flags = resfile->GetEntryFlags(i);
if (!(flags & RESFF_EMBEDDED))
{
auto reader = resfile->GetEntryReader(i, READER_SHARED, 0);
md5Hash(filereader, cksum);
for (size_t j = 0; j < sizeof(cksum); ++j)
{
snprintf(cksumout + (j * 2), 3, "%02X", cksum[j]);
}
fprintf(hashfile, "file: %s, lump: %s, hash: %s, size: %zu\n", filename, resfile->getName(i), cksumout, (uint64_t)resfile->Length(i));
}
}
}
return; return;
} }
} }

View file

@ -35,6 +35,7 @@
#include "fs_findfile.h" #include "fs_findfile.h"
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include <sys/stat.h>
#ifndef _WIN32 #ifndef _WIN32
@ -45,8 +46,6 @@
#endif #endif
#include <unistd.h> #include <unistd.h>
#include <fnmatch.h> #include <fnmatch.h>
#include <sys/stat.h>
#include <dirent.h> #include <dirent.h>
#endif #endif

View file

@ -34,6 +34,7 @@
** **
*/ */
#include <algorithm>
#include <miniz.h> #include <miniz.h>
#include "resourcefile.h" #include "resourcefile.h"
#include "md5.hpp" #include "md5.hpp"
@ -45,7 +46,7 @@
#include "wildcards.hpp" #include "wildcards.hpp"
namespace FileSys { namespace FileSys {
// this is for restricting shared file readers to the main thread. // this is for restricting shared file readers to the main thread.
thread_local bool mainThread; thread_local bool mainThread;
void SetMainThread() void SetMainThread()
@ -161,6 +162,7 @@ static int nulPrintf(FSMessageLevel msg, const char* fmt, ...)
FResourceFile *FResourceFile::DoOpenResourceFile(const char *filename, FileReader &file, bool containeronly, LumpFilterInfo* filter, FileSystemMessageFunc Printf, StringPool* sp) FResourceFile *FResourceFile::DoOpenResourceFile(const char *filename, FileReader &file, bool containeronly, LumpFilterInfo* filter, FileSystemMessageFunc Printf, StringPool* sp)
{ {
if (!file.isOpen()) return nullptr;
if (Printf == nullptr) Printf = nulPrintf; if (Printf == nullptr) Printf = nulPrintf;
for(auto func : funcs) for(auto func : funcs)
{ {

View file

@ -271,12 +271,12 @@ bool CheckFontComplete(FFont* font)
{ {
// Also check if the SmallFont contains all characters this language needs. // Also check if the SmallFont contains all characters this language needs.
// If not, switch back to the original one. // If not, switch back to the original one.
return font->CanPrint(GStrings["REQUIRED_CHARACTERS"]); return font->CanPrint(GStrings.CheckString("REQUIRED_CHARACTERS"));
} }
void UpdateGenericUI(bool cvar) void UpdateGenericUI(bool cvar)
{ {
auto switchstr = GStrings["USE_GENERIC_FONT"]; auto switchstr = GStrings.CheckString("USE_GENERIC_FONT");
generic_ui = (cvar || (switchstr && strtoll(switchstr, nullptr, 0))); generic_ui = (cvar || (switchstr && strtoll(switchstr, nullptr, 0)));
if (!generic_ui) if (!generic_ui)
{ {
@ -311,7 +311,7 @@ void UpdateGenericUI(bool cvar)
} }
} }
// Turkish i crap. What a mess, just to save two code points... :( // Turkish i crap. What a mess, just to save two code points... :(
switchstr = GStrings["REQUIRED_CHARACTERS"]; switchstr = GStrings.CheckString("REQUIRED_CHARACTERS");
special_i = switchstr && strstr(switchstr, "\xc4\xb0") != nullptr; // capital dotted i (İ). special_i = switchstr && strstr(switchstr, "\xc4\xb0") != nullptr; // capital dotted i (İ).
if (special_i) if (special_i)
{ {

View file

@ -133,6 +133,26 @@ DEFINE_ACTION_FUNCTION(IJoystickConfig, SetEnabled)
return 0; return 0;
} }
DEFINE_ACTION_FUNCTION(IJoystickConfig, AllowsEnabledInBackground)
{
PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig);
ACTION_RETURN_BOOL(self->AllowsEnabledInBackground());
}
DEFINE_ACTION_FUNCTION(IJoystickConfig, GetEnabledInBackground)
{
PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig);
ACTION_RETURN_BOOL(self->GetEnabledInBackground());
}
DEFINE_ACTION_FUNCTION(IJoystickConfig, SetEnabledInBackground)
{
PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig);
PARAM_BOOL(enabled);
self->SetEnabledInBackground(enabled);
return 0;
}
void UpdateJoystickMenu(IJoystickConfig *selected) void UpdateJoystickMenu(IJoystickConfig *selected)
{ {

View file

@ -51,6 +51,7 @@
CVAR(String, save_dir, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR(String, save_dir, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
FString SavegameFolder; FString SavegameFolder;
CVAR(Int, save_sort_order, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
//============================================================================= //=============================================================================
// //
@ -84,7 +85,7 @@ int FSavegameManagerBase::RemoveSaveSlot(int index)
int listindex = SaveGames[0]->bNoDelete ? index - 1 : index; int listindex = SaveGames[0]->bNoDelete ? index - 1 : index;
if (listindex < 0) return index; if (listindex < 0) return index;
remove(SaveGames[index]->Filename.GetChars()); RemoveFile(SaveGames[index]->Filename.GetChars());
UnloadSaveData(); UnloadSaveData();
FSaveGameNode *file = SaveGames[index]; FSaveGameNode *file = SaveGames[index];
@ -136,7 +137,19 @@ int FSavegameManagerBase::InsertSaveNode(FSaveGameNode *node)
//if (SaveGames[0] == &NewSaveNode) i++; // To not insert above the "new savegame" dummy entry. //if (SaveGames[0] == &NewSaveNode) i++; // To not insert above the "new savegame" dummy entry.
for (; i < SaveGames.Size(); i++) for (; i < SaveGames.Size(); i++)
{ {
if (SaveGames[i]->bOldVersion || node->SaveTitle.CompareNoCase(SaveGames[i]->SaveTitle) <= 0) bool sortcmp = false;
switch(save_sort_order)
{
case 1:
sortcmp = node->CreationTime.CompareNoCase(SaveGames[i]->CreationTime) > 0;
break;
default:
case 0:
sortcmp = node->SaveTitle.CompareNoCase(SaveGames[i]->SaveTitle) <= 0;
break;
}
if (SaveGames[i]->bOldVersion || sortcmp)
{ {
break; break;
} }
@ -170,12 +183,18 @@ void FSavegameManagerBase::NotifyNewSave(const FString &file, const FString &tit
#endif #endif
{ {
node->SaveTitle = title; node->SaveTitle = title;
node->CreationTime = myasctime();
node->bOldVersion = false; node->bOldVersion = false;
node->bMissingWads = false; node->bMissingWads = false;
// refresh my game's position on the list (needed if time/name changed)
SaveGames.Delete(i);
int index = InsertSaveNode(node);
if (okForQuicksave) if (okForQuicksave)
{ {
if (quickSaveSlot == nullptr || quickSaveSlot == (FSaveGameNode*)1 || forceQuicksave) quickSaveSlot = node; if (quickSaveSlot == nullptr || quickSaveSlot == (FSaveGameNode*)1 || forceQuicksave) quickSaveSlot = node;
LastAccessed = LastSaved = i; LastAccessed = LastSaved = index;
} }
return; return;
} }
@ -183,6 +202,7 @@ void FSavegameManagerBase::NotifyNewSave(const FString &file, const FString &tit
auto node = new FSaveGameNode; auto node = new FSaveGameNode;
node->SaveTitle = title; node->SaveTitle = title;
node->CreationTime = myasctime();
node->Filename = file; node->Filename = file;
node->bOldVersion = false; node->bOldVersion = false;
node->bMissingWads = false; node->bMissingWads = false;
@ -274,7 +294,7 @@ DEFINE_ACTION_FUNCTION(FSavegameManager, DoSave)
unsigned FSavegameManagerBase::ExtractSaveData(int index) unsigned FSavegameManagerBase::ExtractSaveData(int index)
{ {
FResourceFile *resf; std::unique_ptr<FResourceFile> resf;
FSaveGameNode *node; FSaveGameNode *node;
if (index == -1) if (index == -1)
@ -295,7 +315,7 @@ unsigned FSavegameManagerBase::ExtractSaveData(int index)
(node = SaveGames[index]) && (node = SaveGames[index]) &&
!node->Filename.IsEmpty() && !node->Filename.IsEmpty() &&
!node->bOldVersion && !node->bOldVersion &&
(resf = FResourceFile::OpenResourceFile(node->Filename.GetChars(), true)) != nullptr) ( (resf.reset(FResourceFile::OpenResourceFile(node->Filename.GetChars(), true))), resf != nullptr))
{ {
auto info = resf->FindEntry("info.json"); auto info = resf->FindEntry("info.json");
if (info < 0) if (info < 0)
@ -316,7 +336,8 @@ unsigned FSavegameManagerBase::ExtractSaveData(int index)
auto pic = resf->FindEntry("savepic.png"); auto pic = resf->FindEntry("savepic.png");
if (pic >= 0) if (pic >= 0)
{ {
FileReader picreader = resf->GetEntryReader(pic, FileSys::READER_NEW, FileSys::READERFLAG_SEEKABLE); // This must use READER_CACHED or it will lock the savegame file.
FileReader picreader = resf->GetEntryReader(pic, FileSys::READER_CACHED, FileSys::READERFLAG_SEEKABLE);
PNGHandle *png = M_VerifyPNG(picreader); PNGHandle *png = M_VerifyPNG(picreader);
if (png != nullptr) if (png != nullptr)
{ {
@ -329,7 +350,6 @@ unsigned FSavegameManagerBase::ExtractSaveData(int index)
} }
} }
} }
delete resf;
} }
return index; return index;
} }
@ -470,7 +490,7 @@ DEFINE_ACTION_FUNCTION(FSavegameManager, GetSavegame)
void FSavegameManagerBase::InsertNewSaveNode() void FSavegameManagerBase::InsertNewSaveNode()
{ {
NewSaveNode.SaveTitle = GStrings("NEWSAVE"); NewSaveNode.SaveTitle = GStrings.GetString("NEWSAVE");
NewSaveNode.bNoDelete = true; NewSaveNode.bNoDelete = true;
SaveGames.Insert(0, &NewSaveNode); SaveGames.Insert(0, &NewSaveNode);
} }

View file

@ -11,6 +11,7 @@ struct FSaveGameNode
{ {
FString SaveTitle; FString SaveTitle;
FString Filename; FString Filename;
FString CreationTime;
bool bOldVersion = false; bool bOldVersion = false;
bool bMissingWads = false; bool bMissingWads = false;
bool bNoDelete = false; bool bNoDelete = false;

View file

@ -4,6 +4,8 @@
#include "TRS.h" #include "TRS.h"
#include "matrix.h" #include "matrix.h"
#include <variant>
class DBoneComponents : public DObject class DBoneComponents : public DObject
{ {
@ -14,3 +16,41 @@ public:
DBoneComponents() = default; DBoneComponents() = default;
}; };
struct ModelAnimFrameInterp
{
float inter = -1.0f;
int frame1 = -1;
int frame2 = -1;
};
struct ModelAnimFramePrecalculatedIQM
{
TArray<TRS> precalcBones;
};
enum EModelAnimFlags
{
MODELANIM_NONE = 1 << 0, // no animation
MODELANIM_LOOP = 1 << 1, // animation loops, otherwise it stays on the last frame once it ends
};
struct ModelAnim
{
int firstFrame = 0;
int lastFrame = 0;
int loopFrame = 0;
float framerate = 0;
double startFrame = 0;
int flags = MODELANIM_NONE;
double startTic = 0; // when the current animation started (changing framerates counts as restarting) (or when animation starts if interpolating from previous animation)
double switchOffset = 0; // when the animation was changed -- where to interpolate the switch from
};
static_assert(sizeof(ModelAnim) == sizeof(double) * 6);
using ModelAnimFrame = std::variant<std::nullptr_t, ModelAnimFrameInterp, ModelAnimFramePrecalculatedIQM>;
double getCurrentFrame(const ModelAnim &anim, double tic, bool *looped);
void calcFrame(const ModelAnim &anim, double tic, ModelAnimFrameInterp &inter);
void calcFrames(const ModelAnim &curAnim, double tic, ModelAnimFrameInterp &to, float &inter);

View file

@ -9,6 +9,8 @@
#include "tarray.h" #include "tarray.h"
#include "name.h" #include "name.h"
#include "bonecomponents.h"
class DBoneComponents; class DBoneComponents;
class FModelRenderer; class FModelRenderer;
class FGameTexture; class FGameTexture;
@ -94,7 +96,10 @@ public:
virtual void AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) = 0; virtual void AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) = 0;
virtual float getAspectFactor(float vscale) { return 1.f; } virtual float getAspectFactor(float vscale) { return 1.f; }
virtual const TArray<TRS>* AttachAnimationData() { return nullptr; }; virtual const TArray<TRS>* AttachAnimationData() { return nullptr; };
virtual const TArray<VSMatrix> CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray<TRS>* animationData, DBoneComponents* bones, int index) { return {}; };
virtual ModelAnimFrame PrecalculateFrame(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData, DBoneComponents* bones, int index) { return nullptr; };
virtual const TArray<VSMatrix> CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData, DBoneComponents* bones, int index) { return {}; };
void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; } void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; }
IModelVertexBuffer *GetVertexBuffer(int type) const { return mVBuf[type]; } IModelVertexBuffer *GetVertexBuffer(int type) const { return mVBuf[type]; }

View file

@ -120,7 +120,12 @@ public:
void BuildVertexBuffer(FModelRenderer* renderer) override; void BuildVertexBuffer(FModelRenderer* renderer) override;
void AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids) override; void AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids) override;
const TArray<TRS>* AttachAnimationData() override; const TArray<TRS>* AttachAnimationData() override;
const TArray<VSMatrix> CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray<TRS>* animationData, DBoneComponents* bones, int index) override;
ModelAnimFrame PrecalculateFrame(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData, DBoneComponents* bones, int index) override;
const TArray<VSMatrix> CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData, DBoneComponents* bones, int index) override;
ModelAnimFramePrecalculatedIQM CalculateFrameIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray<TRS>* animationData, DBoneComponents* bones, int index);
const TArray<VSMatrix> CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray<TRS>* animationData, DBoneComponents* bones, int index);
private: private:
void LoadGeometry(); void LoadGeometry();

View file

@ -560,7 +560,108 @@ static TRS InterpolateBone(const TRS &from, const TRS &to, float t, float invt)
return bone; return bone;
} }
const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray<TRS>* animationData, DBoneComponents* boneComponentData, int index) #include "printf.h"
ModelAnimFrame IQMModel::PrecalculateFrame(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData, DBoneComponents* bones, int index)
{
if(inter <= 0)
{
return CalculateFrameIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData, bones, index);
}
else if(std::holds_alternative<ModelAnimFrameInterp>(from))
{
auto &from_interp = std::get<ModelAnimFrameInterp>(from);
return CalculateFrameIQM(from_interp.frame2, to.frame2, inter, from_interp.frame1, from_interp.inter, to.frame1, to.inter, nullptr, animationData, bones, index);
}
else if(std::holds_alternative<ModelAnimFramePrecalculatedIQM>(from))
{
return CalculateFrameIQM(0, to.frame2, inter, 0, -1.f, to.frame1, to.inter, &std::get<ModelAnimFramePrecalculatedIQM>(from), animationData, bones, index);
}
else
{
return CalculateFrameIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData, bones, index);
}
}
const TArray<VSMatrix> IQMModel::CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData, DBoneComponents* bones, int index)
{
if(inter <= 0)
{
return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData, bones, index);
}
else if(std::holds_alternative<ModelAnimFrameInterp>(from))
{
auto &from_interp = std::get<ModelAnimFrameInterp>(from);
return CalculateBonesIQM(from_interp.frame2, to.frame2, inter, from_interp.frame1, from_interp.inter, to.frame1, to.inter, nullptr, animationData, bones, index);
}
else if(std::holds_alternative<ModelAnimFramePrecalculatedIQM>(from))
{
return CalculateBonesIQM(0, to.frame2, inter, 0, -1.f, to.frame1, to.inter, &std::get<ModelAnimFramePrecalculatedIQM>(from), animationData, bones, index);
}
else
{
return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData, bones, index);
}
}
ModelAnimFramePrecalculatedIQM IQMModel::CalculateFrameIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray<TRS>* animationData, DBoneComponents* boneComponentData, int index)
{
ModelAnimFramePrecalculatedIQM out;
const TArray<TRS>& animationFrames = animationData ? *animationData : TRSData;
out.precalcBones.Resize(Joints.Size());
if (Joints.Size() > 0)
{
int numbones = Joints.SSize();
int offset1 = frame1 * numbones;
int offset2 = frame2 * numbones;
int offset1_1 = frame1_prev * numbones;
int offset2_1 = frame2_prev * numbones;
float invt = 1.0f - inter;
float invt1 = 1.0f - inter1_prev;
float invt2 = 1.0f - inter2_prev;
for (int i = 0; i < numbones; i++)
{
TRS prev;
if(precalculated)
{
prev = precalculated->precalcBones[i];
}
else
{
if(frame1 >= 0 && (frame1_prev >= 0 || inter1_prev < 0))
{
prev = inter1_prev <= 0 ? animationFrames[offset1 + i] : InterpolateBone(animationFrames[offset1_1 + i], animationFrames[offset1 + i], inter1_prev, invt1);
}
}
TRS next;
if(frame2 >= 0 && (frame2_prev >= 0 || inter2_prev < 0))
{
next = inter2_prev <= 0 ? animationFrames[offset2 + i] : InterpolateBone(animationFrames[offset2_1 + i], animationFrames[offset2 + i], inter2_prev, invt2);
}
if(frame1 >= 0 || inter < 0)
{
out.precalcBones[i] = inter < 0 ? animationFrames[offset1 + i] : InterpolateBone(prev, next , inter, invt);
}
}
}
return out;
}
const TArray<VSMatrix> IQMModel::CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray<TRS>* animationData, DBoneComponents* boneComponentData, int index)
{ {
const TArray<TRS>& animationFrames = animationData ? *animationData : TRSData; const TArray<TRS>& animationFrames = animationData ? *animationData : TRSData;
if (Joints.Size() > 0) if (Joints.Size() > 0)
@ -597,9 +698,16 @@ const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, float in
{ {
TRS prev; TRS prev;
if(frame1 >= 0 && (frame1_prev >= 0 || inter1_prev < 0)) if(precalculated)
{ {
prev = inter1_prev <= 0 ? animationFrames[offset1 + i] : InterpolateBone(animationFrames[offset1_1 + i], animationFrames[offset1 + i], inter1_prev, invt1); prev = precalculated->precalcBones[i];
}
else
{
if(frame1 >= 0 && (frame1_prev >= 0 || inter1_prev < 0))
{
prev = inter1_prev <= 0 ? animationFrames[offset1 + i] : InterpolateBone(animationFrames[offset1_1 + i], animationFrames[offset1 + i], inter1_prev, invt1);
}
} }
TRS next; TRS next;

View file

@ -73,6 +73,9 @@ struct FVoxelDef
int VoxeldefIndex; // Needed by GZDoom int VoxeldefIndex; // Needed by GZDoom
double Scale; double Scale;
DAngle AngleOffset;// added to actor's angle to compensate for wrong-facing voxels DAngle AngleOffset;// added to actor's angle to compensate for wrong-facing voxels
double xoffset;
double yoffset;
double zoffset;
bool PitchFromMomentum; bool PitchFromMomentum;
bool UseActorPitch; bool UseActorPitch;
bool UseActorRoll; bool UseActorRoll;

View file

@ -317,6 +317,8 @@ void DObject::Release()
void DObject::Destroy () void DObject::Destroy ()
{ {
RemoveFromNetwork();
// We cannot call the VM during shutdown because all the needed data has been or is in the process of being deleted. // We cannot call the VM during shutdown because all the needed data has been or is in the process of being deleted.
if (PClass::bVMOperational) if (PClass::bVMOperational)
{ {
@ -328,6 +330,7 @@ void DObject::Destroy ()
} }
OnDestroy(); OnDestroy();
ObjectFlags = (ObjectFlags & ~OF_Fixed) | OF_EuthanizeMe; ObjectFlags = (ObjectFlags & ~OF_Fixed) | OF_EuthanizeMe;
GC::WriteBarrier(this);
} }
DEFINE_ACTION_FUNCTION(DObject, Destroy) DEFINE_ACTION_FUNCTION(DObject, Destroy)
@ -569,8 +572,15 @@ void DObject::Serialize(FSerializer &arc)
SerializeFlag("justspawned", OF_JustSpawned); SerializeFlag("justspawned", OF_JustSpawned);
SerializeFlag("spawned", OF_Spawned); SerializeFlag("spawned", OF_Spawned);
SerializeFlag("networked", OF_Networked);
ObjectFlags |= OF_SerialSuccess; ObjectFlags |= OF_SerialSuccess;
if (arc.isReading() && (ObjectFlags & OF_Networked))
{
ClearNetworkID();
EnableNetworking(true);
}
} }
void DObject::CheckIfSerialized () const void DObject::CheckIfSerialized () const
@ -585,7 +595,6 @@ void DObject::CheckIfSerialized () const
} }
} }
DEFINE_ACTION_FUNCTION(DObject, MSTime) DEFINE_ACTION_FUNCTION(DObject, MSTime)
{ {
ACTION_RETURN_INT((uint32_t)I_msTime()); ACTION_RETURN_INT((uint32_t)I_msTime());
@ -614,3 +623,165 @@ void *DObject::ScriptVar(FName field, PType *type)
// This is only for internal use so I_Error is fine. // This is only for internal use so I_Error is fine.
I_Error("Variable %s not found in %s\n", field.GetChars(), cls->TypeName.GetChars()); I_Error("Variable %s not found in %s\n", field.GetChars(), cls->TypeName.GetChars());
} }
//==========================================================================
//
//
//
//==========================================================================
void NetworkEntityManager::InitializeNetworkEntities()
{
if (!s_netEntities.Size())
s_netEntities.AppendFill(nullptr, NetIDStart); // Allocate the first 0-8 slots for the world and clients.
}
// Clients need special handling since they always go in slots 1 - MAXPLAYERS.
void NetworkEntityManager::SetClientNetworkEntity(DObject* mo, const unsigned int playNum)
{
// If resurrecting, we need to swap the corpse's position with the new pawn's
// position so it's no longer considered the client's body.
const uint32_t id = ClientNetIDStart + playNum;
DObject* const oldBody = s_netEntities[id];
if (oldBody != nullptr)
{
if (oldBody == mo)
return;
const uint32_t curID = mo->GetNetworkID();
s_netEntities[curID] = oldBody;
oldBody->ClearNetworkID();
oldBody->SetNetworkID(curID);
mo->ClearNetworkID();
}
else
{
RemoveNetworkEntity(mo); // Free up its current id.
}
s_netEntities[id] = mo;
mo->SetNetworkID(id);
}
void NetworkEntityManager::AddNetworkEntity(DObject* const ent)
{
if (ent->IsNetworked())
return;
// Slot 0 is reserved for the world.
// Clients go in the first 1 - MAXPLAYERS slots
// Everything else is first come first serve.
uint32_t id = WorldNetID;
if (s_openNetIDs.Size())
{
s_openNetIDs.Pop(id);
s_netEntities[id] = ent;
}
else
{
id = s_netEntities.Push(ent);
}
ent->SetNetworkID(id);
}
void NetworkEntityManager::RemoveNetworkEntity(DObject* const ent)
{
if (!ent->IsNetworked())
return;
const uint32_t id = ent->GetNetworkID();
if (id == WorldNetID)
return;
assert(s_netEntities[id] == ent);
if (id >= NetIDStart)
s_openNetIDs.Push(id);
s_netEntities[id] = nullptr;
ent->ClearNetworkID();
}
DObject* NetworkEntityManager::GetNetworkEntity(const uint32_t id)
{
if (id == WorldNetID || id >= s_netEntities.Size())
return nullptr;
return s_netEntities[id];
}
//==========================================================================
//
//
//
//==========================================================================
void DObject::SetNetworkID(const uint32_t id)
{
if (!IsNetworked())
{
ObjectFlags |= OF_Networked;
_networkID = id;
}
}
void DObject::ClearNetworkID()
{
ObjectFlags &= ~OF_Networked;
_networkID = NetworkEntityManager::WorldNetID;
}
void DObject::EnableNetworking(const bool enable)
{
if (enable)
NetworkEntityManager::AddNetworkEntity(this);
else
NetworkEntityManager::RemoveNetworkEntity(this);
}
void DObject::RemoveFromNetwork()
{
NetworkEntityManager::RemoveNetworkEntity(this);
}
static unsigned int GetNetworkID(DObject* const self)
{
return self->GetNetworkID();
}
DEFINE_ACTION_FUNCTION_NATIVE(DObject, GetNetworkID, GetNetworkID)
{
PARAM_SELF_PROLOGUE(DObject);
ACTION_RETURN_INT(self->GetNetworkID());
}
static void EnableNetworking(DObject* const self, const bool enable)
{
self->EnableNetworking(enable);
}
DEFINE_ACTION_FUNCTION_NATIVE(DObject, EnableNetworking, EnableNetworking)
{
PARAM_SELF_PROLOGUE(DObject);
PARAM_BOOL(enable);
self->EnableNetworking(enable);
return 0;
}
static DObject* GetNetworkEntity(const unsigned int id)
{
return NetworkEntityManager::GetNetworkEntity(id);
}
DEFINE_ACTION_FUNCTION_NATIVE(DObject, GetNetworkEntity, GetNetworkEntity)
{
PARAM_PROLOGUE;
PARAM_UINT(id);
ACTION_RETURN_OBJECT(NetworkEntityManager::GetNetworkEntity(id));
}

View file

@ -351,6 +351,18 @@ protected:
friend T* Create(Args&&... args); friend T* Create(Args&&... args);
friend class JitCompiler; friend class JitCompiler;
private:
// This is intentionally left unserialized.
uint32_t _networkID;
public:
inline bool IsNetworked() const { return (ObjectFlags & OF_Networked); }
inline uint32_t GetNetworkID() const { return _networkID; }
void SetNetworkID(const uint32_t id);
void ClearNetworkID();
void RemoveFromNetwork();
virtual void EnableNetworking(const bool enable);
}; };
// This is the only method aside from calling CreateNew that should be used for creating DObjects // This is the only method aside from calling CreateNew that should be used for creating DObjects
@ -475,4 +487,25 @@ inline T *&DObject::PointerVar(FName field)
return *(T**)ScriptVar(field, nullptr); // pointer check is more tricky and for the handful of uses in the DECORATE parser not worth the hassle. return *(T**)ScriptVar(field, nullptr); // pointer check is more tricky and for the handful of uses in the DECORATE parser not worth the hassle.
} }
class NetworkEntityManager
{
private:
inline static TArray<DObject*> s_netEntities = {};
inline static TArray<uint32_t> s_openNetIDs = {};
public:
NetworkEntityManager() = delete;
static constexpr uint32_t WorldNetID = 0u;
static constexpr uint32_t ClientNetIDStart = 1u;
inline static uint32_t NetIDStart;// = MAXPLAYERS + 1u;
static void InitializeNetworkEntities();
static void SetClientNetworkEntity(DObject* mo, const unsigned int playNum);
static void AddNetworkEntity(DObject* const ent);
static void RemoveNetworkEntity(DObject* const ent);
static DObject* GetNetworkEntity(const uint32_t id);
};
#endif //__DOBJECT_H__ #endif //__DOBJECT_H__

View file

@ -26,6 +26,7 @@ enum EObjectFlags
OF_Transient = 1 << 11, // Object should not be archived (references to it will be nulled on disk) OF_Transient = 1 << 11, // Object should not be archived (references to it will be nulled on disk)
OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning) OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning)
OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function
OF_Networked = 1 << 14, // Object has a unique network identifier that makes it synchronizable between all clients.
}; };
template<class T> class TObjPtr; template<class T> class TObjPtr;

View file

@ -107,6 +107,10 @@ public:
virtual bool GetEnabled(); virtual bool GetEnabled();
virtual void SetEnabled(bool enabled); virtual void SetEnabled(bool enabled);
bool AllowsEnabledInBackground() { return false; }
bool GetEnabledInBackground() { return false; }
void SetEnabledInBackground(bool enabled) {}
virtual void SetDefaultConfig(); virtual void SetDefaultConfig();
virtual FString GetIdentifier(); virtual FString GetIdentifier();

View file

@ -122,7 +122,7 @@ void I_ShowFatalError(const char *message)
} }
int I_PickIWad(WadStuff* const wads, const int numwads, const bool showwin, const int defaultiwad, int&) int I_PickIWad(WadStuff* const wads, const int numwads, const bool showwin, const int defaultiwad, int&, FString&)
{ {
if (!showwin) if (!showwin)
{ {

View file

@ -66,6 +66,7 @@ public:
void NetInit(const char* message, int playerCount); void NetInit(const char* message, int playerCount);
void NetProgress(int count); void NetProgress(int count);
void NetDone(); void NetDone();
void NetClose();
private: private:
NSWindow* m_window; NSWindow* m_window;

View file

@ -531,3 +531,8 @@ void FConsoleWindow::NetDone()
m_netAbortButton = nil; m_netAbortButton = nil;
} }
} }
void FConsoleWindow::NetClose()
{
// TODO: Implement this
}

View file

@ -110,6 +110,11 @@ void FBasicStartupScreen::NetDone()
FConsoleWindow::GetInstance().NetDone(); FConsoleWindow::GetInstance().NetDone();
} }
void FBasicStartupScreen::NetClose()
{
FConsoleWindow::GetInstance().NetClose();
}
bool FBasicStartupScreen::NetLoop(bool (*timerCallback)(void*), void* const userData) bool FBasicStartupScreen::NetLoop(bool (*timerCallback)(void*), void* const userData)
{ {
while (true) while (true)

View file

@ -38,7 +38,7 @@ void I_PrintStr (const char *str);
void I_SetIWADInfo (); void I_SetIWADInfo ();
// Pick from multiple IWADs to use // Pick from multiple IWADs to use
int I_PickIWad (WadStuff *wads, int numwads, bool queryiwad, int defaultiwad, int&); int I_PickIWad (WadStuff *wads, int numwads, bool queryiwad, int defaultiwad, int&, FString &);
// [RH] Checks the registry for Steam's install path, so we can scan its // [RH] Checks the registry for Steam's install path, so we can scan its
// directories for IWADs if the user purchased any through Steam. // directories for IWADs if the user purchased any through Steam.
@ -54,17 +54,6 @@ bool I_WriteIniFailed (const char* filename);
class FGameTexture; class FGameTexture;
bool I_SetCursor(FGameTexture *); bool I_SetCursor(FGameTexture *);
static inline char *strlwr(char *str)
{
char *ptr = str;
while(*ptr)
{
*ptr = tolower(*ptr);
++ptr;
}
return str;
}
inline int I_GetNumaNodeCount() { return 1; } inline int I_GetNumaNodeCount() { return 1; }
inline int I_GetNumaNodeThreadCount(int numaNode) { return std::max<int>(std::thread::hardware_concurrency(), 1); } inline int I_GetNumaNodeThreadCount(int numaNode) { return std::max<int>(std::thread::hardware_concurrency(), 1); }
inline void I_SetThreadNumaNode(std::thread &thread, int numaNode) { } inline void I_SetThreadNumaNode(std::thread &thread, int numaNode) { }

View file

@ -167,6 +167,10 @@ public:
Enabled = enabled; Enabled = enabled;
} }
bool AllowsEnabledInBackground() { return false; }
bool GetEnabledInBackground() { return false; }
void SetEnabledInBackground(bool enabled) {}
FString GetIdentifier() FString GetIdentifier()
{ {
char id[16]; char id[16];

View file

@ -298,7 +298,7 @@ void I_PrintStr(const char *cp)
if (StartWindow) RedrawProgressBar(ProgressBarCurPos,ProgressBarMaxPos); if (StartWindow) RedrawProgressBar(ProgressBarCurPos,ProgressBarMaxPos);
} }
int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& autoloadflags) int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& autoloadflags, FString &extraArgs)
{ {
if (!showwin) if (!showwin)
{ {
@ -308,7 +308,7 @@ int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad, int&
#ifdef __APPLE__ #ifdef __APPLE__
return I_PickIWad_Cocoa (wads, numwads, showwin, defaultiwad); return I_PickIWad_Cocoa (wads, numwads, showwin, defaultiwad);
#else #else
return LauncherWindow::ExecModal(wads, numwads, defaultiwad, &autoloadflags); return LauncherWindow::ExecModal(wads, numwads, defaultiwad, &autoloadflags, &extraArgs);
#endif #endif
} }

View file

@ -57,6 +57,7 @@ class FTTYStartupScreen : public FStartupScreen
void NetInit(const char *message, int num_players); void NetInit(const char *message, int num_players);
void NetProgress(int count); void NetProgress(int count);
void NetDone(); void NetDone();
void NetClose();
bool NetLoop(bool (*timer_callback)(void *), void *userdata); bool NetLoop(bool (*timer_callback)(void *), void *userdata);
protected: protected:
bool DidNetInit; bool DidNetInit;
@ -237,6 +238,11 @@ void FTTYStartupScreen::NetProgress(int count)
} }
} }
void FTTYStartupScreen::NetClose()
{
// TODO: Implement this
}
//=========================================================================== //===========================================================================
// //
// FTTYStartupScreen :: NetLoop // FTTYStartupScreen :: NetLoop

View file

@ -112,7 +112,11 @@ FString M_GetAppDataPath(bool create)
{ {
// Don't use GAME_DIR and such so that ZDoom and its child ports can // Don't use GAME_DIR and such so that ZDoom and its child ports can
// share the node cache. // share the node cache.
FString path = NicePath("$HOME/.config/" GAMENAMELOWERCASE); #if defined(__HAIKU__)
FString path = NicePath("$HOME/config/settings/" GAMENAME);
#else
FString path = NicePath("$HOME/.config/" GAMENAMELOWERCASE);
#endif
if (create) if (create)
{ {
CreatePath(path.GetChars()); CreatePath(path.GetChars());

View file

@ -183,6 +183,10 @@ public:
bool GetEnabled(); bool GetEnabled();
void SetEnabled(bool enabled); void SetEnabled(bool enabled);
bool AllowsEnabledInBackground() { return false; }
bool GetEnabledInBackground() { return false; }
void SetEnabledInBackground(bool enabled) {}
void SetDefaultConfig(); void SetDefaultConfig();
FString GetIdentifier(); FString GetIdentifier();

View file

@ -124,6 +124,9 @@ int BlockMouseMove;
static bool EventHandlerResultForNativeMouse; static bool EventHandlerResultForNativeMouse;
EXTERN_CVAR(Bool, i_pauseinbackground);
CVAR (Bool, k_allowfullscreentoggle, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, k_allowfullscreentoggle, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
static void I_CheckGUICapture () static void I_CheckGUICapture ()
@ -481,8 +484,8 @@ LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
break; break;
case WM_ACTIVATEAPP: case WM_ACTIVATEAPP:
AppActive = wParam == TRUE; AppActive = (wParam == TRUE);
if (wParam) if (wParam || !i_pauseinbackground)
{ {
SetPriorityClass (GetCurrentProcess (), INGAME_PRIORITY_CLASS); SetPriorityClass (GetCurrentProcess (), INGAME_PRIORITY_CLASS);
} }

View file

@ -134,6 +134,39 @@ void I_SetIWADInfo()
{ {
} }
//==========================================================================
//
// isConsoleApp()
//
// runtime detection to detect if this is a console subsystem app.
//
// the reason for doing this is because it is possible to edit a binary directly and change its subsystem
// type via hexedit so in order to gain flexibility it makes no sense to just compile out the unused code.
//
// we may plan to publish tools to allow users to do this manually on their own.
//
//==========================================================================
bool isConsoleApp()
{
static bool alreadychecked = false;
static bool returnvalue;
if (!alreadychecked)
{
DWORD pids[2];
DWORD num_pids = GetConsoleProcessList(pids, 2);
bool win32con_is_exclusive = (num_pids <= 1);
returnvalue = ((GetConsoleWindow() != NULL && !win32con_is_exclusive) || (GetStdHandle(STD_OUTPUT_HANDLE) != NULL));
alreadychecked = true;
}
//printf("isConsoleApp is %i\n", returnvalue);
return returnvalue;
}
//========================================================================== //==========================================================================
// //
// DoMain // DoMain
@ -158,7 +191,22 @@ int DoMain (HINSTANCE hInstance)
Args->AppendArg(FString(wargv[i])); Args->AppendArg(FString(wargv[i]));
} }
if (Args->CheckParm("-stdout")) if (isConsoleApp())
{
StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
DWORD mode;
if (GetConsoleMode(StdOut, &mode))
{
if (SetConsoleMode(StdOut, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
FancyStdOut = IsWindows10OrGreater(); // Windows 8.1 and lower do not understand ANSI formatting.
}
}
else if (Args->CheckParm("-stdout") || Args->CheckParm("-norun"))
{ {
// As a GUI application, we don't normally get a console when we start. // As a GUI application, we don't normally get a console when we start.
// If we were run from the shell and are on XP+, we can attach to its // If we were run from the shell and are on XP+, we can attach to its
@ -475,6 +523,11 @@ CUSTOM_CVAR(Bool, disablecrashlog, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
// //
//========================================================================== //==========================================================================
int wmain()
{
return wWinMain(GetModuleHandle(0), 0, GetCommandLineW(), SW_SHOW);
}
int WINAPI wWinMain (HINSTANCE hInstance, HINSTANCE nothing, LPWSTR cmdline, int nCmdShow) int WINAPI wWinMain (HINSTANCE hInstance, HINSTANCE nothing, LPWSTR cmdline, int nCmdShow)
{ {
g_hInst = hInstance; g_hInst = hInstance;

View file

@ -128,6 +128,11 @@ void MainWindow::HideNetStartPane()
NetStartWindow::HideNetStartPane(); NetStartWindow::HideNetStartPane();
} }
void MainWindow::CloseNetStartPane()
{
NetStartWindow::NetClose();
}
void MainWindow::SetNetStartProgress(int pos) void MainWindow::SetNetStartProgress(int pos)
{ {
NetStartWindow::SetNetStartProgress(pos); NetStartWindow::SetNetStartProgress(pos);

View file

@ -29,6 +29,7 @@ public:
void SetNetStartProgress(int pos); void SetNetStartProgress(int pos);
bool RunMessageLoop(bool (*timer_callback)(void*), void* userdata); bool RunMessageLoop(bool (*timer_callback)(void*), void* userdata);
void HideNetStartPane(); void HideNetStartPane();
void CloseNetStartPane();
void SetWindowTitle(const char* caption); void SetWindowTitle(const char* caption);

View file

@ -267,7 +267,7 @@ void I_CheckNativeMouse(bool preferNative, bool eventhandlerresult)
} }
else else
{ {
if ((GetForegroundWindow() != mainwindow.GetHandle()) || preferNative || !use_mouse) if (preferNative || !use_mouse)
{ {
want_native = true; want_native = true;
} }
@ -286,6 +286,10 @@ void I_CheckNativeMouse(bool preferNative, bool eventhandlerresult)
if (!want_native && eventhandlerresult) if (!want_native && eventhandlerresult)
want_native = true; want_native = true;
// The application should *never* grab the mouse cursor if its window doesn't have the focus.
if (GetForegroundWindow() != mainwindow.GetHandle())
want_native = true;
//Printf ("%d %d %d\n", wantNative, preferNative, NativeMouse); //Printf ("%d %d %d\n", wantNative, preferNative, NativeMouse);
if (want_native != NativeMouse) if (want_native != NativeMouse)

View file

@ -117,6 +117,10 @@ public:
bool GetEnabled(); bool GetEnabled();
void SetEnabled(bool enabled); void SetEnabled(bool enabled);
bool AllowsEnabledInBackground() { return false; }
bool GetEnabledInBackground() { return false; }
void SetEnabledInBackground(bool enabled) {}
void SetDefaultConfig(); void SetDefaultConfig();
FString GetIdentifier(); FString GetIdentifier();

View file

@ -102,6 +102,7 @@
// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
void DestroyCustomCursor(); void DestroyCustomCursor();
bool isConsoleApp();
// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
@ -306,6 +307,7 @@ static void PrintToStdOut(const char *cpt, HANDLE StdOut)
else break; else break;
} }
} }
DWORD bytes_written; DWORD bytes_written;
WriteFile(StdOut, printData.GetChars(), (DWORD)printData.Len(), &bytes_written, NULL); WriteFile(StdOut, printData.GetChars(), (DWORD)printData.Len(), &bytes_written, NULL);
if (terminal) if (terminal)
@ -353,7 +355,7 @@ static void SetQueryIWad(HWND dialog)
// //
//========================================================================== //==========================================================================
int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& autoloadflags) int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& autoloadflags, FString &extraArgs)
{ {
int vkey; int vkey;
if (stricmp(queryiwad_key, "shift") == 0) if (stricmp(queryiwad_key, "shift") == 0)
@ -370,7 +372,7 @@ int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad, int&
} }
if (showwin || (vkey != 0 && GetAsyncKeyState(vkey))) if (showwin || (vkey != 0 && GetAsyncKeyState(vkey)))
{ {
return LauncherWindow::ExecModal(wads, numwads, defaultiwad, &autoloadflags); return LauncherWindow::ExecModal(wads, numwads, defaultiwad, &autoloadflags, &extraArgs);
} }
return defaultiwad; return defaultiwad;
} }

View file

@ -38,7 +38,7 @@ void I_PrintStr (const char *cp);
void I_SetIWADInfo (); void I_SetIWADInfo ();
// Pick from multiple IWADs to use // Pick from multiple IWADs to use
int I_PickIWad(WadStuff* wads, int numwads, bool queryiwad, int defaultiwad, int& autoloadflags); int I_PickIWad(WadStuff* wads, int numwads, bool queryiwad, int defaultiwad, int& autoloadflags, FString &extraArgs);
// The ini could not be saved at exit // The ini could not be saved at exit
bool I_WriteIniFailed (const char* filename); bool I_WriteIniFailed (const char* filename);

View file

@ -65,6 +65,8 @@
#endif #endif
#endif #endif
extern bool AppActive;
// TYPES ------------------------------------------------------------------- // TYPES -------------------------------------------------------------------
typedef DWORD (WINAPI *XInputGetStateType)(DWORD index, XINPUT_STATE *state); typedef DWORD (WINAPI *XInputGetStateType)(DWORD index, XINPUT_STATE *state);
@ -105,6 +107,10 @@ public:
bool GetEnabled(); bool GetEnabled();
void SetEnabled(bool enabled); void SetEnabled(bool enabled);
bool AllowsEnabledInBackground() { return true; }
bool GetEnabledInBackground() { return EnabledInBackground; }
void SetEnabledInBackground(bool enabled) { EnabledInBackground = enabled; }
void SetDefaultConfig(); void SetDefaultConfig();
FString GetIdentifier(); FString GetIdentifier();
@ -142,6 +148,7 @@ protected:
int LastButtons; int LastButtons;
bool Connected; bool Connected;
bool Enabled; bool Enabled;
bool EnabledInBackground;
void Attached(); void Attached();
void Detached(); void Detached();
@ -744,7 +751,10 @@ void FXInputManager::ProcessInput()
{ {
for (int i = 0; i < XUSER_MAX_COUNT; ++i) for (int i = 0; i < XUSER_MAX_COUNT; ++i)
{ {
Devices[i]->ProcessInput(); if(AppActive || Devices[i]->GetEnabledInBackground())
{
Devices[i]->ProcessInput();
}
} }
} }

View file

@ -201,3 +201,8 @@ bool FBasicStartupScreen::NetLoop(bool (*timer_callback)(void *), void *userdata
{ {
return mainwindow.RunMessageLoop(timer_callback, userdata); return mainwindow.RunMessageLoop(timer_callback, userdata);
} }
void FBasicStartupScreen::NetClose()
{
mainwindow.CloseNetStartPane();
}

View file

@ -76,7 +76,7 @@ static void CheckOpenGL(void)
{ {
if (opengl32dll == 0) if (opengl32dll == 0)
{ {
opengl32dll = LoadLibrary(L"OpenGL32.DLL"); opengl32dll = LoadLibraryA("OpenGL32.DLL");
if (opengl32dll != 0) if (opengl32dll != 0)
{ {
createcontext = (HGLRC(WINAPI*)(HDC)) GetProcAddress(opengl32dll, "wglCreateContext"); createcontext = (HGLRC(WINAPI*)(HDC)) GetProcAddress(opengl32dll, "wglCreateContext");
@ -134,7 +134,7 @@ static PROC WinGetProcAddress(const char *name)
#if defined(__APPLE__) #if defined(__APPLE__)
#define IntGetProcAddress(name) AppleGLGetProcAddress(name) #define IntGetProcAddress(name) AppleGLGetProcAddress(name)
#else #else
#if defined(__sgi) || defined(__sun) || defined(__unix__) #if defined(__sgi) || defined(__sun) || defined(__unix__) || defined(__HAIKU__)
void* SDL_GL_GetProcAddress(const char* proc); void* SDL_GL_GetProcAddress(const char* proc);
#define IntGetProcAddress(name) SDL_GL_GetProcAddress((const char*)name) #define IntGetProcAddress(name) SDL_GL_GetProcAddress((const char*)name)
//#define IntGetProcAddress(name) PosixGetProcAddress((const GLubyte*)name) //#define IntGetProcAddress(name) PosixGetProcAddress((const GLubyte*)name)

View file

@ -54,3 +54,6 @@ EXTERN_CVAR(Int, gl_shadowmap_filter)
EXTERN_CVAR(Bool, gl_brightfog) EXTERN_CVAR(Bool, gl_brightfog)
EXTERN_CVAR(Bool, gl_lightadditivesurfaces) EXTERN_CVAR(Bool, gl_lightadditivesurfaces)
EXTERN_CVAR(Bool, gl_notexturefill) EXTERN_CVAR(Bool, gl_notexturefill)
EXTERN_CVAR(Bool, r_radarclipper)
EXTERN_CVAR(Bool, r_dithertransparency)

View file

@ -25,7 +25,7 @@ enum ERenderEffect
EFF_SPHEREMAP, EFF_SPHEREMAP,
EFF_BURN, EFF_BURN,
EFF_STENCIL, EFF_STENCIL,
EFF_DITHERTRANS,
MAX_EFFECTS MAX_EFFECTS
}; };

View file

@ -299,6 +299,7 @@ const FEffectShader effectshaders[] =
{ "spheremap", "shaders/glsl/main.vp", "shaders/glsl/main.fp", "shaders/glsl/func_normal.fp", "shaders/glsl/material_normal.fp", "#define SPHEREMAP\n#define NO_ALPHATEST\n" }, { "spheremap", "shaders/glsl/main.vp", "shaders/glsl/main.fp", "shaders/glsl/func_normal.fp", "shaders/glsl/material_normal.fp", "#define SPHEREMAP\n#define NO_ALPHATEST\n" },
{ "burn", "shaders/glsl/main.vp", "shaders/glsl/burn.fp", nullptr, nullptr, "#define SIMPLE\n#define NO_ALPHATEST\n" }, { "burn", "shaders/glsl/main.vp", "shaders/glsl/burn.fp", nullptr, nullptr, "#define SIMPLE\n#define NO_ALPHATEST\n" },
{ "stencil", "shaders/glsl/main.vp", "shaders/glsl/stencil.fp", nullptr, nullptr, "#define SIMPLE\n#define NO_ALPHATEST\n" }, { "stencil", "shaders/glsl/main.vp", "shaders/glsl/stencil.fp", nullptr, nullptr, "#define SIMPLE\n#define NO_ALPHATEST\n" },
{ "dithertrans", "shaders/glsl/main.vp", "shaders/glsl/main.fp", "shaders/glsl/func_normal.fp", "shaders/glsl/material_normal.fp", "#define NO_ALPHATEST\n#define DITHERTRANS\n" },
}; };
int DFrameBuffer::GetShaderCount() int DFrameBuffer::GetShaderCount()

View file

@ -146,11 +146,27 @@ float VREyeInfo::getShift() const
return vr_swap_eyes ? -res : res; return vr_swap_eyes ? -res : res;
} }
VSMatrix VREyeInfo::GetProjection(float fov, float aspectRatio, float fovRatio) const VSMatrix VREyeInfo::GetProjection(float fov, float aspectRatio, float fovRatio, bool iso_ortho) const
{ {
VSMatrix result; VSMatrix result;
if (mShiftFactor == 0) if (iso_ortho) // Orthographic projection for isometric viewpoint
{
double zNear = -3.0/fovRatio; // screen->GetZNear();
double zFar = screen->GetZFar();
double fH = tan(DEG2RAD(fov) / 2) / fovRatio;
double fW = fH * aspectRatio * mScaleFactor;
double left = -fW;
double right = fW;
double bottom = -fH;
double top = fH;
VSMatrix fmat(1);
fmat.ortho((float)left, (float)right, (float)bottom, (float)top, (float)zNear, (float)zFar);
return fmat;
}
else if (mShiftFactor == 0)
{ {
float fovy = (float)(2 * RAD2DEG(atan(tan(DEG2RAD(fov) / 2) / fovRatio))); float fovy = (float)(2 * RAD2DEG(atan(tan(DEG2RAD(fov) / 2) / fovRatio)));
result.perspective(fovy, aspectRatio, screen->GetZNear(), screen->GetZFar()); result.perspective(fovy, aspectRatio, screen->GetZNear(), screen->GetZFar());

View file

@ -27,7 +27,7 @@ struct VREyeInfo
float mShiftFactor; float mShiftFactor;
float mScaleFactor; float mScaleFactor;
VSMatrix GetProjection(float fov, float aspectRatio, float fovRatio) const; VSMatrix GetProjection(float fov, float aspectRatio, float fovRatio, bool iso_ortho) const;
DVector3 GetViewShift(float yaw) const; DVector3 GetViewShift(float yaw) const;
private: private:
float getShift() const; float getShift() const;

View file

@ -50,6 +50,7 @@
//=========================================================================== //===========================================================================
CVAR(Bool, gl_aalines, false, CVAR_ARCHIVE) CVAR(Bool, gl_aalines, false, CVAR_ARCHIVE)
CVAR(Bool, hw_2dmip, true, CVAR_ARCHIVE)
void Draw2D(F2DDrawer* drawer, FRenderState& state) void Draw2D(F2DDrawer* drawer, FRenderState& state)
{ {
@ -71,6 +72,8 @@ void Draw2D(F2DDrawer* drawer, FRenderState& state, int x, int y, int width, int
state.EnableMultisampling(false); state.EnableMultisampling(false);
state.EnableLineSmooth(gl_aalines); state.EnableLineSmooth(gl_aalines);
bool cache_hw_2dmip = hw_2dmip; // cache cvar lookup so it's not done in a loop
auto &vertices = drawer->mVertices; auto &vertices = drawer->mVertices;
auto &indices = drawer->mIndices; auto &indices = drawer->mIndices;
auto &commands = drawer->mData; auto &commands = drawer->mData;
@ -180,7 +183,7 @@ void Draw2D(F2DDrawer* drawer, FRenderState& state, int x, int y, int width, int
auto flags = cmd.mTexture->GetUseType() >= ETextureType::Special? UF_None : cmd.mTexture->GetUseType() == ETextureType::FontChar? UF_Font : UF_Texture; auto flags = cmd.mTexture->GetUseType() >= ETextureType::Special? UF_None : cmd.mTexture->GetUseType() == ETextureType::FontChar? UF_Font : UF_Texture;
auto scaleflags = cmd.mFlags & F2DDrawer::DTF_Indexed ? CTF_Indexed : 0; auto scaleflags = cmd.mFlags & F2DDrawer::DTF_Indexed ? CTF_Indexed : 0;
state.SetMaterial(cmd.mTexture, flags, scaleflags, cmd.mFlags & F2DDrawer::DTF_Wrap ? CLAMP_NONE : CLAMP_XY_NOMIP, cmd.mTranslationId, -1); state.SetMaterial(cmd.mTexture, flags, scaleflags, cmd.mFlags & F2DDrawer::DTF_Wrap ? CLAMP_NONE : (cache_hw_2dmip ? CLAMP_XY : CLAMP_XY_NOMIP), cmd.mTranslationId, -1);
state.EnableTexture(true); state.EnableTexture(true);
// Canvas textures are stored upside down // Canvas textures are stored upside down

View file

@ -947,6 +947,7 @@ PPCustomShaderInstance::PPCustomShaderInstance(PostProcessShader *desc) : Desc(d
case PostProcessUniformType::Int: AddUniformField(offset, name, UniformType::Int, sizeof(int)); break; case PostProcessUniformType::Int: AddUniformField(offset, name, UniformType::Int, sizeof(int)); break;
case PostProcessUniformType::Vec2: AddUniformField(offset, name, UniformType::Vec2, sizeof(float) * 2); break; case PostProcessUniformType::Vec2: AddUniformField(offset, name, UniformType::Vec2, sizeof(float) * 2); break;
case PostProcessUniformType::Vec3: AddUniformField(offset, name, UniformType::Vec3, sizeof(float) * 3, sizeof(float) * 4); break; case PostProcessUniformType::Vec3: AddUniformField(offset, name, UniformType::Vec3, sizeof(float) * 3, sizeof(float) * 4); break;
case PostProcessUniformType::Vec4: AddUniformField(offset, name, UniformType::Vec4, sizeof(float) * 4); break;
default: break; default: break;
} }
} }
@ -1085,6 +1086,13 @@ void PPCustomShaderInstance::SetUniforms(PPRenderState *renderstate)
fValues[2] = (float)pair->Value.Values[2]; fValues[2] = (float)pair->Value.Values[2];
memcpy(dst, fValues, sizeof(float) * 3); memcpy(dst, fValues, sizeof(float) * 3);
break; break;
case PostProcessUniformType::Vec4:
fValues[0] = (float)pair->Value.Values[0];
fValues[1] = (float)pair->Value.Values[1];
fValues[2] = (float)pair->Value.Values[2];
fValues[3] = (float)pair->Value.Values[3];
memcpy(dst, fValues, sizeof(float) * 4);
break;
default: default:
break; break;
} }

View file

@ -116,6 +116,31 @@ DEFINE_ACTION_FUNCTION(_PPShader, SetUniform3f)
return 0; return 0;
} }
DEFINE_ACTION_FUNCTION(_PPShader, SetUniform4f)
{
PARAM_PROLOGUE;
PARAM_STRING(shaderName);
PARAM_STRING(uniformName);
PARAM_FLOAT(x);
PARAM_FLOAT(y);
PARAM_FLOAT(z);
PARAM_FLOAT(w);
for (unsigned int i = 0; i < PostProcessShaders.Size(); i++)
{
PostProcessShader &shader = PostProcessShaders[i];
if (shader.Name == shaderName)
{
double *vec4 = shader.Uniforms[uniformName].Values;
vec4[0] = x;
vec4[1] = y;
vec4[2] = z;
vec4[3] = w;
}
}
return 0;
}
DEFINE_ACTION_FUNCTION(_PPShader, SetUniform1i) DEFINE_ACTION_FUNCTION(_PPShader, SetUniform1i)
{ {
PARAM_PROLOGUE; PARAM_PROLOGUE;

View file

@ -9,7 +9,8 @@ enum class PostProcessUniformType
Int, Int,
Float, Float,
Vec2, Vec2,
Vec3 Vec3,
Vec4
}; };
struct PostProcessUniformValue struct PostProcessUniformValue

View file

@ -1,7 +1,7 @@
/*--------------------------------------------------------------------------- /*---------------------------------------------------------------------------
** **
** Copyright(C) 2017 Magnus Norddahl ** Copyright(C) 2017 Magnus Norddahl
** Copyright(C) 2017-2020 Rachael Alexanderson ** Copyright(C) 2017-2024 Rachael Alexanderson
** All rights reserved. ** All rights reserved.
** **
** Redistribution and use in source and binary forms, with or without ** Redistribution and use in source and binary forms, with or without
@ -98,6 +98,33 @@ namespace
{ {
return (uint32_t)((float)inheight * v_MinimumToFill(inwidth, inheight)); return (uint32_t)((float)inheight * v_MinimumToFill(inwidth, inheight));
} }
float v_MinimumToFill2(uint32_t inwidth, uint32_t inheight)
{
// sx = screen x dimension, sy = same for y
float sx = (float)inwidth * 1.2f, sy = (float)inheight;
static float lastsx = 0., lastsy = 0., result = 0.;
if (lastsx != sx || lastsy != sy)
{
if (sx <= 0. || sy <= 0.)
return 1.; // prevent x/0 error
// set absolute minimum scale to fill the entire screen but get as close to 640x400 as possible
float ssx = (float)(VID_MIN_UI_WIDTH) / 1.2f / sx, ssy = (float)(VID_MIN_UI_HEIGHT) / sy;
result = (ssx < ssy) ? ssy : ssx;
lastsx = sx;
lastsy = sy;
}
return result;
}
inline uint32_t v_mfillX2(uint32_t inwidth, uint32_t inheight)
{
return (uint32_t)((float)inwidth * v_MinimumToFill2(inwidth, inheight) * 1.2);
}
inline uint32_t v_mfillY2(uint32_t inwidth, uint32_t inheight)
{
return (uint32_t)((float)inheight * v_MinimumToFill2(inwidth, inheight));
}
inline void refresh_minimums() inline void refresh_minimums()
{ {
// specialUI is tracking a state where high-res console fonts are actually required, and // specialUI is tracking a state where high-res console fonts are actually required, and
@ -128,16 +155,17 @@ namespace
// the odd formatting of this struct definition is meant to resemble a table header. set your tab stops to 4 when editing this file. // the odd formatting of this struct definition is meant to resemble a table header. set your tab stops to 4 when editing this file.
struct v_ScaleTable struct v_ScaleTable
{ bool isValid; uint32_t(*GetScaledWidth)(uint32_t Width, uint32_t Height); uint32_t(*GetScaledHeight)(uint32_t Width, uint32_t Height); float pixelAspect; bool isCustom; }; { bool isValid; uint32_t(*GetScaledWidth)(uint32_t Width, uint32_t Height); uint32_t(*GetScaledHeight)(uint32_t Width, uint32_t Height); float pixelAspect; bool isCustom; };
v_ScaleTable vScaleTable[] = v_ScaleTable vScaleTable[] =
{ {
{ true, [](uint32_t Width, uint32_t Height)->uint32_t { return Width; }, [](uint32_t Width, uint32_t Height)->uint32_t { return Height; }, 1.0f, false }, // 0 - Native { true, [](uint32_t Width, uint32_t Height)->uint32_t { return Width; }, [](uint32_t Width, uint32_t Height)->uint32_t { return Height; }, 1.0f, false }, // 0 - Native
{ true, [](uint32_t Width, uint32_t Height)->uint32_t { return v_mfillX(Width, Height); }, [](uint32_t Width, uint32_t Height)->uint32_t { return v_mfillY(Width, Height); }, 1.0f, false }, // 6 - Minimum Scale to Fill Entire Screen { true, [](uint32_t Width, uint32_t Height)->uint32_t { return v_mfillX(Width, Height); }, [](uint32_t Width, uint32_t Height)->uint32_t { return v_mfillY(Width, Height); }, 1.0f, false }, // 1 - Minimum Scale to Fill Entire Screen
{ true, [](uint32_t Width, uint32_t Height)->uint32_t { return 640; }, [](uint32_t Width, uint32_t Height)->uint32_t { return 400; }, 1.2f, false }, // 2 - 640x400 (formerly 320x200) { true, [](uint32_t Width, uint32_t Height)->uint32_t { return 640; }, [](uint32_t Width, uint32_t Height)->uint32_t { return 400; }, 1.2f, false }, // 2 - 640x400 (formerly 320x200)
{ true, [](uint32_t Width, uint32_t Height)->uint32_t { return 960; }, [](uint32_t Width, uint32_t Height)->uint32_t { return 600; }, 1.2f, false }, // 3 - 960x600 (formerly 640x400) { true, [](uint32_t Width, uint32_t Height)->uint32_t { return 960; }, [](uint32_t Width, uint32_t Height)->uint32_t { return 600; }, 1.2f, false }, // 3 - 960x600 (formerly 640x400)
{ true, [](uint32_t Width, uint32_t Height)->uint32_t { return 1280; }, [](uint32_t Width, uint32_t Height)->uint32_t { return 800; }, 1.2f, false }, // 4 - 1280x800 { true, [](uint32_t Width, uint32_t Height)->uint32_t { return 1280; }, [](uint32_t Width, uint32_t Height)->uint32_t { return 800; }, 1.2f, false }, // 4 - 1280x800
{ true, [](uint32_t Width, uint32_t Height)->uint32_t { return vid_scale_customwidth; }, [](uint32_t Width, uint32_t Height)->uint32_t { return vid_scale_customheight; }, 1.0f, true }, // 5 - Custom { true, [](uint32_t Width, uint32_t Height)->uint32_t { return vid_scale_customwidth; }, [](uint32_t Width, uint32_t Height)->uint32_t { return vid_scale_customheight; }, 1.0f, true }, // 5 - Custom
{ true, [](uint32_t Width, uint32_t Height)->uint32_t { return 320; }, [](uint32_t Width, uint32_t Height)->uint32_t { return 200; }, 1.2f, false }, // 7 - 320x200 { true, [](uint32_t Width, uint32_t Height)->uint32_t { return 320; }, [](uint32_t Width, uint32_t Height)->uint32_t { return 200; }, 1.2f, false }, // 6 - 320x200
{ true, [](uint32_t Width, uint32_t Height)->uint32_t { return v_mfillX2(Width, Height) * 1.2f; }, [](uint32_t Width, uint32_t Height)->uint32_t { return v_mfillY2(Width, Height); }, 1.2f, false }, // 7 - Minimum Scale to Fill Entire Screen (1.2)
}; };
bool isOutOfBounds(int x) bool isOutOfBounds(int x)
{ {
@ -248,7 +276,7 @@ CCMD (vid_scaletoheight)
} }
} }
inline bool atob(char* I) inline bool atob(const char* I)
{ {
if (stricmp (I, "true") == 0 || stricmp (I, "1") == 0) if (stricmp (I, "true") == 0 || stricmp (I, "1") == 0)
return true; return true;

View file

@ -1842,6 +1842,12 @@ FxExpression *FxTypeCast::Resolve(FCompileContext &ctx)
{ {
goto basereturn; goto basereturn;
} }
else if (ctx.Version >= MakeVersion(4, 15, 0) && basex->ValueType == TypeNullPtr && (ValueType == TypeSpriteID || ValueType == TypeTextureID || ValueType == TypeTranslationID))
{
delete basex;
basex = new FxConstant(0, ScriptPosition);
goto basereturn;
}
else if (IsFloat()) else if (IsFloat())
{ {
FxExpression *x = new FxFloatCast(basex); FxExpression *x = new FxFloatCast(basex);
@ -2828,83 +2834,104 @@ FxExpression *FxAssign::Resolve(FCompileContext &ctx)
// Special case: Assignment to a bitfield. // Special case: Assignment to a bitfield.
IsBitWrite = Base->GetBitValue(); IsBitWrite = Base->GetBitValue();
if (IsBitWrite >= 0x10000)
{
// internal flags - need more here
IsBitWrite &= 0xffff;
}
return this; return this;
} }
ExpEmit FxAssign::Emit(VMFunctionBuilder *build) ExpEmit FxAssign::Emit(VMFunctionBuilder *build)
{ {
static const uint8_t loadops[] = { OP_LK, OP_LKF, OP_LKS, OP_LKP }; if (IsBitWrite < 64)
assert(Base->ValueType->GetRegType() == Right->ValueType->GetRegType());
ExpEmit pointer = Base->Emit(build);
Address = pointer;
ExpEmit result;
bool intconst = false;
int intconstval = 0;
if (Right->isConstant() && Right->ValueType->GetRegType() == REGT_INT)
{ {
intconst = true; static const uint8_t loadops[] = { OP_LK, OP_LKF, OP_LKS, OP_LKP };
intconstval = static_cast<FxConstant*>(Right)->GetValue().GetInt(); assert(Base->ValueType->GetRegType() == Right->ValueType->GetRegType());
result.Konst = true;
result.RegType = REGT_INT;
}
else
{
result = Right->Emit(build);
}
assert(result.RegType <= REGT_TYPE);
if (pointer.Target) ExpEmit pointer = Base->Emit(build);
{ Address = pointer;
if (result.Konst)
ExpEmit result;
bool intconst = false;
int intconstval = 0;
if (Right->isConstant() && Right->ValueType->GetRegType() == REGT_INT)
{ {
if (intconst) build->EmitLoadInt(pointer.RegNum, intconstval); intconst = true;
else build->Emit(loadops[result.RegType], pointer.RegNum, result.RegNum); intconstval = static_cast<FxConstant*>(Right)->GetValue().GetInt();
result.Konst = true;
result.RegType = REGT_INT;
} }
else else
{ {
build->Emit(Right->ValueType->GetMoveOp(), pointer.RegNum, result.RegNum); result = Right->Emit(build);
} }
} assert(result.RegType <= REGT_TYPE);
else
{ if (pointer.Target)
if (result.Konst) {
if (result.Konst)
{
if (intconst) build->EmitLoadInt(pointer.RegNum, intconstval);
else build->Emit(loadops[result.RegType], pointer.RegNum, result.RegNum);
}
else
{
build->Emit(Right->ValueType->GetMoveOp(), pointer.RegNum, result.RegNum);
}
}
else
{
if (result.Konst)
{
ExpEmit temp(build, result.RegType);
if (intconst) build->EmitLoadInt(temp.RegNum, intconstval);
else build->Emit(loadops[result.RegType], temp.RegNum, result.RegNum);
result.Free(build);
result = temp;
}
if (IsBitWrite == -1)
{
build->Emit(Base->ValueType->GetStoreOp(), pointer.RegNum, result.RegNum, build->GetConstantInt(0));
}
else
{
build->Emit(OP_SBIT, pointer.RegNum, result.RegNum, 1 << IsBitWrite);
}
}
if (AddressRequested)
{ {
ExpEmit temp(build, result.RegType);
if (intconst) build->EmitLoadInt(temp.RegNum, intconstval);
else build->Emit(loadops[result.RegType], temp.RegNum, result.RegNum);
result.Free(build); result.Free(build);
result = temp; return pointer;
} }
if (IsBitWrite == -1) pointer.Free(build);
{
build->Emit(Base->ValueType->GetStoreOp(), pointer.RegNum, result.RegNum, build->GetConstantInt(0)); if (intconst)
{ //fix int constant return for assignment
return Right->Emit(build);
} }
else else
{ {
build->Emit(OP_SBIT, pointer.RegNum, result.RegNum, 1 << IsBitWrite); return result;
} }
}
if (AddressRequested)
{
result.Free(build);
return pointer;
}
pointer.Free(build);
if(intconst)
{ //fix int constant return for assignment
return Right->Emit(build);
} }
else else
{ {
return result; VMFunction* callfunc;
auto sym = FindBuiltinFunction(NAME_HandleDeprecatedFlags);
assert(sym);
callfunc = sym->Variants[0].Implementation;
FunctionCallEmitter emitters(callfunc);
emitters.AddParameter(build, Base);
emitters.AddParameter(build, Right);
emitters.AddParameterIntConst(IsBitWrite - 64);
return emitters.EmitCall(build);
} }
} }
@ -2934,23 +2961,40 @@ FxExpression *FxAssignSelf::Resolve(FCompileContext &ctx)
ExpEmit FxAssignSelf::Emit(VMFunctionBuilder *build) ExpEmit FxAssignSelf::Emit(VMFunctionBuilder *build)
{ {
ExpEmit pointer = Assignment->Address; // FxAssign should have already emitted it if (Assignment->IsBitWrite < 64)
if (!pointer.Target)
{ {
ExpEmit out(build, ValueType->GetRegType(), ValueType->GetRegCount()); ExpEmit pointer = Assignment->Address; // FxAssign should have already emitted it
if (Assignment->IsBitWrite != -1) if (!pointer.Target)
{ {
build->Emit(OP_LBIT, out.RegNum, pointer.RegNum, 1 << Assignment->IsBitWrite); ExpEmit out(build, ValueType->GetRegType(), ValueType->GetRegCount());
if (Assignment->IsBitWrite == -1)
{
build->Emit(ValueType->GetLoadOp(), out.RegNum, pointer.RegNum, build->GetConstantInt(0));
}
else
{
build->Emit(OP_LBIT, out.RegNum, pointer.RegNum, 1 << Assignment->IsBitWrite);
}
return out;
} }
else else
{ {
build->Emit(ValueType->GetLoadOp(), out.RegNum, pointer.RegNum, build->GetConstantInt(0)); return pointer;
} }
return out;
} }
else else
{ {
return pointer; VMFunction* callfunc;
auto sym = FindBuiltinFunction(NAME_CheckDeprecatedFlags);
assert(sym);
callfunc = sym->Variants[0].Implementation;
FunctionCallEmitter emitters(callfunc);
emitters.AddParameter(build, Assignment->Base);
emitters.AddParameterIntConst(Assignment->IsBitWrite - 64);
emitters.AddReturn(REGT_INT);
return emitters.EmitCall(build);
} }
} }
@ -7728,56 +7772,73 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx)
ExpEmit FxStructMember::Emit(VMFunctionBuilder *build) ExpEmit FxStructMember::Emit(VMFunctionBuilder *build)
{ {
ExpEmit obj = classx->Emit(build); if (membervar->BitValue < 64 || AddressRequested)
assert(obj.RegType == REGT_POINTER);
if (obj.Konst)
{ {
// If the situation where we are dereferencing a constant ExpEmit obj = classx->Emit(build);
// pointer is common, then it would probably be worthwhile assert(obj.RegType == REGT_POINTER);
// to add new opcodes for those. But as of right now, I
// don't expect it to be a particularly common case.
ExpEmit newobj(build, REGT_POINTER);
build->Emit(OP_LKP, newobj.RegNum, obj.RegNum);
obj = newobj;
}
if (membervar->Flags & VARF_Meta) if (obj.Konst)
{
obj.Free(build);
ExpEmit meta(build, REGT_POINTER);
build->Emit(OP_META, meta.RegNum, obj.RegNum);
obj = meta;
}
if (AddressRequested)
{
if (membervar->Offset == 0)
{ {
return obj; // If the situation where we are dereferencing a constant
// pointer is common, then it would probably be worthwhile
// to add new opcodes for those. But as of right now, I
// don't expect it to be a particularly common case.
ExpEmit newobj(build, REGT_POINTER);
build->Emit(OP_LKP, newobj.RegNum, obj.RegNum);
obj = newobj;
}
if (membervar->Flags & VARF_Meta)
{
obj.Free(build);
ExpEmit meta(build, REGT_POINTER);
build->Emit(OP_META, meta.RegNum, obj.RegNum);
obj = meta;
}
if (AddressRequested)
{
if (membervar->Offset == 0)
{
return obj;
}
obj.Free(build);
ExpEmit out(build, REGT_POINTER);
build->Emit(OP_ADDA_RK, out.RegNum, obj.RegNum, build->GetConstantInt((int)membervar->Offset));
return out;
}
int offsetreg = build->GetConstantInt((int)membervar->Offset);
ExpEmit loc(build, membervar->Type->GetRegType(), membervar->Type->GetRegCount());
if (membervar->BitValue == -1)
{
build->Emit(membervar->Type->GetLoadOp(), loc.RegNum, obj.RegNum, offsetreg);
}
else
{
ExpEmit out(build, REGT_POINTER);
build->Emit(OP_ADDA_RK, out.RegNum, obj.RegNum, offsetreg);
build->Emit(OP_LBIT, loc.RegNum, out.RegNum, 1 << membervar->BitValue);
out.Free(build);
} }
obj.Free(build); obj.Free(build);
ExpEmit out(build, REGT_POINTER); return loc;
build->Emit(OP_ADDA_RK, out.RegNum, obj.RegNum, build->GetConstantInt((int)membervar->Offset));
return out;
}
int offsetreg = build->GetConstantInt((int)membervar->Offset);
ExpEmit loc(build, membervar->Type->GetRegType(), membervar->Type->GetRegCount());
if (membervar->BitValue == -1)
{
build->Emit(membervar->Type->GetLoadOp(), loc.RegNum, obj.RegNum, offsetreg);
} }
else else
{ {
ExpEmit out(build, REGT_POINTER); VMFunction* callfunc;
build->Emit(OP_ADDA_RK, out.RegNum, obj.RegNum, offsetreg); auto sym = FindBuiltinFunction(NAME_CheckDeprecatedFlags);
build->Emit(OP_LBIT, loc.RegNum, out.RegNum, 1 << membervar->BitValue);
out.Free(build); assert(sym);
callfunc = sym->Variants[0].Implementation;
FunctionCallEmitter emitters(callfunc);
emitters.AddParameter(build, classx);
emitters.AddParameterIntConst(membervar->BitValue - 64);
emitters.AddReturn(REGT_INT);
return emitters.EmitCall(build);
} }
obj.Free(build);
return loc;
} }
@ -8213,8 +8274,12 @@ static bool CheckFunctionCompatiblity(FScriptPosition &ScriptPosition, PFunction
FxFunctionCall::FxFunctionCall(FName methodname, FName rngname, FArgumentList &&args, const FScriptPosition &pos) FxFunctionCall::FxFunctionCall(FName methodname, FName rngname, FArgumentList &&args, const FScriptPosition &pos)
: FxExpression(EFX_FunctionCall, pos) : FxExpression(EFX_FunctionCall, pos)
{ {
const bool isClient = methodname == NAME_CRandom || methodname == NAME_CFRandom
|| methodname == NAME_CRandomPick || methodname == NAME_CFRandomPick
|| methodname == NAME_CRandom2 || methodname == NAME_CSetRandomSeed;
MethodName = methodname; MethodName = methodname;
RNG = &pr_exrandom; RNG = isClient ? &M_Random : &pr_exrandom;
ArgList = std::move(args); ArgList = std::move(args);
if (rngname != NAME_None) if (rngname != NAME_None)
{ {
@ -8226,7 +8291,16 @@ FxFunctionCall::FxFunctionCall(FName methodname, FName rngname, FArgumentList &&
case NAME_FRandomPick: case NAME_FRandomPick:
case NAME_Random2: case NAME_Random2:
case NAME_SetRandomSeed: case NAME_SetRandomSeed:
RNG = FRandom::StaticFindRNG(rngname.GetChars()); RNG = FRandom::StaticFindRNG(rngname.GetChars(), false);
break;
case NAME_CRandom:
case NAME_CFRandom:
case NAME_CRandomPick:
case NAME_CFRandomPick:
case NAME_CRandom2:
case NAME_CSetRandomSeed:
RNG = FRandom::StaticFindRNG(rngname.GetChars(), true);
break; break;
default: default:
@ -8443,6 +8517,7 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
case NAME_State: case NAME_State:
case NAME_SpriteID: case NAME_SpriteID:
case NAME_TextureID: case NAME_TextureID:
case NAME_TranslationID:
if (CheckArgSize(MethodName, ArgList, 1, 1, ScriptPosition)) if (CheckArgSize(MethodName, ArgList, 1, 1, ScriptPosition))
{ {
PType *type = PType *type =
@ -8492,13 +8567,22 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
} }
break; break;
case NAME_CSetRandomSeed:
if (CheckArgSize(NAME_CRandom, ArgList, 1, 1, ScriptPosition))
{
func = new FxRandomSeed(RNG, ArgList[0], ScriptPosition, ctx.FromDecorate);
ArgList[0] = nullptr;
}
break;
case NAME_Random: case NAME_Random:
case NAME_CRandom:
// allow calling Random without arguments to default to (0, 255) // allow calling Random without arguments to default to (0, 255)
if (ArgList.Size() == 0) if (ArgList.Size() == 0)
{ {
func = new FxRandom(RNG, new FxConstant(0, ScriptPosition), new FxConstant(255, ScriptPosition), ScriptPosition, ctx.FromDecorate); func = new FxRandom(RNG, new FxConstant(0, ScriptPosition), new FxConstant(255, ScriptPosition), ScriptPosition, ctx.FromDecorate);
} }
else if (CheckArgSize(NAME_Random, ArgList, 2, 2, ScriptPosition)) else if (CheckArgSize(MethodName, ArgList, 2, 2, ScriptPosition))
{ {
func = new FxRandom(RNG, ArgList[0], ArgList[1], ScriptPosition, ctx.FromDecorate); func = new FxRandom(RNG, ArgList[0], ArgList[1], ScriptPosition, ctx.FromDecorate);
ArgList[0] = ArgList[1] = nullptr; ArgList[0] = ArgList[1] = nullptr;
@ -8506,7 +8590,8 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
break; break;
case NAME_FRandom: case NAME_FRandom:
if (CheckArgSize(NAME_FRandom, ArgList, 2, 2, ScriptPosition)) case NAME_CFRandom:
if (CheckArgSize(MethodName, ArgList, 2, 2, ScriptPosition))
{ {
func = new FxFRandom(RNG, ArgList[0], ArgList[1], ScriptPosition); func = new FxFRandom(RNG, ArgList[0], ArgList[1], ScriptPosition);
ArgList[0] = ArgList[1] = nullptr; ArgList[0] = ArgList[1] = nullptr;
@ -8515,14 +8600,17 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
case NAME_RandomPick: case NAME_RandomPick:
case NAME_FRandomPick: case NAME_FRandomPick:
case NAME_CRandomPick:
case NAME_CFRandomPick:
if (CheckArgSize(MethodName, ArgList, 1, -1, ScriptPosition)) if (CheckArgSize(MethodName, ArgList, 1, -1, ScriptPosition))
{ {
func = new FxRandomPick(RNG, ArgList, MethodName == NAME_FRandomPick, ScriptPosition, ctx.FromDecorate); func = new FxRandomPick(RNG, ArgList, MethodName == NAME_FRandomPick || MethodName == NAME_CFRandomPick, ScriptPosition, ctx.FromDecorate);
} }
break; break;
case NAME_Random2: case NAME_Random2:
if (CheckArgSize(NAME_Random2, ArgList, 0, 1, ScriptPosition)) case NAME_CRandom2:
if (CheckArgSize(MethodName, ArgList, 0, 1, ScriptPosition))
{ {
func = new FxRandom2(RNG, ArgList.Size() == 0? nullptr : ArgList[0], ScriptPosition, ctx.FromDecorate); func = new FxRandom2(RNG, ArgList.Size() == 0? nullptr : ArgList[0], ScriptPosition, ctx.FromDecorate);
if (ArgList.Size() > 0) ArgList[0] = nullptr; if (ArgList.Size() > 0) ArgList[0] = nullptr;
@ -8795,7 +8883,24 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
return Self; return Self;
} }
} }
else if (Self->ValueType == TypeTextureID) else if (ctx.Version >= MakeVersion(4, 15, 0) && Self->ValueType == TypeSound && MethodName == NAME_IsValid)
{
if (ArgList.Size() > 0)
{
ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars());
delete this;
return nullptr;
}
Self->ValueType = TypeSInt32; // treat as integer
FxExpression *x = new FxCompareRel('>', Self, new FxConstant(0, ScriptPosition));
Self = nullptr;
SAFE_RESOLVE(x, ctx);
delete this;
return x;
}
else if (Self->ValueType == TypeTextureID || (ctx.Version >= MakeVersion(4, 15, 0) && (Self->ValueType == TypeTranslationID)))
{ {
if (MethodName == NAME_IsValid || MethodName == NAME_IsNull || MethodName == NAME_Exists || MethodName == NAME_SetInvalid || MethodName == NAME_SetNull) if (MethodName == NAME_IsValid || MethodName == NAME_IsNull || MethodName == NAME_Exists || MethodName == NAME_SetInvalid || MethodName == NAME_SetNull)
{ {
@ -8838,6 +8943,67 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
} }
} }
else if (ctx.Version >= MakeVersion(4, 15, 0) && Self->ValueType == TypeSpriteID)
{
if (MethodName == NAME_IsValid || MethodName == NAME_IsEmpty || MethodName == NAME_IsFixed || MethodName == NAME_IsKeep
|| MethodName == NAME_Exists
|| MethodName == NAME_SetInvalid || MethodName == NAME_SetEmpty || MethodName == NAME_SetFixed || MethodName == NAME_SetKeep)
{
if (ArgList.Size() > 0)
{
ScriptPosition.Message(MSG_ERROR, "Too many parameters in call to %s", MethodName.GetChars());
delete this;
return nullptr;
}
// No need to create a dedicated node here, all builtins map directly to trivial operations.
Self->ValueType = TypeSInt32; // all builtins treat the texture index as integer.
FxExpression *x = nullptr;
switch (MethodName.GetIndex())
{
case NAME_IsValid:
x = new FxCompareRel(TK_Geq, Self, new FxConstant(0, ScriptPosition));
break;
case NAME_IsEmpty: // TNT1
x = new FxCompareEq(TK_Eq, Self, new FxConstant(0, ScriptPosition));
break;
case NAME_IsFixed: // "----"
x = new FxCompareEq(TK_Eq, Self, new FxConstant(1, ScriptPosition));
break;
case NAME_IsKeep: // "####"
x = new FxCompareEq(TK_Eq, Self, new FxConstant(2, ScriptPosition));
break;
case NAME_Exists:
x = new FxCompareRel(TK_Geq, Self, new FxConstant(3, ScriptPosition));
break;
case NAME_SetInvalid:
x = new FxAssign(Self, new FxConstant(-1, ScriptPosition));
break;
case NAME_SetEmpty: // TNT1
x = new FxAssign(Self, new FxConstant(0, ScriptPosition));
break;
case NAME_SetFixed: // "----"
x = new FxAssign(Self, new FxConstant(1, ScriptPosition));
break;
case NAME_SetKeep: // "####"
x = new FxAssign(Self, new FxConstant(2, ScriptPosition));
break;
}
Self = nullptr;
SAFE_RESOLVE(x, ctx);
if (MethodName == NAME_SetInvalid || MethodName == NAME_SetEmpty || MethodName == NAME_SetFixed || MethodName == NAME_SetKeep) x->ValueType = TypeVoid; // override the default type of the assignment operator.
delete this;
return x;
}
}
else if (Self->IsVector()) else if (Self->IsVector())
{ {
// handle builtins: Vectors got 5. // handle builtins: Vectors got 5.
@ -8912,7 +9078,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
} }
else else
{ {
if (PFunction **Override; ctx.Version >= MakeVersion(4, 11, 0) && (Override = static_cast<PDynArray*>(Self->ValueType)->FnOverrides.CheckKey(MethodName))) if (PFunction **Override; (Override = static_cast<PDynArray*>(Self->ValueType)->FnOverrides.CheckKey(MethodName)))
{ {
afd_override = *Override; afd_override = *Override;
} }
@ -9544,7 +9710,9 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
if(FnPtrCall) static_cast<VMScriptFunction*>(ctx.Function->Variants[0].Implementation)->blockJit = true; if(FnPtrCall) static_cast<VMScriptFunction*>(ctx.Function->Variants[0].Implementation)->blockJit = true;
int implicit = Function->GetImplicitArgs(); unsigned implicit = Function->GetImplicitArgs();
bool relaxed_named_arugments = (ctx.Version >= MakeVersion(4, 13));
if (!CheckAccessibility(ctx.Version)) if (!CheckAccessibility(ctx.Version))
{ {
@ -9576,21 +9744,128 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
CallingFunction = ctx.Function; CallingFunction = ctx.Function;
if (ArgList.Size() > 0) if (ArgList.Size() > 0)
{ {
if (argtypes.Size() == 0) 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()); ScriptPosition.Message(MSG_ERROR, "Too many arguments in call to %s", Function->SymbolName.GetChars());
delete this; delete this;
return nullptr; return nullptr;
} }
bool isvararg = (argtypes.Last() == nullptr);
{
TDeletingArray<FxExpression*> OrderedArgs;
const unsigned count = (argtypes.Size() - implicit) - isvararg;
OrderedArgs.Resize(count);
memset(OrderedArgs.Data(), 0, sizeof(FxExpression*) * count);
unsigned index = 0;
unsigned n = ArgList.Size();
for(unsigned i = 0; i < n; i++)
{
if(ArgList[i]->ExprType == EFX_NamedNode)
{
if(FnPtrCall)
{
ScriptPosition.Message(MSG_ERROR, "Named arguments not supported in function pointer calls");
delete this;
return nullptr;
}
else if((index >= count) && isvararg)
{
ScriptPosition.Message(MSG_ERROR, "Cannot use a named argument in the varargs part of the parameter list.");
delete this;
return nullptr;
}
else
{
FName name = static_cast<FxNamedNode *>(ArgList[i])->name;
if(argnames[index + implicit] != name)
{
unsigned j;
for (j = 0; j < count; j++)
{
if (argnames[j + implicit] == name)
{
if(!relaxed_named_arugments && !(argflags[j + implicit] & VARF_Optional))
{
ScriptPosition.Message(MSG_ERROR, "Cannot use a named argument here - not all required arguments have been passed.");
}
else if(!relaxed_named_arugments && j < index)
{
ScriptPosition.Message(MSG_ERROR, "Named argument %s comes before current position in argument list.", name.GetChars());
}
// i don't think this needs any further optimization?
// O(N^2) complexity technically but N isn't likely to be large,
// and the check itself is just an int comparison, so it should be fine
index = j;
break;
}
}
if(j == count)
{
ScriptPosition.Message(MSG_ERROR, "Named argument %s not found.", name.GetChars());
delete this;
return nullptr;
}
}
else if(!relaxed_named_arugments && !(argflags[index + implicit] & VARF_Optional))
{
ScriptPosition.Message(MSG_ERROR, "Cannot use a named argument here - not all required arguments have been passed.");
}
}
}
if(index >= count)
{
if(isvararg)
{
OrderedArgs.Push(ArgList[i]);
ArgList[i] = nullptr;
index++;
}
else
{
ScriptPosition.Message(MSG_ERROR, "Too many arguments in call to %s", Function->SymbolName.GetChars());
delete this;
return nullptr;
}
}
else
{
if(ArgList[i]->ExprType == EFX_NamedNode)
{
auto * node = static_cast<FxNamedNode *>(ArgList[i]);
OrderedArgs[index] = node->value;
node->value = nullptr;
}
else
{
OrderedArgs[index] = ArgList[i];
}
ArgList[i] = nullptr;
index++;
}
}
ArgList = std::move(OrderedArgs);
}
bool foundvarargs = false; bool foundvarargs = false;
PType * type = nullptr; PType * type = nullptr;
int flag = 0; int flag = 0;
if (argtypes.Size() > 0 && argtypes.Last() != nullptr && ArgList.Size() + implicit > argtypes.Size())
int defaults_index = 0;
for(unsigned i = 0; i < implicit; i++)
{ {
ScriptPosition.Message(MSG_ERROR, "Too many arguments in call to %s", Function->SymbolName.GetChars()); defaults_index += argtypes[i]->GetRegCount();
delete this;
return nullptr;
} }
for (unsigned i = 0; i < ArgList.Size(); i++) for (unsigned i = 0; i < ArgList.Size(); i++)
@ -9608,94 +9883,45 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
} }
assert(type != nullptr); assert(type != nullptr);
if (ArgList[i]->ExprType == EFX_NamedNode) if(!foundvarargs)
{ {
if(FnPtrCall) if(ArgList[i] == nullptr)
{ {
ScriptPosition.Message(MSG_ERROR, "Named arguments not supported in function pointer calls"); if(!(flag & VARF_Optional))
delete this;
return nullptr;
}
if (!(flag & VARF_Optional))
{
ScriptPosition.Message(MSG_ERROR, "Cannot use a named argument here - not all required arguments have been passed.");
delete this;
return nullptr;
}
if (foundvarargs)
{
ScriptPosition.Message(MSG_ERROR, "Cannot use a named argument in the varargs part of the parameter list.");
delete this;
return nullptr;
}
unsigned j;
bool done = false;
FName name = static_cast<FxNamedNode *>(ArgList[i])->name;
for (j = 0; j < argnames.Size() - implicit; j++)
{
if (argnames[j + implicit] == name)
{ {
if (j < i) ScriptPosition.Message(MSG_ERROR, "Required argument %s has not been passed in call to %s", argnames[i + implicit].GetChars(), Function->SymbolName.GetChars());
delete this;
return nullptr;
}
auto ntype = argtypes[i + implicit];
// If this is a reference argument, the pointer type must be undone because the code below expects the pointed type as value type.
if (argflags[i + implicit] & VARF_Ref)
{
assert(ntype->isPointer());
ntype = TypeNullPtr; // the default of a reference type can only be a null pointer
}
if (ntype->GetRegCount() == 1)
{
ArgList[i] = new FxConstant(ntype, (*defaults)[defaults_index], ScriptPosition);
}
else
{
// Vectors need special treatment because they are not normal constants
FxConstant *cs[4] = { nullptr };
for (int l = 0; l < ntype->GetRegCount(); l++)
{ {
ScriptPosition.Message(MSG_ERROR, "Named argument %s comes before current position in argument list.", name.GetChars()); cs[l] = new FxConstant(TypeFloat64, (*defaults)[l + defaults_index], ScriptPosition);
delete this;
return nullptr;
} }
// copy the original argument into the list ArgList[i] = new FxVectorValue(cs[0], cs[1], cs[2], cs[3], ScriptPosition);
auto old = static_cast<FxNamedNode *>(ArgList[i]);
ArgList[i] = old->value;
old->value = nullptr;
delete old;
// now fill the gap with constants created from the default list so that we got a full list of arguments.
int insert = j - i;
int skipdefs = 0;
// Defaults contain multiple entries for pointers so we need to calculate how much additional defaults we need to skip
for (unsigned k = 0; k < i + implicit; k++)
{
skipdefs += argtypes[k]->GetRegCount() - 1;
}
for (int k = 0; k < insert; k++)
{
auto ntype = argtypes[i + k + implicit];
// If this is a reference argument, the pointer type must be undone because the code below expects the pointed type as value type.
if (argflags[i + k + implicit] & VARF_Ref)
{
assert(ntype->isPointer());
ntype = TypeNullPtr; // the default of a reference type can only be a null pointer
}
if (ntype->GetRegCount() == 1)
{
auto x = new FxConstant(ntype, (*defaults)[i + k + skipdefs + implicit], ScriptPosition);
ArgList.Insert(i + k, x);
}
else
{
// Vectors need special treatment because they are not normal constants
FxConstant *cs[4] = { nullptr };
for (int l = 0; l < ntype->GetRegCount(); l++)
{
cs[l] = new FxConstant(TypeFloat64, (*defaults)[l + i + k + skipdefs + implicit], ScriptPosition);
}
FxExpression *x = new FxVectorValue(cs[0], cs[1], cs[2], cs[3], ScriptPosition);
ArgList.Insert(i + k, x);
skipdefs += ntype->GetRegCount() - 1;
}
}
done = true;
break;
} }
} }
if (!done)
{ defaults_index += argtypes[i + implicit]->GetRegCount();
ScriptPosition.Message(MSG_ERROR, "Named argument %s not found.", name.GetChars());
delete this;
return nullptr;
}
// re-get the proper info for the inserted node.
type = argtypes[i + implicit];
flag = argflags[i + implicit];
} }
assert(ArgList[i]);
FxExpression *x = nullptr; FxExpression *x = nullptr;
if (foundvarargs && (Function->Variants[0].Flags & VARF_VarArg)) if (foundvarargs && (Function->Variants[0].Flags & VARF_VarArg))
{ {
@ -12525,6 +12751,15 @@ FxExpression *FxLocalVariableDeclaration::Resolve(FCompileContext &ctx)
if (ValueType->RegType == REGT_NIL && ValueType != TypeAuto) if (ValueType->RegType == REGT_NIL && ValueType != TypeAuto)
{ {
auto sfunc = static_cast<VMScriptFunction *>(ctx.Function->Variants[0].Implementation); auto sfunc = static_cast<VMScriptFunction *>(ctx.Function->Variants[0].Implementation);
const unsigned MAX_STACK_ALLOC = 512 * 1024; // Windows stack is 1 MB, but we cannot go up there without problems
if (uint64_t(ValueType->Size) + uint64_t(sfunc->ExtraSpace) > MAX_STACK_ALLOC)
{
ScriptPosition.Message(MSG_ERROR, "%s exceeds max. allowed size of 512kb for local variables at variable %s", sfunc->Name.GetChars(), Name.GetChars());
delete this;
return nullptr;
}
StackOffset = sfunc->AllocExtraStack(ValueType); StackOffset = sfunc->AllocExtraStack(ValueType);
if (Init != nullptr) if (Init != nullptr)

View file

@ -40,6 +40,7 @@
#include "filesystem.h" #include "filesystem.h"
CVAR(Bool, strictdecorate, false, CVAR_GLOBALCONFIG | CVAR_ARCHIVE) CVAR(Bool, strictdecorate, false, CVAR_GLOBALCONFIG | CVAR_ARCHIVE)
CVAR(Bool, warningstoerrors, false, CVAR_GLOBALCONFIG | CVAR_ARCHIVE)
EXTERN_CVAR(Bool, vm_jit) EXTERN_CVAR(Bool, vm_jit)
EXTERN_CVAR(Bool, vm_jit_aot) EXTERN_CVAR(Bool, vm_jit_aot)
@ -879,10 +880,18 @@ void FFunctionBuildList::Build()
{ {
if (!item.Code->CheckReturn()) if (!item.Code->CheckReturn())
{ {
auto newcmpd = new FxCompoundStatement(item.Code->ScriptPosition); if (ctx.ReturnProto == nullptr || !ctx.ReturnProto->ReturnTypes.Size())
newcmpd->Add(item.Code); {
newcmpd->Add(new FxReturnStatement(nullptr, item.Code->ScriptPosition)); auto newcmpd = new FxCompoundStatement(item.Code->ScriptPosition);
item.Code = newcmpd->Resolve(ctx); newcmpd->Add(item.Code);
newcmpd->Add(new FxReturnStatement(nullptr, item.Code->ScriptPosition));
item.Code = newcmpd->Resolve(ctx);
}
else
{
item.Code->ScriptPosition.Message(MSG_ERROR, "Missing return statement in %s", item.PrintableName.GetChars());
continue;
}
} }
item.Proto = ctx.ReturnProto; item.Proto = ctx.ReturnProto;

View file

@ -330,7 +330,8 @@ void PType::StaticInit()
TypeVoidPtr = NewPointer(TypeVoid, false); TypeVoidPtr = NewPointer(TypeVoid, false);
TypeRawFunction = new PPointer; TypeRawFunction = new PPointer;
TypeRawFunction->mDescriptiveName = "Raw Function Pointer"; TypeRawFunction->mDescriptiveName = "Raw Function Pointer";
TypeTable.AddType(TypeRawFunction, NAME_None);
TypeVMFunction = NewPointer(NewStruct("VMFunction", nullptr, true)); TypeVMFunction = NewPointer(NewStruct("VMFunction", nullptr, true));
TypeColorStruct = NewStruct("@ColorStruct", nullptr); //This name is intentionally obfuscated so that it cannot be used explicitly. The point of this type is to gain access to the single channels of a color value. TypeColorStruct = NewStruct("@ColorStruct", nullptr); //This name is intentionally obfuscated so that it cannot be used explicitly. The point of this type is to gain access to the single channels of a color value.
TypeStringStruct = NewStruct("Stringstruct", nullptr, true); TypeStringStruct = NewStruct("Stringstruct", nullptr, true);

View file

@ -2286,6 +2286,11 @@ PType *ZCCCompiler::ResolveArraySize(PType *baseType, ZCC_Expression *arraysize,
Error(arraysize, "Array size must be positive"); Error(arraysize, "Array size must be positive");
return TypeError; return TypeError;
} }
if (uint64_t(size) * baseType->Size > 0x7fffffff)
{
Error(arraysize, "Array size overflow. Total size must be less than 2GB");
return TypeError;
}
baseType = NewArray(baseType, size); baseType = NewArray(baseType, size);
} }

View file

@ -282,9 +282,9 @@ DEFINE_ACTION_FUNCTION(FStringStruct, DeleteLastCharacter)
static void LocalizeString(const FString &label, bool prefixed, FString *result) static void LocalizeString(const FString &label, bool prefixed, FString *result)
{ {
if (!prefixed) *result = GStrings(label); if (!prefixed) *result = GStrings.GetString(label);
else if (label[0] != '$') *result = label; else if (label[0] != '$') *result = label;
else *result = GStrings(&label[1]); else *result = GStrings.GetString(&label[1]);
} }
DEFINE_ACTION_FUNCTION_NATIVE(FStringTable, Localize, LocalizeString) DEFINE_ACTION_FUNCTION_NATIVE(FStringTable, Localize, LocalizeString)

View file

@ -670,7 +670,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(FFont, GetBottomAlignOffset, GetBottomAlignOffset)
static int StringWidth(FFont *font, const FString &str, int localize) static int StringWidth(FFont *font, const FString &str, int localize)
{ {
const char *txt = (localize && str[0] == '$') ? GStrings(&str[1]) : str.GetChars(); const char *txt = (localize && str[0] == '$') ? GStrings.GetString(&str[1]) : str.GetChars();
return font->StringWidth(txt); return font->StringWidth(txt);
} }
@ -684,7 +684,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(FFont, StringWidth, StringWidth)
static int GetMaxAscender(FFont* font, const FString& str, int localize) static int GetMaxAscender(FFont* font, const FString& str, int localize)
{ {
const char* txt = (localize && str[0] == '$') ? GStrings(&str[1]) : str.GetChars(); const char* txt = (localize && str[0] == '$') ? GStrings.GetString(&str[1]) : str.GetChars();
return font->GetMaxAscender(txt); return font->GetMaxAscender(txt);
} }
@ -698,7 +698,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(FFont, GetMaxAscender, GetMaxAscender)
static int CanPrint(FFont *font, const FString &str, int localize) static int CanPrint(FFont *font, const FString &str, int localize)
{ {
const char *txt = (localize && str[0] == '$') ? GStrings(&str[1]) : str.GetChars(); const char *txt = (localize && str[0] == '$') ? GStrings.GetString(&str[1]) : str.GetChars();
return font->CanPrint(txt); return font->CanPrint(txt);
} }
@ -770,6 +770,26 @@ DEFINE_ACTION_FUNCTION_NATIVE(FFont, GetDisplayTopOffset, GetDisplayTopOffset)
ACTION_RETURN_FLOAT(GetDisplayTopOffset(self, code)); ACTION_RETURN_FLOAT(GetDisplayTopOffset(self, code));
} }
static int GetChar(FFont* font, int c)
{
int texc = 0;
auto getch = font->GetChar(c, CR_UNDEFINED, nullptr);
if (getch)
texc = getch->GetID().GetIndex();
return texc;
}
DEFINE_ACTION_FUNCTION_NATIVE(FFont, GetChar, ::GetChar)
{
PARAM_SELF_STRUCT_PROLOGUE(FFont);
PARAM_INT(mchar);
if (numret > 0) ret[0].SetInt(::GetChar(self, mchar));
if (numret > 1) ret[1].SetInt(self->GetCharWidth(mchar));
return min(2, numret);
}
//========================================================================== //==========================================================================
// //
// file system // file system
@ -1142,6 +1162,17 @@ DEFINE_ACTION_FUNCTION(_Console, PrintfEx)
return 0; return 0;
} }
DEFINE_ACTION_FUNCTION(_Console, DebugPrintf)
{
PARAM_PROLOGUE;
PARAM_INT(debugLevel);
PARAM_VA_POINTER(va_reginfo);
FString s = FStringFormat(VM_ARGS_NAMES, 1);
DPrintf(debugLevel, "%s\n", s.GetChars());
return 0;
}
static void StopAllSounds() static void StopAllSounds()
{ {
soundEngine->StopAllChannels(); soundEngine->StopAllChannels();
@ -1675,7 +1706,7 @@ DEFINE_ACTION_FUNCTION(DScriptScanner, ScriptError)
{ {
PARAM_SELF_PROLOGUE(DScriptScanner); PARAM_SELF_PROLOGUE(DScriptScanner);
FString s = FStringFormat(VM_ARGS_NAMES); FString s = FStringFormat(VM_ARGS_NAMES, 1);
self->wrapped.ScriptError("%s", s.GetChars()); self->wrapped.ScriptError("%s", s.GetChars());
return 0; return 0;
} }
@ -1684,7 +1715,7 @@ DEFINE_ACTION_FUNCTION(DScriptScanner, ScriptMessage)
{ {
PARAM_SELF_PROLOGUE(DScriptScanner); PARAM_SELF_PROLOGUE(DScriptScanner);
FString s = FStringFormat(VM_ARGS_NAMES); FString s = FStringFormat(VM_ARGS_NAMES, 1);
self->wrapped.ScriptMessage("%s", s.GetChars()); self->wrapped.ScriptMessage("%s", s.GetChars());
return 0; return 0;
} }

View file

@ -111,7 +111,7 @@ FEndoomScreen::FEndoomScreen(int loading_lump)
StartupBitmap.Create(80 * 8, 26 * 16); // line 26 is for our own 'press any key to quit' message. StartupBitmap.Create(80 * 8, 26 * 16); // line 26 is for our own 'press any key to quit' message.
DrawTextScreen(StartupBitmap, endoom_screen); DrawTextScreen(StartupBitmap, endoom_screen);
ClearBlock(StartupBitmap, {0, 0, 0, 255}, 0, 25*16, 640, 16); ClearBlock(StartupBitmap, {0, 0, 0, 255}, 0, 25*16, 640, 16);
DrawString(StartupBitmap, 0, 25, GStrings("TXT_QUITENDOOM"), { 128, 128, 128 ,255}, { 0, 0, 0, 255}); DrawString(StartupBitmap, 0, 25, GStrings.GetString("TXT_QUITENDOOM"), { 128, 128, 128 ,255}, { 0, 0, 0, 255});
lastUpdateTime = I_msTime(); lastUpdateTime = I_msTime();
// Does this screen need blinking? // Does this screen need blinking?

View file

@ -372,6 +372,13 @@ FStartScreen* GetGameStartScreen(int max_progress)
return nullptr; return nullptr;
} }
FStartScreen::~FStartScreen()
{
if (StartupTexture) delete StartupTexture;
if (HeaderTexture) delete HeaderTexture;
if (NetTexture) delete NetTexture;
}
//========================================================================== //==========================================================================
// //
// ST_Util_ClearBlock // ST_Util_ClearBlock
@ -607,7 +614,7 @@ bool FStartScreen::NetInit(const char* message, int numplayers)
{ {
NetMaxPos = numplayers; NetMaxPos = numplayers;
NetCurPos = 0; NetCurPos = 0;
NetMessageString.Format("%s %s", message, GStrings("TXT_NET_PRESSESC")); NetMessageString.Format("%s %s", message, GStrings.GetString("TXT_NET_PRESSESC"));
NetProgress(1); // You always know about yourself NetProgress(1); // You always know about yourself
return true; return true;
} }
@ -692,8 +699,8 @@ void FStartScreen::Render(bool force)
twod->OnFrameDone(); twod->OnFrameDone();
} }
auto newtime = I_msTime(); auto newtime = I_msTime();
if ((newtime - nowtime) * 2.0 > minwaittime) // slow down drawing the start screen if we're on a slow GPU! if ((newtime - nowtime) * 2 > minwaittime) // slow down drawing the start screen if we're on a slow GPU!
minwaittime = (newtime - nowtime) * 2.0; minwaittime = (newtime - nowtime) * 2;
} }
FImageSource* CreateStartScreenTexture(FBitmap& srcdata); FImageSource* CreateStartScreenTexture(FBitmap& srcdata);

View file

@ -70,7 +70,7 @@ protected:
FGameTexture* NetTexture = nullptr; FGameTexture* NetTexture = nullptr;
public: public:
FStartScreen(int maxp) { MaxPos = maxp; } FStartScreen(int maxp) { MaxPos = maxp; }
virtual ~FStartScreen() = default; virtual ~FStartScreen();
void Render(bool force = false); void Render(bool force = false);
bool Progress(int); bool Progress(int);
void NetProgress(int count); void NetProgress(int count);

View file

@ -0,0 +1,139 @@
/*
** firetexture.cpp
** PSX/N64-style fire texture implementation
**
**---------------------------------------------------------------------------
** Copyright Cacodemon345 2024
** 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.
**---------------------------------------------------------------------------
**
*/
/* Algorithm based on https://github.com/fabiensanglard/DoomFirePSX */
#include "bitmap.h"
#include "firetexture.h"
#include "m_random.h"
#include "imagehelpers.h"
#include "engineerrors.h"
static constexpr int32_t FIRESKY_W = 64;
static constexpr int32_t FIRESKY_H = 128;
FireTexture::FireTexture()
{
SetSize(FIRESKY_W, FIRESKY_H);
Image.Clear();
Image.AppendFill(0, Width * Height);
}
void FireTexture::SetPalette(TArray<PalEntry>& colors)
{
Palette.Clear();
Palette.Append(colors);
/* This shouldn't happen in any circumstances. */
if (Palette.Size() > 256)
{
I_FatalError("Fire palette too big!");
}
for (unsigned int i = 0; i < Width; i++) {
Image[(Height - 1) * Width + i] = (uint8_t)(Palette.Size() - 1);
}
}
void FireTexture::Update()
{
for (unsigned int y = 1; y < Height; y++)
{
for (unsigned int x = 0; x < Width; x++)
{
uint8_t srcPixel = Image[y * Width + x];
if (srcPixel == 0)
{
Image[(y - 1) * Width + x] = 0;
}
else
{
int XRand = M_Random() & 3;
int destRand = M_Random() & 1;
Image[(y - 1) * Width + ((x + 1 - XRand) % Width)] = srcPixel - destRand;
}
}
}
}
FBitmap FireTexture::GetBgraBitmap(const PalEntry* remap, int* trans)
{
FBitmap bitmap;
bitmap.Create(Width, Height);
uint32_t* pixels = (uint32_t*)bitmap.GetPixels();
for (unsigned int y = 0; y < Height; y++)
{
for (unsigned int x = 0; x < Width; x++)
{
pixels[y * Width + x] = Palette[Image[y * Width + x]];
}
}
return bitmap;
}
TArray<uint8_t> FireTexture::Get8BitPixels(bool alphatex)
{
FBitmap bitmap = GetBgraBitmap(nullptr, nullptr);
const uint8_t* data = bitmap.GetPixels();
uint8_t* dest_p;
int dest_adv = Height;
int dest_rew = Width * Height - 1;
TArray<uint8_t> Pixels(Width * Height);
dest_p = Pixels.Data();
bool doalpha = alphatex;
// Convert the source image from row-major to column-major format and remap it
for (int y = Height; y != 0; --y)
{
for (int x = Width; x != 0; --x)
{
int b = *data++;
int g = *data++;
int r = *data++;
int a = *data++;
if (a < 128) *dest_p = 0;
else *dest_p = ImageHelpers::RGBToPalette(doalpha, r, g, b);
dest_p += dest_adv;
}
dest_p -= dest_rew;
}
return Pixels;
}

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