Compare commits

...

38 commits

Author SHA1 Message Date
obesecatlord
f47d1c0331 Add OpenXR VR rendering support 2026-05-10 22:07:36 -05:00
Alug
d3b482c103 fix parent process being killed before child per interrupt/terminate signals 2026-05-05 08:12:58 -04:00
NepDisk
87885fafdb Port using nanosleep on *nix from SRB2Classic 2026-05-05 08:08:48 -04:00
NepDisk
b3938d3ecd Revert "Why did this move up again"
This reverts commit 424a50832d.
2026-05-05 01:12:20 -04:00
NepDisk
03400d0a81 Use the correct hash for this 2026-05-05 00:44:16 -04:00
NepDisk
4c8899b373 Revert "DUMPCONSISTENCY for the modern age"
This reverts commit 40dc3de468.
2026-05-05 00:37:01 -04:00
NepDisk
424a50832d Why did this move up again 2026-05-05 00:05:25 -04:00
Sally Coolatta
40dc3de468 DUMPCONSISTENCY for the modern age
- dumpconsistency cvar is always enabled (rather than a define), but is now a cheat.
- It now dumps on resend, instead of on consistency failure kick. (Those don't even happen on too many resyncs anymore, anyways...)
- It now dumps for both the server & the client that is resyncing, so there's gamestates to compare. The two files are given names with metadata so they can be matched up.

It's not great, but it was easy enough to do and more useable than having 0 tools to inspect resync at all.
2026-05-04 01:27:27 -04:00
minenice55
d6e388b191 c'est le temps 2026-05-03 21:47:07 -04:00
yamamama
732c44c0d2 Fix V_DrawAffinePatch not respecting translucency and blendmodes in Software 2026-05-03 20:09:22 -04:00
NepDisk
915f00b286 Let attraction shield steal bumpers 2026-05-03 16:49:04 -04:00
minenice55
a59a4360cb fix attraction shield tackle not working in battle 2026-05-03 16:41:47 -04:00
minenice55
7c28b24fb6 Update d_main.cpp 2026-05-03 16:16:07 -04:00
NepDisk
5d2b5552c5 Add bubbleshield boost timer and rename wheel graphics 2026-05-03 15:57:44 -04:00
minenice55
856684a68c reduce starting flame fuel by 10%
suggestion from playtest to compensate for removal of passive drain
2026-05-03 14:59:24 -04:00
minenice55
eaa676f9d6 use the nicer value from testing 2026-05-03 14:53:46 -04:00
minenice55
426bec4395 make recovery dash VFX respect kart tilt 2026-05-03 14:53:11 -04:00
minenice55
d838d4a8ff make sure recovery spin is active before allowing this 2026-05-03 14:41:41 -04:00
NepDisk
137ffd14cf Use FP classify here 2026-05-03 13:35:37 -04:00
Alug
9f52b1e158 use destructor attribute for W_Shutdown
makes sure this is always called in any case
also just simpler
2026-05-03 13:33:23 -04:00
NepDisk
247c3cb323 Warning clean up 2026-05-03 12:58:37 -04:00
yamamama
7039fff6ab Fix dial speedometer in Battle/Item Breaker, assign the *ring meter*
offsets instead of the dial speedometer
2026-05-03 03:57:17 -04:00
minenice55
5f2e63e44c make this *actually* match the old behaviour
in theory adding K_FlipFromObject here would be "better" but I'll just have this match old code for now
2026-05-03 00:21:47 -04:00
minenice55
288867dda3 revise CSS controls a bit, horn and voice preview
- Grid CSS now has to be actively entered (either with the confirm button or by navigating left/right)
- Grid CSS can be exited via selection or by pressing escape, in addition to the usual way of navigating out via the top / bottom
- Selecting a skin now plays the skin's overtake voice line, and respects voice setting and voice preferences
- Pressing confirm over the Follower option will now play that follower's horn
2026-05-02 23:53:26 -04:00
NepDisk
5cfd8d8823 Use 1000 as the default packetsize
Seems to be the most compat
2026-05-02 20:33:22 -04:00
yamamama
24bb78e448 Remove ringoffset from the dial speedometer coords 2026-05-02 18:25:59 -04:00
minenice55
38ab10e2ff Update d_main.cpp 2026-05-02 16:53:24 -04:00
yamamama
6a3a78a002 Dial speedometer
HUDTRANS is kind of b0rked on software
2026-05-02 16:43:06 -04:00
yamamama
011147e39a ACTUALLY FIX linedef 403 and 404
The other fix fixed Pandaemonium, but broke Diamond Square in the process
This fixes both!
2026-05-02 16:43:06 -04:00
NepDisk
bea6246ff7 These are booleans 2026-05-02 16:29:54 -04:00
minenice55
74260033e0 this is auto-flipped so no need for this 2026-05-02 16:13:22 -04:00
minenice55
aa585e00c7 fix affine rotation not flipping in gravity flip
this matches software now
2026-05-02 16:11:17 -04:00
minenice55
b9eb26f55e why was this re-defined?? 2026-05-02 15:43:17 -04:00
NepDisk
7d3a00b0b5 Update hash 2026-05-02 15:24:53 -04:00
NepDisk
00804d6d52 Update hash 2026-05-02 15:06:43 -04:00
NepDisk
d5529f8458 High Res rank toggle 2026-05-02 13:46:59 -04:00
yamamama
b5e9e1e4da Player bump gameplay setting
You can either toggle bumping off completely (outside of power items), or limit it to on-ground bumps only
2026-05-02 13:28:46 -04:00
yamamama
899dc271ca Change return value of PvPTouchDamage
Will now return the result of its P_DamageMobj calls instead of just if a P_DamageMobj call happened at all
2026-05-02 13:28:46 -04:00
61 changed files with 3107 additions and 415 deletions

View file

@ -4,6 +4,8 @@ BlanKart is a modification of SRB2Kart v2 Indev to make it closer to SRB2Kart ga
If you're interested in helping out, theres a matrix room located [here](https://matrix.to/#/#blankart:matrix.org)!
The VR work in this fork is inspired in part by [`kart-public-vr`](https://git.do.srb2.org/chreas/kart-public-vr), an earlier SRB2Kart OpenVR implementation.
# Notice
This is still in active development and things are going to change. If you find any bugs besure to report to the [issue tracker](https://codeberg.org/NepDisk/blankart/issues)!

View file

@ -157,6 +157,9 @@ add_executable(BLANKART MACOSX_BUNDLE WIN32
h_timers.cpp
stun.c
lonesha256.c
vr/vr_main.c
vr/vr_render.c
vr/vr_math.c
)
if(("${CMAKE_COMPILER_IS_GNUCC}" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows")
@ -364,6 +367,13 @@ if(SRB2_CONFIG_ENABLE_DISCORDRPC)
target_sources(BLANKART PRIVATE discord.c)
endif()
# VR Support
target_compile_definitions(BLANKART PRIVATE -DHAVE_VR)
target_link_libraries(BLANKART PRIVATE openxr_loader)
if(UNIX AND NOT APPLE)
target_link_libraries(BLANKART PRIVATE GL GLX X11)
endif()
set(SRB2_HAVE_THREADS ON)
target_compile_definitions(BLANKART PRIVATE -DHAVE_THREADS)

View file

@ -1455,7 +1455,7 @@ static UINT32 xxHashString32(const char *name)
#define NAME cvar_map_t
#define KEY_TY const char *
#define VAL_TY consvar_t *
#define HASH_FN xxHashString32
#define HASH_FN FNV1a_HashLowercaseString
#define CMPR_FN vt_cmpr_casestring
#include "verstable.h"

View file

@ -710,7 +710,7 @@ static char shiftxform[] =
INT32 CON_ShiftChar(INT32 ch)
{
if (I_UseNativeKeyboard() || ch >= sizeof(shiftxform))
if (I_UseNativeKeyboard() || ch >= (INT32)sizeof(shiftxform))
return ch;
// warning: shiftdown is NOT a boolean, it's 1 or 2 for lshift/rshift

View file

@ -4449,8 +4449,8 @@ void CL_RemoveSplitscreenPlayer(UINT8 p)
static void GotOurIP(UINT32 address)
{
const unsigned char * p = (const unsigned char *)&address;
#ifdef DEVELOP
const unsigned char * p = (const unsigned char *)&address;
CONS_Printf("Got IP of %u.%u.%u.%u\n", p[0], p[1], p[2], p[3]);
#endif
ourIP = address;
@ -7215,6 +7215,7 @@ static void UpdatePingTable(void)
}
// It's that time again! Send everyone a safe message to sign, so we can show off their signature and prove we're playing fair.
#if 0
static void SendChallenges(void)
{
int i;
@ -7375,6 +7376,7 @@ static void UpdateChallenges(void)
HandleSigfail("Didn't receive client signatures.");
}
}
#endif
static void RenewHolePunch(void)
{

View file

@ -56,6 +56,7 @@
#include "r_local.h"
#include "r_voicepreference.hpp" // Preferences directory
#include "s_sound.h"
#include "screen.h"
#include "st_stuff.h"
#include "v_video.h"
#include "w_wad.h"
@ -102,9 +103,9 @@
#define ASSET_HASH_GFX_KART 0xc91b0d43f5ba131f
#define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291
#define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b
#define ASSET_HASH_MAPS_KART 0x2be29dfb3a146dfa
#define ASSET_HASH_MAIN_PK3 0x4ea7e79e2d5d0d63
#define ASSET_HASH_MAPPATCH_PK3 0x1745690024efbaf8
#define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9
#define ASSET_HASH_MAIN_PK3 0xbeaab17108f0815a
#define ASSET_HASH_MAPPATCH_PK3 0xdfb3c9da6c8c3adf
#define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461
#ifdef USE_PATCH_FILE
#define ASSET_HASH_PATCH_PK3 0x0000000000000000
@ -425,12 +426,80 @@ gamestate_t wipegamestate = GS_LEVEL;
INT16 wipetypepre = -1;
INT16 wipetypepost = -1;
#ifdef HAVE_VR
#include "vr/vr_main.h"
#include "vr/vr_render.h"
static void D_Display_Internal(void);
static void D_Display(void)
{
static boolean vr_auto_started_menu = false;
if (vr_started && gamestate == GS_TITLESCREEN && !menustack[0] && !vr_auto_started_menu)
{
M_StartControlPanel();
CONS_Printf("VR: Auto-opened title menu; menustack[0]=%d, gamestate=%d.\n",
(INT32)menustack[0], (INT32)gamestate);
vr_auto_started_menu = true;
}
else if (!vr_started || gamestate != GS_TITLESCREEN)
vr_auto_started_menu = false;
const boolean vr_quad_ui = (cv_vruimode.value != 0);
if (vr_started && VR_BeginFrame())
{
if (VR_SetEye(0))
{
D_Display_Internal();
VR_ReleaseEye(0);
}
if (VR_SetEye(1))
{
D_Display_Internal();
VR_ReleaseEye(1);
}
if (vr_quad_ui && VR_BindUISwapchain())
{
D_Display_Internal();
VR_BindDefaultFramebuffer();
}
VR_EndFrame();
}
else
{
vr_render_pass = VR_PASS_NONE;
D_Display_Internal();
}
}
static void D_Display_Internal(void)
#else
static void D_Display(void)
#endif
{
boolean forcerefresh = false;
static boolean wipe = false;
INT32 wipedefindex = 0;
UINT8 i;
#ifdef HAVE_VR
const boolean vr_quad_ui = (vr_started && cv_vruimode.value != 0);
const boolean vr_in_eye_ui = (vr_started && !vr_quad_ui
&& (vr_render_pass == VR_PASS_3D_LEFT || vr_render_pass == VR_PASS_3D_RIGHT));
const boolean vr_draw_3d = (!vr_started || vr_render_pass == VR_PASS_NONE
|| vr_render_pass == VR_PASS_3D_LEFT || vr_render_pass == VR_PASS_3D_RIGHT);
const boolean vr_draw_ui = (!vr_started || vr_render_pass == VR_PASS_NONE || vr_render_pass == VR_PASS_UI
|| (!vr_quad_ui && (vr_render_pass == VR_PASS_3D_LEFT || vr_render_pass == VR_PASS_3D_RIGHT)));
const boolean vr_draw_title = (!vr_started || vr_render_pass != VR_PASS_UI);
#else
const boolean vr_in_eye_ui = false;
const boolean vr_draw_3d = true;
const boolean vr_draw_ui = true;
const boolean vr_draw_title = true;
#endif
ZoneScoped;
@ -539,79 +608,89 @@ static void D_Display(void)
}
// do buffered drawing
switch (gamestate)
if (vr_draw_ui)
{
case GS_TITLESCREEN:
if (!titlemapinaction || !curbghide) {
F_TitleScreenDrawer();
break;
}
/* FALLTHRU */
case GS_LEVEL:
if (!gametic)
break;
AM_Drawer();
break;
#ifdef HAVE_VR
if (vr_in_eye_ui)
VR_BeginInEyeUI();
#endif
case GS_INTERMISSION:
Y_IntermissionDrawer();
HU_Drawer();
break;
case GS_VOTING:
Y_VoteDrawer();
HU_Drawer();
break;
case GS_TIMEATTACK:
break;
case GS_INTRO:
F_IntroDrawer();
if (wipegamestate == (gamestate_t)-1)
switch (gamestate)
{
wipe = true;
wipedefindex = gamestate; // wipe_xxx_toblack
}
break;
case GS_TITLESCREEN:
if (!vr_draw_title)
break;
if (!titlemapinaction || !curbghide) {
F_TitleScreenDrawer();
break;
}
/* FALLTHRU */
case GS_LEVEL:
if (!gametic)
break;
AM_Drawer();
break;
case GS_CUTSCENE:
F_CutsceneDrawer();
HU_Drawer();
break;
case GS_EVALUATION:
F_GameEvaluationDrawer();
HU_Drawer();
break;
case GS_CREDITS:
F_CreditDrawer();
HU_Drawer();
break;
case GS_BLANCREDITS:
F_BlanCreditDrawer();
HU_Drawer();
break;
case GS_SECRETCREDITS:
F_SecretCreditsDrawer();
HU_Drawer();
break;
case GS_WAITINGPLAYERS:
// The clientconnect drawer is independent...
if (netgame)
{
// I don't think HOM from nothing drawing is independent...
F_WaitingPlayersDrawer();
case GS_INTERMISSION:
Y_IntermissionDrawer();
HU_Drawer();
}
case GS_DEDICATEDSERVER:
case GS_NULL:
case FORCEWIPE:
break;
break;
case GS_VOTING:
Y_VoteDrawer();
HU_Drawer();
break;
case GS_TIMEATTACK:
break;
case GS_INTRO:
F_IntroDrawer();
if (wipegamestate == (gamestate_t)-1)
{
wipe = true;
wipedefindex = gamestate; // wipe_xxx_toblack
}
break;
case GS_CUTSCENE:
F_CutsceneDrawer();
HU_Drawer();
break;
case GS_EVALUATION:
F_GameEvaluationDrawer();
HU_Drawer();
break;
case GS_CREDITS:
F_CreditDrawer();
HU_Drawer();
break;
case GS_BLANCREDITS:
F_BlanCreditDrawer();
HU_Drawer();
break;
case GS_SECRETCREDITS:
F_SecretCreditsDrawer();
HU_Drawer();
break;
case GS_WAITINGPLAYERS:
// The clientconnect drawer is independent...
if (netgame)
{
// I don't think HOM from nothing drawing is independent...
F_WaitingPlayersDrawer();
HU_Drawer();
}
case GS_DEDICATEDSERVER:
case GS_NULL:
case FORCEWIPE:
break;
}
}
// STUPID race condition...
@ -620,22 +699,31 @@ static void D_Display(void)
// clean up border stuff
// see if the border needs to be initially drawn
if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide && (!hidetitlemap)))
{
D_RenderLevel();
ps_uitime = I_GetPreciseTime();
if (gamestate == GS_LEVEL)
if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide && (!hidetitlemap)))
{
if (vr_draw_3d)
D_RenderLevel();
ps_uitime = I_GetPreciseTime();
#ifdef HAVE_VR
if (vr_in_eye_ui)
VR_BeginInEyeUI();
#endif
if (vr_draw_ui && gamestate == GS_LEVEL)
{
ST_Drawer();
F_TextPromptDrawer();
HU_Drawer();
}
else if (vr_draw_ui)
{
if (vr_draw_title)
F_TitleScreenDrawer();
}
}
else
F_TitleScreenDrawer();
}
else
{
ps_uitime = I_GetPreciseTime();
@ -648,7 +736,7 @@ static void D_Display(void)
V_SetPalette(0);
// draw pause pic
if (paused && cv_showhud.value && !demo.playback)
if (vr_draw_ui && paused && cv_showhud.value && !demo.playback)
{
INT32 py;
patch_t *patch;
@ -660,24 +748,45 @@ static void D_Display(void)
V_DrawScaledPatch(viewwindowx + (BASEVIDWIDTH - patch->width)/2, py, V_SNAPTOTOP, patch);
}
if (demo.rewinding)
if (vr_draw_ui && demo.rewinding)
V_DrawFadeScreen(TC_RAINBOW, (leveltime & 0x20) ? SKINCOLOR_PASTEL : SKINCOLOR_MOONSLAM);
// vid size change is now finished if it was on...
vid.recalc = false;
#ifdef HAVE_THREADS
I_lock_mutex(&m_menu_mutex);
if (vr_draw_ui)
{
#ifdef HAVE_VR
if (vr_in_eye_ui)
VR_BeginInEyeUI();
#endif
M_Drawer(); // menu is drawn even on top of everything
if (cv_songcredits.value && !( (G_GamestateUsesLevel() && hu_showscores) && (netgame || multiplayer) ))
HU_DrawSongCredits(); // As are music credits.
#ifdef HAVE_THREADS
I_unlock_mutex(m_menu_mutex);
#endif
// focus lost moved to M_Drawer
CON_Drawer();
#ifdef HAVE_VR
if (vr_started && vr_render_pass == VR_PASS_UI)
{
static boolean vr_logged_ui_pass = false;
if (!vr_logged_ui_pass)
{
CONS_Printf("VR: UI pass active; menustack[0]=%d, gamestate=%d, vid=%dx%d dup=%d, ui=%ux%u.\n",
(INT32)menustack[0], (INT32)gamestate, vid.width, vid.height, vid.dup, vr_ui_width, vr_ui_height);
vr_logged_ui_pass = true;
}
}
#endif
#ifdef HAVE_THREADS
I_lock_mutex(&m_menu_mutex);
#endif
M_Drawer(); // menu is drawn even on top of everything
if (cv_songcredits.value && !( (G_GamestateUsesLevel() && hu_showscores) && (netgame || multiplayer) ))
HU_DrawSongCredits(); // As are music credits.
#ifdef HAVE_THREADS
I_unlock_mutex(m_menu_mutex);
#endif
// focus lost moved to M_Drawer
CON_Drawer();
}
ps_uitime = I_GetPreciseTime() - ps_uitime;
@ -712,13 +821,16 @@ dedipostwipe:
if (dedicated)
return; // NOW we can bail
NetUpdate(); // send out any new accumulation
// It's safe to end the game now.
if (G_GetExitGameFlag())
if (vr_draw_ui)
{
Command_ExitGame_f();
G_ClearExitGameFlag();
NetUpdate(); // send out any new accumulation
// It's safe to end the game now.
if (G_GetExitGameFlag())
{
Command_ExitGame_f();
G_ClearExitGameFlag();
}
}
//
@ -726,10 +838,10 @@ dedipostwipe:
//
if (!wipe)
{
if (cv_shittyscreen.value)
if (vr_draw_ui && cv_shittyscreen.value)
V_DrawVhsEffect(cv_shittyscreen.value == 2);
if (cv_netstat.value)
if (vr_draw_ui && cv_netstat.value)
{
char s[50];
Net_GetNetStat();
@ -746,7 +858,7 @@ dedipostwipe:
V_DrawRightAlignedString(BASEVIDWIDTH, BASEVIDHEIGHT-ST_HEIGHT-10, V_YELLOWMAP, s);
}
if (cv_perfstats.value)
if (vr_draw_ui && cv_perfstats.value)
{
M_DrawPerfStats();
}
@ -812,6 +924,15 @@ static double D_EndFrame(precise_t enterprecise, int *frameskip)
double deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
double deltatics = deltasecs * NEWTICRATE;
#ifdef HAVE_VR
if (vr_started)
{
if (frameskip)
*frameskip = 0;
return deltatics;
}
#endif
// If time spent this game loop exceeds a single tic,
// it's probably because of rendering.
//

View file

@ -1246,7 +1246,8 @@ boolean D_CheckNetGame(void)
software_MAXPACKETLENGTH = hardware_MAXPACKETLENGTH;
#else
// MTU, IPV4, UDP header.
software_MAXPACKETLENGTH = 1500 - 20 - 8;
// 1500 - 20 - 8;
software_MAXPACKETLENGTH = 1000;
#endif
if (M_CheckParm("-packetsize"))
{

View file

@ -180,6 +180,7 @@ static void KartItemBreaker_OnChange(void);
static void KartBumpSpark_OnChange(void);
static void KartItemList_OnChange(void);
static void KartTrailSlow_OnChange(void);
static void PlayerBump_OnChange(void);
static void Schedule_OnChange(void);
@ -679,6 +680,11 @@ consvar_t cv_kartkeepstuff = CVAR_INIT ("kartkeepstuff", "No", CV_NETVAR|CV_CALL
consvar_t cv_trailslow = CVAR_INIT ("trailslow", "Yes", CV_NETVAR|CV_CALL|CV_NOINIT|CV_GUARD, CV_YesNo, KartTrailSlow_OnChange);
static CV_PossibleValue_t playerbump_cons_t[] = {{PBUMP_STANDARD, "Standard"},
{PBUMP_GNDONLY, "Only on Ground"},
{PBUMP_NONE, "None"},
{0, NULL}};
consvar_t cv_playerbump = CVAR_INIT ("playerbump", "Standard", CV_NETVAR|CV_CALL|CV_NOINIT|CV_GUARD, playerbump_cons_t, PlayerBump_OnChange);
#define ANTIBUMP_MAX (UINT32_MAX / TICRATE)
static CV_PossibleValue_t antibump_cons_t[] = {{0, "MIN"}, {ANTIBUMP_MAX, "MAX"}, {0, NULL}};
@ -728,7 +734,7 @@ consvar_t cv_kartbubble_boost_offroadignore = CVAR_INIT ("kartbubble_boost_offro
consvar_t cv_kartflame_fastfuel = CVAR_INIT ("kartflame_fastfuel", "On", CV_NETVAR, CV_OnOff, NULL);
consvar_t cv_kartflame_offroadburn = CVAR_INIT ("kartflame_offroadburn", "On", CV_NETVAR, CV_OnOff, NULL);
consvar_t cv_kartattraction_assistpower = CVAR_INIT ("kartattraction_assistpower", "0.5", CV_NETVAR|CV_FLOAT, CV_Unsigned, NULL);
consvar_t cv_kartattraction_assistpower = CVAR_INIT ("kartattraction_assistpower", "0.2", CV_NETVAR|CV_FLOAT, CV_Unsigned, NULL);
// Attraction Shield damage toggles
static CV_PossibleValue_t kartattractiondmg_cons_t[] = {{0, "None"}, {DMG_WIPEOUT, "Wipe-out"}, {DMG_FLIPOVER, "Flip-over"}, {DMG_EXPLODE, "Explode"}, {0, NULL}};
@ -1506,6 +1512,19 @@ void D_RegisterClientCommands(void)
CV_RegisterVar(&cv_scr_depth);
CV_RegisterVar(&cv_scr_width);
CV_RegisterVar(&cv_scr_height);
CV_RegisterVar(&cv_vrviewmode);
CV_RegisterVar(&cv_vrcomfortmode);
CV_RegisterVar(&cv_vrenabled);
CV_RegisterVar(&cv_vrresolution);
CV_RegisterVar(&cv_vrscale);
CV_RegisterVar(&cv_vruidistance);
CV_RegisterVar(&cv_vruiscale);
CV_RegisterVar(&cv_vruimode);
CV_RegisterVar(&cv_vrposemode);
CV_RegisterVar(&cv_vrplayerscale);
CV_RegisterVar(&cv_vrspriterotate);
CV_RegisterVar(&cv_vrdisableskystereo);
CV_RegisterVar(&cv_vrtrackintro);
CV_RegisterVar(&cv_parallelsoftware);
CV_RegisterVar(&cv_director);
@ -2158,7 +2177,7 @@ static void SendNameAndColor(UINT8 n)
// If you're not in a netgame, merely update the skin, color, and name.
if (!netgame)
{
INT32 foundskin, voxid;
INT32 foundskin;
CleanupPlayerName(playernum, cv_playername[n].zstring);
strcpy(player_names[playernum], cv_playername[n].zstring);
@ -9953,6 +9972,24 @@ static void KartTrailSlow_OnChange(void)
}
}
static void PlayerBump_OnChange(void)
{
if (K_CanChangeRules(false) == false)
{
return;
}
if (leveltime < starttime)
{
CONS_Printf(M_GetText("Player bump type has been changed to \"%s\".\n"), cv_playerbump.string);
playerbumpactive = (playerbumptype_t)cv_playerbump.value;
}
else
{
CONS_Printf(M_GetText("Player bump type will be changed to \"%s\" next round.\n"), cv_playerbump.string);
}
}
static void Schedule_OnChange(void)
{
size_t i;

View file

@ -264,6 +264,8 @@ extern consvar_t cv_kartantibump;
extern consvar_t cv_trailslow;
extern consvar_t cv_playerbump;
extern consvar_t cv_kartforcelegacyodds;
extern consvar_t cv_handleboostslip;

View file

@ -101,34 +101,6 @@ INT32 flags; Bits = 3232 MF_SOLID|MF_SHOOTABLE|MF_DROPOFF|M
INT32 raisestate; Respawn frame = 32 S_NULL // raisestate
}, */
#ifdef HWRENDER
static INT32 searchvalue(const char *s)
{
while (s[0] != '=' && s[0])
s++;
if (s[0] == '=')
return atoi(&s[1]);
else
{
deh_warning("No value found");
return 0;
}
}
static float searchfvalue(const char *s)
{
while (s[0] != '=' && s[0])
s++;
if (s[0] == '=')
return (float)atof(&s[1]);
else
{
deh_warning("No value found");
return 0;
}
}
#endif
// These are for clearing all of various things
void clear_emblems(void)
{

View file

@ -1919,5 +1919,10 @@ struct int_const_s const INT_CONST[] = {
{"KEEPSTUFF_GROWSHRINK", KEEPSTUFF_GROWSHRINK},
{"KEEPSTUFF_FLAME", KEEPSTUFF_FLAME},
// playerbumptype_t
{"PBUMP_STANDARD", PBUMP_STANDARD},
{"PBUMP_GNDONLY", PBUMP_GNDONLY},
{"PBUMP_NONE", PBUMP_NONE},
{NULL,0}
};

View file

@ -266,7 +266,7 @@ INT32 greasetics = 3*TICRATE;
INT32 wipeoutslowtime = 20;
INT32 wantedreduce = 5*TICRATE;
INT32 wantedfrequency = 10*TICRATE;
INT32 flametime = (((8*TICRATE)*3) + (4*TICRATE));
INT32 flametime = 25*TICRATE + (2*TICRATE/10); // (((8*TICRATE)*3) + (4*TICRATE))
UINT8 use1upSound = 0;
UINT8 maxXtraLife = 2; // Max extra lives from rings

View file

@ -377,6 +377,16 @@ typedef struct
} restatmessage_t;
void G_HandleRestatMessage(restatmessage_t *rm);
typedef enum
{
PBUMP_STANDARD = 0,
PBUMP_GNDONLY,
PBUMP_NONE,
NUMPLAYERBUMPTYPES
} ATTRPACK playerbumptype_t;
extern playerbumptype_t playerbumpactive;
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -1298,6 +1298,8 @@ vector3_t G_GetCalibratedGyroOffset(INT32 p)
void G_UpdateGamepadAutoCalibration(INT32 p, vector3_t accel, vector3_t gyro, boolean allowautocalibration)
{
(void)gyro;
fixed_t trust = FV3_Distance(&localaccelcalibrationoffset[p], &accel);
FV3_Load(
&localaccelcalibrationoffset[p],
@ -1569,4 +1571,4 @@ boolean G_GetGamepadCanUseTilt(INT32 p)
#undef GyroCalibrationTrust
#undef GyroCalibrationStart
#undef GyroCalibrationRollingAvgSamples
#undef GyroCalibrationRollingAvgSamples

View file

@ -2,6 +2,7 @@
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by haya3218.
// Copyright (C) 2018-2024 by Kart Krew
// Copyright (C) 2026 by Team Blankart.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
@ -295,6 +296,12 @@ void K_DisplayItemTimers(void)
{qche("K_TIRING")},
1,
},
{ // bubbleshield
"bubbleshield",
stplyr->bubbleboost,
{qche("K_TIBUBS")},
1,
},
{ // flameshield
"flameshield",
stplyr->flametimer,

View file

@ -78,6 +78,9 @@
#include "../tables.h"
#include "r_opengl/r_opengl.h"
#include "../r_main.h" // for cv_fov
#ifdef HAVE_VR
#include "../vr/vr_main.h"
#endif
typedef struct clipnode_s
{
@ -333,6 +336,11 @@ angle_t gld_FrustumAngle(angle_t tiltangle)
// ok, this is a gross hack that barely works...
// but at least it doesn't overestimate too much...
#ifdef HAVE_VR
if (vr_started)
clipfov = 120.0;
else
#endif
clipfov = atan(1 / (GLdouble)projMatrix[0]) * 360.0 / M_PI;
floatangle = 2.0 + (45.0 + ((double)tilt / 1.9)) * clipfov / 90.0;
if (floatangle >= 180.0)

View file

@ -294,7 +294,7 @@ void HWR_DrawAffinePatch(patch_t *gpatch, fixed_t x, fixed_t y, const affine_t *
// i don't fucking know, i spent a day on this and got absolutely nowhere, but this guy knows:
// https://nigeltao.github.io/blog/2021/inverting-3x2-affine-transformation-matrix.html
float determinant = fa * fd - fb * fc;
if (determinant == 0.0f)
if (fpclassify(determinant) == FP_ZERO)
return;
float ba = fd / determinant;
float bb = -fb / determinant;

View file

@ -46,6 +46,11 @@
#include "../p_slopes.h"
#include "hw_md2.h"
#ifdef HAVE_VR
#include "../screen.h"
#include "../vr/vr_main.h"
#endif
// SRB2Kart
#include "../k_kart.h"
#include "../r_fps.h"
@ -522,6 +527,18 @@ void HWR_RenderViewpoint(player_t *player, boolean drawSkyTexture, boolean is_sk
// since our clipper uses this to determine our actual visible geometry
GL_SetTransform(&atransform);
HWR_ClearSprites();
#ifdef HAVE_VR
// Let sprite billboards and clipping follow the user's HMD yaw in VR.
if (vr_started && cv_vrspriterotate.value)
{
if (cv_kartencore.value)
viewangle += ANGLE_MAX * atan2(vrHMDPoseMatrix[8], vrHMDPoseMatrix[0]) / M_PI * 0.5;
else
viewangle -= ANGLE_MAX * atan2(vrHMDPoseMatrix[8], vrHMDPoseMatrix[0]) / M_PI * 0.5;
}
#endif
HWR_ResetClipper();
if (rootportal)
@ -560,7 +577,11 @@ void HWR_RenderViewpoint(player_t *player, boolean drawSkyTexture, boolean is_sk
// HWR_DrawSkyBackground is not able to set the texture without
// pausing batching first
HWR_PauseBatching();
if (skyboxmo[0] && cv_skybox.value && !is_skybox && !rootportal && !gl_debugportal)
if (skyboxmo[0] && cv_skybox.value && !is_skybox && !rootportal && !gl_debugportal
#ifdef HAVE_VR
&& (!vr_started || !cv_vrcomfortmode.value)
#endif
)
{
//if (gl_printportals)
// CONS_Printf("drawing a skybox\n");
@ -701,11 +722,33 @@ void HWR_RollTransform(FTransform *tr, angle_t roll)
}
}
#ifdef HAVE_VR
static inline boolean HWR_VRRenderingEye(void)
{
return (vr_started && !vr_drawing_ui &&
(vr_render_pass == VR_PASS_3D_LEFT || vr_render_pass == VR_PASS_3D_RIGHT));
}
#endif
// -----------------+
// HWR_ClearView : clear the viewwindow, with maximum z value. also clears stencil buffer.
// -----------------+
static inline void HWR_ClearView(void)
{
#ifdef HAVE_VR
if (HWR_VRRenderingEye())
{
GL_GClipRect(0,
0,
(INT32)vr_render_width,
(INT32)vr_render_height,
ZCLIP_PLANE);
GL_ClearBuffer(false, true, true, NULL);
return;
}
#endif
GL_GClipRect(viewwindowx,
viewwindowy,
(viewwindowx + viewwidth),
@ -719,7 +762,11 @@ void HWR_RenderPlayerView(void)
{
player_t * player = &players[displayplayers[viewssnum]];
const boolean skybox = (skyboxmo[0] && cv_skybox.value); // True if there's a skybox object and skyboxes are on
const boolean skybox = (skyboxmo[0] && cv_skybox.value
#ifdef HAVE_VR
&& (!vr_started || !cv_vrcomfortmode.value)
#endif
); // True if there's a skybox object and skyboxes are on
FRGBAFloat ClearColor;
@ -744,6 +791,14 @@ void HWR_RenderPlayerView(void)
if (viewssnum == 0) // Only do it if it's the first screen being rendered
GL_ClearBuffer(true, true, true, &ClearColor); // Clear the Color Buffer, stops HOMs. Also seems to fix the skybox issue on Intel GPUs.
#ifdef HAVE_VR
if (vr_started)
{
const float player_scale = (cv_vrplayerscale.value && player->mo) ? FIXED_TO_FLOAT(player->mo->scale) : 1.0f;
VR_ScaleViewMatrices(player_scale, cv_vrdisableskystereo.value ? 0 : 1);
}
#endif
ps_hw_skyboxtime = I_GetPreciseTime();
if (skybox) // If there's a skybox and we should be drawing the sky, draw the skybox
{
@ -798,6 +853,11 @@ void HWR_RenderPlayerView(void)
// added by Hurdler for correct splitscreen
// moved here by hurdler so it works with the new near clipping plane
#ifdef HAVE_VR
if (HWR_VRRenderingEye())
GL_GClipRect(0, 0, (INT32)vr_render_width, (INT32)vr_render_height, NZCLIP_PLANE);
else
#endif
GL_GClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
}

View file

@ -15,6 +15,9 @@
#include "hw_gpu.h"
#include "hw_shaders.h"
#include "../z_zone.h"
#ifdef HAVE_VR
#include "../vr/vr_main.h"
#endif
// ================
// Shader sources
@ -361,7 +364,17 @@ static void HWR_CompileShader(int index)
if (vertex_source)
{
char *preprocessed = HWR_PreprocessShader(vertex_source);
char *source_to_preprocess = vertex_source;
#ifdef HAVE_VR
const int shader_target = index % NUMSHADERTARGETS;
if (vr_started && shader_target >= SHADER_FLOOR && shader_target <= SHADER_SKY)
source_to_preprocess = Z_StrDup(GLSL_VR_VERTEX_SHADER);
#endif
char *preprocessed = HWR_PreprocessShader(source_to_preprocess);
#ifdef HAVE_VR
if (source_to_preprocess != vertex_source)
Z_Free(source_to_preprocess);
#endif
if (!preprocessed) return;
GL_LoadShader(index, preprocessed, HWD_SHADERSTAGE_VERTEX);
}

View file

@ -23,12 +23,35 @@
//
#define GLSL_FALLBACK_VERTEX_SHADER \
"uniform mat4 vrEyeMatrix;\n" \
"uniform mat4 vrEyeProjection;\n" \
"uniform mat4 vrHeadPoseMatrix;\n" \
"void main()\n" \
"{\n" \
"gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
"gl_FrontColor = gl_Color;\n" \
"gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;\n" \
"gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" \
"\tif (vrEyeProjection[0][0] != 0.0)\n" \
"\t{\n" \
"\t\tgl_Position = vrEyeProjection * vrEyeMatrix * vrHeadPoseMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
"\t\tgl_ClipVertex = vrEyeMatrix * vrHeadPoseMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
"\t}\n" \
"\telse\n" \
"\t{\n" \
"\t\tgl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
"\t\tgl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" \
"\t}\n" \
"\tgl_FrontColor = gl_Color;\n" \
"\tgl_TexCoord[0].xy = gl_MultiTexCoord0.xy;\n" \
"}\0"
#define GLSL_VR_VERTEX_SHADER \
"uniform mat4 vrEyeMatrix;\n" \
"uniform mat4 vrEyeProjection;\n" \
"uniform mat4 vrHeadPoseMatrix;\n" \
"void main()\n" \
"{\n" \
"\tgl_Position = vrEyeProjection * vrEyeMatrix * vrHeadPoseMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
"\tgl_ClipVertex = vrEyeMatrix * vrHeadPoseMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
"\tgl_FrontColor = gl_Color;\n" \
"\tgl_TexCoord[0].xy = gl_MultiTexCoord0.xy;\n" \
"}\0"
//

View file

@ -17,6 +17,11 @@
#include "../g_game.h"
#include "../r_fps.h"
#ifdef HAVE_VR
#include "../screen.h"
#include "../vr/vr_main.h"
#endif
// ==========================================================================
// Sky dome rendering, ported from PrBoom+
@ -163,6 +168,11 @@ void HWR_BuildSkyDome(void)
void HWR_DrawSkyBackground(player_t *player)
{
#ifdef HAVE_VR
if (vr_started && cv_vrcomfortmode.value)
return;
#endif
GL_SetBlend(PF_Translucent|PF_NoDepthTest|PF_Modulated);
if (cv_glskydome.value)

View file

@ -1781,7 +1781,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
float base_topoffs = FIXED_TO_FLOAT(spr_topoffset);
#ifdef ROTSPRITE
spriterotangle = R_SpriteRotationAngle(thing, NULL, &interp, false);
spriterotangle = R_SpriteRotationAngle(thing, NULL, &interp, (affinesprite && vflip));
if (spriterotangle != 0
&& !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE))

View file

@ -28,6 +28,12 @@
#include "../hw_shaders.h"
#include "../hw_gpu.h"
#ifdef HAVE_VR
#include "../../vr/vr_main.h"
#include "../../screen.h"
extern boolean gl_rendering_skybox;
#endif
#if defined (HWRENDER) && !defined (NOROPENGL)
// requires GL 4.3
@ -603,6 +609,7 @@ typedef void (APIENTRY *PFNglUniform4f) (GLint, GLfloat, GLfloat, GLfloat, GL
typedef void (APIENTRY *PFNglUniform1fv) (GLint, GLsizei, const GLfloat*);
typedef void (APIENTRY *PFNglUniform2fv) (GLint, GLsizei, const GLfloat*);
typedef void (APIENTRY *PFNglUniform3fv) (GLint, GLsizei, const GLfloat*);
typedef void (APIENTRY *PFNglUniformMatrix4fv) (GLint, GLsizei, GLboolean, const GLfloat*);
typedef GLint (APIENTRY *PFNglGetUniformLocation) (GLuint, const GLchar*);
static PFNglCreateShader pglCreateShader;
@ -625,6 +632,7 @@ static PFNglUniform4f pglUniform4f;
static PFNglUniform1fv pglUniform1fv;
static PFNglUniform2fv pglUniform2fv;
static PFNglUniform3fv pglUniform3fv;
static PFNglUniformMatrix4fv pglUniformMatrix4fv;
static PFNglGetUniformLocation pglGetUniformLocation;
// 13062019
@ -659,6 +667,12 @@ typedef enum
gluniform_scr_resolution,
#ifdef HAVE_VR
gluniform_evm, // Eye View Matrix
gluniform_epm, // Eye Projection Matrix
gluniform_hpm, // Head Pose Matrix
#endif
gluniform_max,
} gluniform_t;
@ -740,6 +754,7 @@ void SetupGLFunc4(void)
*(void**)&pglUniform1fv = GetGLFunc("glUniform1fv");
*(void**)&pglUniform2fv = GetGLFunc("glUniform2fv");
*(void**)&pglUniform3fv = GetGLFunc("glUniform3fv");
*(void**)&pglUniformMatrix4fv = GetGLFunc("glUniformMatrix4fv");
*(void**)&pglGetUniformLocation = GetGLFunc("glGetUniformLocation");
#endif
@ -1389,7 +1404,10 @@ void GL_ClearBuffer(FBOOLEAN ColorMask,
if (StencilMask)
ClearMask |= GL_STENCIL_BUFFER_BIT;
GL_SetBlend(DepthMask ? PF_Occlude | CurrentPolyFlags : CurrentPolyFlags&~PF_Occlude);
// A depth clear begins a fresh 3D pass. Do not preserve HUD/UI state such
// as PF_NoDepthTest or translucent blending here, or later transparent
// materials can draw through opaque world geometry in VR multi-pass frames.
GL_SetBlend(DepthMask ? PF_Occlude : CurrentPolyFlags&~PF_Occlude);
pglClear(ClearMask);
pglEnableClientState(GL_VERTEX_ARRAY); // We always use this one
@ -1944,6 +1962,50 @@ static void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAF
UNIFORM_1(shader->uniforms[gluniform_leveltime], shader_leveltime, pglUniform1f);
#ifdef HAVE_VR
if (vr_started)
{
static const GLfloat identityMatrix[16] = {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
static const GLfloat zeroMatrix[16] = {
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f
};
const GLfloat *eyeviewmatrix = identityMatrix;
const GLfloat *headposematrix = identityMatrix;
const GLfloat *projectionmatrix = zeroMatrix;
if (!vr_drawing_ui && (vr_render_pass == VR_PASS_3D_LEFT || vr_render_pass == VR_PASS_3D_RIGHT))
{
if (gl_rendering_skybox)
{
eyeviewmatrix = vrEyeSkyboxViewMatrix[vr_current_eye];
headposematrix = (cv_vrposemode.value == 1 || cv_vrposemode.value == 3) ? vrHMDPoseSkyboxMatrix : identityMatrix;
}
else
{
eyeviewmatrix = vrScaledEyeViewMatrix[vr_current_eye];
headposematrix = (cv_vrposemode.value == 1 || cv_vrposemode.value == 3) ? vrHMDScaledPoseMatrix : identityMatrix;
}
projectionmatrix = vrEyeProjMatrix[vr_current_eye];
}
if (shader->uniforms[gluniform_evm] != -1)
pglUniformMatrix4fv(shader->uniforms[gluniform_evm], 1, GL_FALSE, eyeviewmatrix);
if (shader->uniforms[gluniform_epm] != -1)
pglUniformMatrix4fv(shader->uniforms[gluniform_epm], 1, GL_FALSE, projectionmatrix);
if (shader->uniforms[gluniform_hpm] != -1)
pglUniformMatrix4fv(shader->uniforms[gluniform_hpm], 1, GL_FALSE, headposematrix);
}
#endif
UNIFORM_2(shader->uniforms[gluniform_scr_resolution], (GLfloat)vid.width, (GLfloat)vid.height, pglUniform2f);
#undef UNIFORM_1
@ -2082,6 +2144,12 @@ static boolean Shader_CompileProgram(gl_shader_t *shader, GLint i)
// misc. (custom shaders)
shader->uniforms[gluniform_leveltime] = GETUNI("leveltime");
#ifdef HAVE_VR
shader->uniforms[gluniform_evm] = GETUNI("vrEyeMatrix");
shader->uniforms[gluniform_epm] = GETUNI("vrEyeProjection");
shader->uniforms[gluniform_hpm] = GETUNI("vrHeadPoseMatrix");
#endif
#undef GETUNI
// set permanent uniform values

View file

@ -118,43 +118,3 @@ void I_UpdateTime(fixed_t timescale)
g_time.timefrac = FLOAT_TO_FIXED(fractional);
}
}
void I_SleepDuration(precise_t duration)
{
UINT64 precision = I_GetPrecisePrecision();
INT32 sleepvalue = cv_sleep.value;
UINT64 delaygranularity;
precise_t cur;
precise_t dest;
{
double gran = round(((double)(precision / 1000) * sleepvalue * MIN_SLEEP_DURATION_MS));
delaygranularity = (UINT64)gran;
}
cur = I_GetPreciseTime();
dest = cur + duration;
// the reason this is not dest > cur is because the precise counter may wrap
// two's complement arithmetic is our friend here, though!
// e.g. cur 0xFFFFFFFFFFFFFFFE = -2, dest 0x0000000000000001 = 1
// 0x0000000000000001 - 0xFFFFFFFFFFFFFFFE = 3
while ((INT64)(dest - cur) > 0)
{
// If our cv_sleep value exceeds the remaining sleep duration, use the
// hard sleep function.
if (sleepvalue > 0 && (dest - cur) > delaygranularity)
{
#if defined(_WIN32)
DWORD sleepDuration = (DWORD)min((INT64)(dest - cur), sleepvalue);
SleepEx(sleepDuration, TRUE);
#else
I_Sleep(sleepvalue);
#endif
}
// Otherwise, this is a spinloop.
cur = I_GetPreciseTime();
}
}

View file

@ -817,7 +817,7 @@ static void K_BubbleShieldCollideDrain(player_t *player, mobj_t *bubble, INT16 d
player->bubbleblowup /= 2;
}
boolean K_BubbleShieldReflectSpecialCase(mobj_t *t)
boolean K_BubbleShieldReflectSpecialCase(const mobj_t *t)
{
if ((!t) || (P_MobjWasRemoved(t)))
return false; // Object doesn't even exist
@ -1108,13 +1108,11 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
{
if (t1Condition == true && t2Condition == false)
{
P_DamageMobj(t2, t1, t1, 1, invindamage);
return true;
return P_DamageMobj(t2, t1, t1, 1, invindamage);
}
else if (t1Condition == false && t2Condition == true)
{
P_DamageMobj(t1, t2, t2, 1, invindamage);
return true;
return P_DamageMobj(t1, t2, t2, 1, invindamage);
}
}
@ -1168,19 +1166,17 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
}
if (P_IsObjectOnGround(t1) && P_IsObjectOnGround(t2))
{
P_DamageMobj(t2, t1, t1, 1, DMG_SQUISH);
return P_DamageMobj(t2, t1, t1, 1, DMG_SQUISH);
}
else
{
switch (cv_kartairsquish.value)
{
case 1:
P_DamageMobj(t2, t1, t1, 1, DMG_SQUISH);
return true;
return P_DamageMobj(t2, t1, t1, 1, DMG_SQUISH);
break;
case 2:
P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER);
return true;
return P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER);
break;
default:
return false;
@ -1213,27 +1209,24 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
}
else if (P_IsObjectOnGround(t1) && P_IsObjectOnGround(t2))
{
P_DamageMobj(t1, t2, t2, 1, DMG_SQUISH);
return P_DamageMobj(t1, t2, t2, 1, DMG_SQUISH);
}
else
{
switch (cv_kartairsquish.value)
{
case 1:
P_DamageMobj(t1, t2, t2, 1, DMG_SQUISH);
return true;
return P_DamageMobj(t1, t2, t2, 1, DMG_SQUISH);
break;
case 2:
P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER);
return true;
return P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER);
break;
default:
return false;
break;
}
}
}
}
return true;
}
// Flame Shield dash damage
@ -1242,13 +1235,11 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
if (t1Condition == true && t2Condition == false)
{
P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER);
return true;
return P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER);
}
else if (t1Condition == false && t2Condition == true)
{
P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER);
return true;
return P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER);
}
// Attraction Shield tackle damage
@ -1257,13 +1248,21 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
t2Condition = (t2->player->attractionattack && t2->player->attractionattack_hipower && (K_GetShieldFromPlayer(t1->player) != KSHIELD_BUBBLE));
if (t1Condition == true && t2Condition == false)
{
P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER);
return true;
UINT8 damage = DMG_FLIPOVER;
if (gametypes[gametype]->rules & GTR_BUMPERS)
damage |= DMG_STEAL;
return P_DamageMobj(t2, t1, t1, 1, damage);
}
else if (t1Condition == false && t2Condition == true)
{
P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER);
return true;
UINT8 damage = DMG_FLIPOVER;
if (gametypes[gametype]->rules & GTR_BUMPERS)
damage |= DMG_STEAL;
return P_DamageMobj(t1, t2, t2, 1, damage);
}
// Battle Mode Sneaker and Bubble damage
@ -1281,13 +1280,11 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
if (t1Condition == true && t2Condition == false)
{
P_DamageMobj(t2, t1, t1, 1, DMG_WIPEOUT|DMG_STEAL);
return true;
return P_DamageMobj(t2, t1, t1, 1, DMG_WIPEOUT|DMG_STEAL);
}
else if (t1Condition == false && t2Condition == true)
{
P_DamageMobj(t1, t2, t2, 1, DMG_WIPEOUT|DMG_STEAL);
return true;
return P_DamageMobj(t1, t2, t2, 1, DMG_WIPEOUT|DMG_STEAL);
}
}

View file

@ -24,7 +24,7 @@ void K_AttractionShieldAttack(mobj_t *actor, fixed_t size, boolean hipower);
boolean K_BubbleShieldReflect(mobj_t *t1, mobj_t *t2);
boolean K_BubbleShieldCanReflect(mobj_t *t);
boolean K_BubbleShieldReflectSpecialCase(mobj_t *t);
boolean K_BubbleShieldReflectSpecialCase(const mobj_t *t);
boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2);
boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2);

View file

@ -88,6 +88,7 @@ static CV_PossibleValue_t speedo_cons_t[]= {
{0, "Default"},
{1, "Small"},
{2, "P-Meter"},
{3, "Dial"},
{0, NULL}
};
@ -115,6 +116,9 @@ consvar_t cv_draftindicator = CVAR_INIT ("draftindicator", "On", CV_SAVE, CV_OnO
consvar_t cv_showstats = CVAR_INIT ("showstats", "On", CV_SAVE, CV_OnOff, NULL);
// make char potraits use their high-res version instead
consvar_t cv_highresportrait = CVAR_INIT ("highresportrait", "Off", CV_SAVE, CV_OnOff, NULL);
typedef enum
{
NT_OFF = 0,
@ -239,6 +243,13 @@ patch_t *kp_itemtarget_far[2][2];
patch_t *kp_itemtarget_far_text[2];
patch_t *kp_itemtarget_near[2][8];
// dial speedometer
static patch_t *kp_dial = NULL;
static patch_t *kp_dialfinish = NULL;
static patch_t *kp_dialbase[4] = {NULL};
static patch_t *kp_speedpatchesdial[8] = {NULL};
static patch_t *kp_dialnum[20] = {NULL};
void K_RegisterKartHUDStuff(void)
{
K_ReloadHUDColorCvar();
@ -292,6 +303,7 @@ void K_RegisterKartHUDStuff(void)
CV_RegisterVar(&cv_newtabranking);
CV_RegisterVar(&cv_draftindicator);
CV_RegisterVar(&cv_showstats);
CV_RegisterVar(&cv_highresportrait);
// k_items.c
CV_RegisterVar(&cv_fancyroulette);
@ -432,6 +444,41 @@ void K_LoadKartHUDGraphics(void)
// Speedometer Sticker Color
HU_UpdatePatch(&kp_speedometersticker[1], "SC_SMSTC");
// Dial speedometer
HU_UpdatePatch(&kp_dial, "K_DSDIAL");
kp_dial->pivot.x = kp_dial->width / 2;
kp_dial->pivot.y = kp_dial->height / 2;
kp_dial->alignflags |= PATCHALIGN_USEPIVOTS;
HU_UpdatePatch(&kp_dialfinish, "K_DILFIN");
HU_UpdatePatch(&kp_dialbase[0], "K_DSPBS1");
HU_UpdatePatch(&kp_dialbase[1], "K_DSPBS2");
HU_UpdatePatch(&kp_dialbase[2], "K_DSPBC1");
HU_UpdatePatch(&kp_dialbase[3], "K_DSPBC2");
HU_UpdatePatch(&kp_speedpatchesdial[0], "SP_DKMH");
HU_UpdatePatch(&kp_speedpatchesdial[1], "SP_DMPH");
HU_UpdatePatch(&kp_speedpatchesdial[2], "SP_DFRAC");
HU_UpdatePatch(&kp_speedpatchesdial[3], "SP_DPERC");
HU_UpdatePatch(&kp_speedpatchesdial[4], "SC_DKMH");
HU_UpdatePatch(&kp_speedpatchesdial[5], "SC_DMPH");
HU_UpdatePatch(&kp_speedpatchesdial[6], "SC_DFRAC");
HU_UpdatePatch(&kp_speedpatchesdial[7], "SC_DPERC");
sprintf(buffer, "K_DSPNMx");
for (i = 0; i < 10; i++)
{
buffer[7] = '0'+(i%10);
HU_UpdatePatch(&kp_dialnum[i], "%s", buffer);
}
sprintf(buffer, "K_DSPNCx");
for (i = 0; i < 10; i++)
{
buffer[7] = '0'+(i%10);
HU_UpdatePatch(&kp_dialnum[i+10], "%s", buffer);
}
// Driftgauge Stickers
HU_UpdatePatch(&kp_driftgauge[0], "K_DGAU0"); // Spee
HU_UpdatePatch(&kp_driftgauge[1], "K_DGAU1"); // Achii
@ -628,7 +675,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&joyshadow, "JOYSHD");
// Input UI Wheel
HU_UpdatePatch(&kp_inputwheel, "K_WHEEL0");
HU_UpdatePatch(&kp_inputwheel, "B_WHEEL0");
kp_inputwheel->pivot.x =
(kp_inputwheel->width / 2) + kp_inputwheel->leftoffset;
kp_inputwheel->pivot.y =
@ -636,7 +683,7 @@ void K_LoadKartHUDGraphics(void)
kp_inputwheel->alignflags |=
(PATCHALIGN_AUTOCENTER | PATCHALIGN_USEPIVOTS);
HU_UpdatePatch(&kp_inputwheel_shadow, "K_WHEEL1");
HU_UpdatePatch(&kp_inputwheel_shadow, "B_WHEEL1");
kp_inputwheel_shadow->pivot.x =
(kp_inputwheel_shadow->width / 2) + kp_inputwheel_shadow->leftoffset;
kp_inputwheel_shadow->pivot.y =
@ -818,6 +865,16 @@ skincolornum_t K_GetHudColor(void)
return ((stplyr && gamestate == GS_LEVEL) ? stplyr->skincolor : cv_playercolor[0].value);
}
static boolean K_IsHighResolution(void)
{
return (vid.width >= 640 && vid.height >= 400);
}
boolean K_UseHighResPortraits(void)
{
return (cv_highresportrait.value && K_IsHighResolution());
}
static boolean K_BigLapSticker(void)
{
return ((cv_numlaps.value > 9) && (!stplyr->exiting));
@ -1308,11 +1365,13 @@ void K_getRingsDrawinfo(drawinfo_t *out)
{
INT32 fx, fy, splitflags = 0;
boolean dialspeedometer = (cv_newspeedometer.value == 3 && r_splitscreen < 1);
// pain and suffering defined below
if (r_splitscreen < 2) // don't change shit for THIS splitscreen.
{
fx = RING_X;
fy = RING_Y;
fx = RING_X + ((dialspeedometer) ? 31 : 0);
fy = RING_Y + ((dialspeedometer) ? 14 : 0);
splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANS|V_SPLITSCREEN;
}
else
@ -2295,7 +2354,9 @@ static boolean K_drawKartPositionFaces(void)
else
colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE);
patch_t *facerank = faceprefix[players[rankplayer[i]].skin][FACE_RANK];
boolean hires = K_UseHighResPortraits();
patch_t *facerank = faceprefix[players[rankplayer[i]].skin][hires ? FACE_WANTED : FACE_RANK];
offsets.x = facerank->leftoffset;
offsets.y = facerank->topoffset;
@ -2314,14 +2375,14 @@ static boolean K_drawKartPositionFaces(void)
}
#endif
V_DrawMappedPatch(FACE_X + offsets.x, Y + offsets.y, V_HUDTRANS|V_SNAPTOLEFT, facerank, colormap);
V_DrawFixedPatch((FACE_X + offsets.x)<<FRACBITS, (Y + offsets.y)<<FRACBITS, hires ? FRACUNIT / 2 : FRACUNIT, V_HUDTRANS|V_SNAPTOLEFT, facerank, colormap);
if (players[rankplayer[i]].smonitortimer)
{
colormap = R_GetTranslationColormap(TC_BLINK, K_SMonitorColor(leveltime / 2), GTC_CACHE);
smntrhudtrans = K_SMonitorHUDVisibility(players[rankplayer[i]].smonitortimer);
V_DrawMappedPatch(FACE_X + offsets.x, Y + offsets.y, smntrhudtrans|V_SNAPTOLEFT|V_ADD, facerank, colormap);
V_DrawFixedPatch((FACE_X + offsets.x)<<FRACBITS, (Y + offsets.y)<<FRACBITS, hires ? FRACUNIT / 2 : FRACUNIT, smntrhudtrans|V_SNAPTOLEFT|V_ADD, facerank, colormap);
}
if (LUA_HudEnabled(hud_battlebumpers))
@ -2683,7 +2744,7 @@ void K_SetScoreboardModStatus(const char *name, SINT8 active)
CONS_Alert(CONS_WARNING, "Server mod '%s' does not exist so status cannot be changed.\n", name);
}
#define BASEMODS 21
#define BASEMODS 23
static void K_DrawServerMods(INT32 x, INT32 y)
{
UINT8 i, j;
@ -2713,6 +2774,8 @@ static void K_DrawServerMods(INT32 x, INT32 y)
{"Bump Spring", 0, &cv_kartbumpspring, -1, true},
{"Keep Stuff", 0, NULL, K_KeepStuffActive() > 0, true},
{"No BananaDrag", 0, NULL, K_TrailSlowActive() < 1, true},
{"No Air Bumps", 0, NULL, K_GetPlayerBump() == PBUMP_GNDONLY, true},
{"No PvP Bumps", 0, NULL, K_GetPlayerBump() == PBUMP_NONE, true},
//TODO: separate drawer that enumerates item changes?
};
@ -2850,9 +2913,12 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN
else
V_DrawString(x + 20, y2, ((tab[i].num == whiteplayer) ? hightlightcolor : 0)|V_ALLOWLOWERCASE, playername);
patch_t *facerank = faceprefix[players[tab[i].num].skin][FACE_RANK];
boolean hires = K_UseHighResPortraits();
patch_t *facerank = faceprefix[players[tab[i].num].skin][hires ? FACE_WANTED : FACE_RANK];
INT16 leftoffset = facerank->leftoffset;
INT16 topoffset = facerank->topoffset;
V_DrawMappedPatch(x+facerank->leftoffset, y-4+facerank->topoffset, 0, facerank, colormap);
V_DrawFixedPatch((x+leftoffset)<<FRACBITS, (y-4+topoffset)<<FRACBITS, hires ? FRACUNIT/2 : FRACUNIT, 0, facerank, colormap);
/*if ((gametypes[gametype]->rules & GTR_BUMPERS) && players[tab[i].num].bumper > 0) -- not enough space for this
{
@ -3026,7 +3092,7 @@ static void K_drawKartStatsnLives(void)
if ((cv_lives_xoffset.value == 0 && cv_lives_yoffset.value == 0) || split)
{
if ((cv_newspeedometer.value == 0 || cv_newspeedometer.value == 2) && !K_RingsActive() && !split)
if ((cv_newspeedometer.value == 0 || cv_newspeedometer.value == 2 || cv_newspeedometer.value == 3) && !K_RingsActive() && !split)
{
offsetx = 25;
offsety = 15;
@ -3039,8 +3105,11 @@ static void K_drawKartStatsnLives(void)
// We specify stplyr->skincolor since we want it to match the player and not the hud color.
UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE);
patch_t *facerank = faceprefix[stplyr->skin][FACE_RANK];
V_DrawMappedPatch(fx+59+offsetx+facerank->leftoffset, fy-16+offsety+facerank->topoffset, V_HUDTRANS|splitflags, facerank, colormap);
boolean hires = K_UseHighResPortraits();
patch_t *facerank = faceprefix[stplyr->skin][hires ? FACE_WANTED : FACE_RANK];
INT16 topoffset = hires ? facerank->topoffset / 2 : facerank->topoffset;
INT16 leftoffset = hires ? facerank->leftoffset / 2 : facerank->leftoffset;
V_DrawFixedPatch((fx+59+offsetx+leftoffset)<<FRACBITS, (fy-16+offsety+topoffset)<<FRACBITS, hires ? FRACUNIT/2 : FRACUNIT, V_HUDTRANS|splitflags, facerank, colormap);
if (stplyr->lives >= 0 && uselives)
V_DrawScaledPatch(fx+77+offsetx, fy-11+offsety, V_HUDTRANS|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow OR underflow
@ -3057,8 +3126,8 @@ static void K_drawKartStatsnLives(void)
colormapstat2 = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_MUSTARD, GTC_CACHE);
}
V_DrawFixedPatch((fx+56+offsetx+facerank->leftoffset)<< FRACBITS, (fy-19+offsety+facerank->topoffset)<< FRACBITS, FRACUNIT, V_HUDTRANS|splitflags, kp_facenum[(stplyr->kartspeed % 10)], colormapstat);
V_DrawFixedPatch((fx+69+offsetx+facerank->leftoffset)<< FRACBITS, (fy-4+offsety+facerank->topoffset)<< FRACBITS, FRACUNIT, V_HUDTRANS|splitflags, kp_facenum[(stplyr->kartweight % 10)], colormapstat2);
V_DrawFixedPatch((fx+56+offsetx+leftoffset)<< FRACBITS, (fy-19+offsety+topoffset)<< FRACBITS, FRACUNIT, V_HUDTRANS|splitflags, kp_facenum[(stplyr->kartspeed % 10)], colormapstat);
V_DrawFixedPatch((fx+69+offsetx+leftoffset)<< FRACBITS, (fy-4+offsety+topoffset)<< FRACBITS, FRACUNIT, V_HUDTRANS|splitflags, kp_facenum[(stplyr->kartweight % 10)], colormapstat2);
}
}
}
@ -3137,33 +3206,201 @@ static void K_drawKartAccessibilityIcons(INT32 fx)
}
}
#ifdef ROTSPRITE
#define DIALSPDDIV 97090 // 1.48148; converts 200 to 135
#define MPHDIV 68283 // 1.04192; converts 141 to 135
static void K_DrawDialNum(INT32 x, INT32 y, boolean colorized, INT32 flags, INT32 num, INT32 digits, const UINT8 *colormap)
{
INT32 w = 0;
w = kp_dialnum[0]->width;
if (flags & V_NOSCALESTART)
w *= vid.dup;
if (num < 0)
num = -num;
// draw the number
do
{
x -= (w);
if (colorized)
V_DrawFixedPatch(x << FRACBITS, y << FRACBITS, FRACUNIT, flags, kp_dialnum[(num % 10) + 10], colormap);
else
V_DrawFixedPatch(x << FRACBITS, y << FRACBITS, FRACUNIT, flags, kp_dialnum[num % 10], colormap);
num /= 10;
} while (--digits);
}
static void K_DrawDialLaps(INT32 x, INT32 y, INT32 num, INT32 total, INT32 flags)
{
INT32 fx;
fx = x + ((num < 10) ? 0 : 6);
V_DrawRankNum(fx, y, flags, num, (num < 10) ? 1 : 2, NULL);
V_DrawScaledPatch(fx + 2, y, flags, frameslash);
V_DrawRankNum(fx + 13 + ((total < 10) ? 0 : 6), y, flags, total, (total < 10) ? 1 : 2, NULL);
}
static void K_DrawDialSpeedometer(fixed_t speed,
fixed_t divisor,
UINT16 labeln,
INT32 splitflags,
boolean battlemode,
boolean infoactive,
boolean colorized)
{
const UINT8* colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE);
INT32 ringoffset = 0;
const UINT8 infoidx = (infoactive) ? 1 : 0;
const fixed_t spd = FixedDiv(speed, divisor);
const angle_t speedangle =
FixedAngle(((min(135 * FRACUNIT, spd) - (45 * FRACUNIT))));
if (K_RingsActive() == true)
{
ringoffset = -16;
}
if (colorized) // Colourized hud
{
V_DrawMappedPatch(
SPDM_X, SPDM_Y - 25, (V_HUDTRANS | splitflags), kp_dialbase[infoidx + 2], colormap);
V_DrawMappedPatch(SPDM_X,
SPDM_Y + 14,
V_HUDTRANS | splitflags,
kp_speedpatchesdial[labeln + 4],
colormap);
}
else
{
V_DrawScaledPatch(SPDM_X, SPDM_Y - 25, (V_HUDTRANS | splitflags), kp_dialbase[infoidx]);
V_DrawScaledPatch(SPDM_X,
SPDM_Y + 14,
V_HUDTRANS | splitflags,
kp_speedpatchesdial[labeln]);
}
K_DrawDialNum(SPDM_X + 10,
SPDM_Y + 9,
colorized,
V_HUDTRANS | splitflags,
speed / FRACUNIT,
3,
colormap);
// gotta center the dial manually
V_DrawRotatedPatch((SPDM_X - 19) << FRACBITS,
(SPDM_Y - 1) << FRACBITS,
speedangle,
FRACUNIT,
FRACUNIT,
(V_HUDTRANS | splitflags),
kp_dial,
colormap);
if (!infoactive)
{
// no need to draw info if we're not supposed to
return;
}
// draw the info
if (battlemode)
{
if (itembreaker)
{
V_DrawMappedPatch(SPDM_X + 28, SPDM_Y + 12, V_HUDTRANS | splitflags, kp_itemboxminimap, NULL);
K_DrawDialLaps(SPDM_X + 46,
SPDM_Y + 13,
numtargets,
nummapboxes,
V_HUDTRANS | splitflags);
}
else
{
if (stplyr->bumper <= 0 && (gametypes[gametype]->rules & GTR_KARMA) && comeback)
{
V_DrawMappedPatch(
SPDM_X + 28, SPDM_Y + 12, V_HUDTRANS | splitflags, kp_splitkarmabomb, colormap);
K_DrawDialLaps(SPDM_X + 46,
SPDM_Y + 13,
stplyr->karmapoints,
2,
V_HUDTRANS | splitflags);
}
else // the above doesn't need to account for weird stuff since the max amount of karma
// necessary is always 2 ^^^^
{
INT32 maxbumper = K_StartingBumperCount();
V_DrawMappedPatch(
SPDM_X + 28, SPDM_Y + 12, V_HUDTRANS | splitflags, kp_rankbumper, colormap);
K_DrawDialLaps(SPDM_X + 46,
SPDM_Y + 13,
stplyr->bumper,
maxbumper,
V_HUDTRANS | splitflags);
}
}
}
else
{
V_DrawScaledPatch(SPDM_X + 28, SPDM_Y + 12, V_HUDTRANS | splitflags, kp_splitlapflag);
if (stplyr->exiting)
V_DrawScaledPatch(SPDM_X + 39, SPDM_Y + 13, V_HUDTRANS | splitflags, kp_dialfinish);
else
K_DrawDialLaps(SPDM_X + 46,
SPDM_Y + 13,
stplyr->laps,
numlaps,
V_HUDTRANS | splitflags);
}
}
#endif
static void K_drawKartSpeedometer(void)
{
static fixed_t convSpeed;
static fixed_t convSpeed[2] = {0};
UINT8 labeln = 0;
UINT8 numbers[3];
INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN;
INT32 battleoffset = 0;
INT32 ringoffset = 0;
INT32 oldringoffset = 0;
#ifdef ROTSPRITE
fixed_t dial_divisor = DIALSPDDIV;
#endif
switch (cv_kartspeedometer.value)
{
case 1: // Kilometers
convSpeed = FixedDiv(FixedMul(stplyr->speed, 142371), mapobjectscale) / FRACUNIT; // 2.172409058
convSpeed[0] = FixedDiv(FixedMul(stplyr->speed, 142371), mapobjectscale); // 2.172409058
convSpeed[1] = convSpeed[0] / FRACUNIT;
labeln = 0;
break;
case 2: // Miles
convSpeed = FixedDiv(FixedMul(stplyr->speed, 88465), mapobjectscale) / FRACUNIT; // 1.349868774
convSpeed[0] = FixedDiv(FixedMul(stplyr->speed, 88465), mapobjectscale); // 1.349868774
convSpeed[1] = convSpeed[0] / FRACUNIT;
labeln = 1;
break;
case 3: // Fracunits
convSpeed = FixedDiv(stplyr->speed, mapobjectscale) / FRACUNIT; // 1.0. duh.
convSpeed[0] = FixedDiv(stplyr->speed, mapobjectscale); // 1.0. duh.
convSpeed[1] = convSpeed[0] / FRACUNIT;
labeln = 2;
break;
case 4: // Sonic Drift 2 style percentage
if (stplyr->mo)
convSpeed = (FixedDiv(stplyr->speed, FixedMul(K_GetKartSpeed(stplyr, false, false), K_PlayerBaseFriction(stplyr, ORIG_FRICTION)))*100)>>FRACBITS;
{
convSpeed[0] = (FixedDiv(stplyr->speed, FixedMul(K_GetKartSpeed(stplyr, false, false), K_PlayerBaseFriction(stplyr, ORIG_FRICTION)))*100);
convSpeed[1] = convSpeed[0] >> FRACBITS;
}
labeln = 3;
break;
default:
@ -3172,8 +3409,11 @@ static void K_drawKartSpeedometer(void)
// Don't overflow
// (negative speed IS really high speed :V)
if (convSpeed > 999 || convSpeed < 0)
convSpeed = 999;
if (convSpeed[1] > 999 || convSpeed[1] < 0)
{
convSpeed[1] = 999;
convSpeed[0] = convSpeed[1] * FRACUNIT;
}
if (cv_speed_xoffset.value == 0 && cv_speed_yoffset.value == 0)
{
@ -3191,16 +3431,16 @@ static void K_drawKartSpeedometer(void)
{
switch (cv_kartspeedometer.value) {
case 1:
V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%3d km/h", convSpeed));
V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%3d km/h", convSpeed[1]));
break;
case 2:
V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%3d mph", convSpeed));
V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%3d mph", convSpeed[1]));
break;
case 3:
V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%3d fu/t", convSpeed));
V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%3d fu/t", convSpeed[1]));
break;
case 4:
V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%4d %%", convSpeed));
V_DrawKartString(SPDM_X + oldringoffset, SPDM_Y-18 + battleoffset + ringoffset, V_HUDTRANS|splitflags, va("%4d %%", convSpeed[1]));
break;
default:
break;
@ -3208,9 +3448,9 @@ static void K_drawKartSpeedometer(void)
}
else if (cv_newspeedometer.value == 1)
{
numbers[0] = ((convSpeed / 100) % 10);
numbers[1] = ((convSpeed / 10) % 10);
numbers[2] = (convSpeed % 10);
numbers[0] = ((convSpeed[1] / 100) % 10);
numbers[1] = ((convSpeed[1] / 10) % 10);
numbers[2] = (convSpeed[1] % 10);
if (!K_UseColorHud())
{
@ -3254,10 +3494,27 @@ static void K_drawKartSpeedometer(void)
V_DrawScaledPatch(SPDM_X, SPDM_Y-yoffset + battleoffset + ringoffset, V_HUDTRANS|splitflags, (!K_RingsActive() ? kp_kartzspeedo : kp_kartzspeedo_smol)[spdpatch]);
}
#ifdef ROTSPRITE
else if (cv_newspeedometer.value == 3)
{
K_DrawDialSpeedometer(convSpeed[0],
dial_divisor,
labeln,
splitflags,
(boolean)((gametypes[gametype]->rules & GTR_BUMPERS) == GTR_BUMPERS),
(LUA_HudEnabled(hud_gametypeinfo)),
(K_UseColorHud()));
}
#endif
K_drawKartAccessibilityIcons((cv_newspeedometer.value == 0 || cv_newspeedometer.value == 2) ? 50 : 56);
}
#ifdef ROTSPRITE
#undef DIALSPDDIV
#undef MPHDIV
#endif
static void K_drawRingMeter(void)
{
UINT8 rn[2];
@ -5463,8 +5720,7 @@ static void K_drawKartFirstPerson(void)
// doesn't need to ever support 4p
static void K_drawInput(void)
{
static INT32 pn = 0;
INT32 target = 0, splitflags = (V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SLIDEIN);
INT32 splitflags = (V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SLIDEIN);
INT32 x = (BASEVIDWIDTH - 32)*FRACUNIT, y = (BASEVIDHEIGHT - 24)*FRACUNIT;
UINT8 *shadowcolormap = NULL;
INT32 offs, col;
@ -6241,10 +6497,33 @@ static void K_SlipstreamIndicator(boolean tiny)
V_DrawThinString(fx - V_StringWidth(str, flags)/2, fy, flags, str);
}
// determines if gametype info (laps/bumpers) should be hidden
static boolean K_DisableGametypeInfo(void)
{
if (!LUA_HudEnabled(hud_gametypeinfo))
{
return true;
}
#ifdef ROTSPRITE
if (r_splitscreen || (!cv_kartspeedometer.value))
{
// don't need to run checks if we're in splitscreen, or not using the speedometer
return false;
}
if (cv_newspeedometer.value == 3)
return true;
#endif
return false;
}
void K_drawKartHUD(void)
{
boolean islonesome = false;
boolean battlefullscreen = false;
boolean gameinfovisible = false;
UINT8 viewnum = R_GetViewNumber();
boolean freecam = camera[viewnum].freecam; //disable some hud elements w/ freecam
UINT8 i;
@ -6352,6 +6631,9 @@ void K_drawKartHUD(void)
if (!stplyr->spectator && !freecam) // Bottom of the screen elements, don't need in spectate mode
{
// get gametype info visibility ahead of time
gameinfovisible = (!K_DisableGametypeInfo());
if (demo.title) // Draw title logo instead in demo.titles
{
INT32 x = (BASEVIDWIDTH - 32), y = 128, snapflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT;
@ -6388,12 +6670,15 @@ void K_drawKartHUD(void)
{
if (gametypes[gametype]->rules & GTR_CIRCUIT)
{
K_drawKartLaps();
if (gameinfovisible)
K_drawKartLaps();
K_drawKartStatsnLives();
}
else if (gametypes[gametype]->rules & GTR_BUMPERS)
{
K_drawKartBumpersOrKarma();
if (gameinfovisible)
K_drawKartBumpersOrKarma();
}
}

View file

@ -73,6 +73,8 @@ extern consvar_t cv_newtabranking;
extern consvar_t cv_kartdebughudtracker;
extern consvar_t cv_highresportrait;
extern patch_t *kp_facenum[MAXPLAYERS+1];
extern patch_t *kp_itemboxminimap;
extern patch_t *kp_minimapdot;
@ -189,6 +191,8 @@ extern patch_t *kp_itemtarget_far[2][2];
extern patch_t *kp_itemtarget_far_text[2];
extern patch_t *kp_itemtarget_near[2][8];
boolean K_UseHighResPortraits(void);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -2160,7 +2160,6 @@ void K_PlayerItemThink(player_t *player, boolean onground)
{
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
angle_t newangle;
INT32 moloop;
mobj_t *mo = NULL;
mobj_t *prev = player->mo;
@ -2233,7 +2232,6 @@ void K_PlayerItemThink(player_t *player, boolean onground)
case KITEM_ORBINAUT:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
angle_t newangle;
INT32 moloop;
mobj_t *mo = NULL;
mobj_t *prev = player->mo;
@ -2267,7 +2265,6 @@ void K_PlayerItemThink(player_t *player, boolean onground)
case KITEM_JAWZ:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
angle_t newangle;
INT32 moloop;
mobj_t *mo = NULL;
mobj_t *prev = player->mo;
@ -2404,7 +2401,7 @@ void K_PlayerItemThink(player_t *player, boolean onground)
{
if (K_GetShieldFromPlayer(player) != KSHIELD_ATTRACTION)
{
mobj_t *shield = K_SpawnEquippedItem(player, KITEMEQUIP_SHIELD, MT_ATTRACTIONSHIELD, 0, NULL);
/*mobj_t *shield =*/ K_SpawnEquippedItem(player, KITEMEQUIP_SHIELD, MT_ATTRACTIONSHIELD, 0, NULL);
S_StartSound(player->mo, sfx_attrsg);
}
@ -2530,7 +2527,7 @@ void K_PlayerItemThink(player_t *player, boolean onground)
{
if (K_GetShieldFromPlayer(player) != KSHIELD_THUNDER)
{
mobj_t *shield = K_SpawnEquippedItem(player, KITEMEQUIP_SHIELD, MT_THUNDERSHIELD, 0, NULL);
/*mobj_t *shield =*/K_SpawnEquippedItem(player, KITEMEQUIP_SHIELD, MT_THUNDERSHIELD, 0, NULL);
S_StartSound(player->mo, sfx_s3k41);
}
@ -2554,7 +2551,7 @@ void K_PlayerItemThink(player_t *player, boolean onground)
case KITEM_BUBBLESHIELD:
if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE)
{
mobj_t *shield = K_SpawnEquippedItem(player, KITEMEQUIP_SHIELD, MT_BUBBLESHIELD, 0, NULL);
/*mobj_t *shield =*/ K_SpawnEquippedItem(player, KITEMEQUIP_SHIELD, MT_BUBBLESHIELD, 0, NULL);
S_StartSound(player->mo, sfx_s3k3f);
if (player->bubblehealth <= 0 || player->bubblehealth > MAXBUBBLEHEALTH)
player->bubblehealth = MAXBUBBLEHEALTH;
@ -2640,7 +2637,7 @@ void K_PlayerItemThink(player_t *player, boolean onground)
player->itemamount--;
player->flametimer = flametime;
player->flamedash = 0;
mobj_t *shield = K_SpawnEquippedItem(player, KITEMEQUIP_SHIELD, MT_FLAMESHIELD, 0, NULL);
/*mobj_t *shield =*/ K_SpawnEquippedItem(player, KITEMEQUIP_SHIELD, MT_FLAMESHIELD, 0, NULL);
S_StartSound(player->mo, sfx_s3k3e);
}
break;
@ -2980,7 +2977,6 @@ mobj_t *K_SpawnEquippedItem(player_t *player, kartitemequip_e equipstyle, mobjty
switch (equipstyle)
{
case KITEMEQUIP_ORBIT: // Kart orbit items
K_MatchGenericExtraFlags(mo, player->mo);
newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90;
mo->angle = newangle;
mo->movecount = player->itemamount;
@ -2997,6 +2993,7 @@ mobj_t *K_SpawnEquippedItem(player_t *player, kartitemequip_e equipstyle, mobjty
mo->movedir = mo->lastlook = moloop+1;
break;
case KITEMEQUIP_SHIELD: // Force Field Shields
K_FlipFromObject(mo, player->mo);
P_SetScale(mo, (mo->destscale = (5*mo->destscale)>>2));
P_SetTarget(&player->shieldtracer, mo);
break;

View file

@ -491,6 +491,7 @@ void K_RegisterKartStuff(void)
CV_RegisterVar(&cv_kartkeepstuff);
CV_RegisterVar(&cv_trailslow);
CV_RegisterVar(&cv_playerbump);
}
//}
@ -7242,6 +7243,12 @@ INT32 K_GetBumpSpark(void)
return max(min(BUMPSPARK_ALL, (bumpsparktype_t)bumpsparkactive), BUMPSPARK_NONE);
}
// Returns the playerbump value as an enum.
playerbumptype_t K_GetPlayerBump(void)
{
return max(min(PBUMP_NONE, (playerbumptype_t)playerbumpactive), PBUMP_STANDARD);
}
/** \brief Decreases various kart timers and powers per frame. Called in P_PlayerThink in p_user.c
\param player player object passed from P_PlayerThink
@ -7978,7 +7985,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
fixed_t vrate = (-FixedMul(rate * 2 - FRACUNIT, rate * 2 - FRACUNIT)) + FRACUNIT;
FV2_Rotate(&tyreoffs, 45*FRACUNIT);
player->mo->spritexoffset += Easing_Linear(rate, 0, (5*tyredistance/2));
player->mo->spriteyoffset += Easing_Linear(vrate, 0, abs(tyreoffs.y/2) * P_MobjFlip(player->mo));
player->mo->spriteyoffset += Easing_Linear(vrate, 0, abs(tyreoffs.y/2));
// CONS_Debug(DBG_PLAYER, "tyre dist : %4.3f\n", FIXED_TO_FLOAT(tyredistance));
// CONS_Debug(DBG_PLAYER, "kart tilt : %4.3f\n", FIXED_TO_FLOAT(player->karttilt));
@ -10058,7 +10065,7 @@ static boolean K_PlayerCanRecoverySpin(player_t *player)
static void K_RecoveryDash(player_t *player)
{
if (K_PlayerWantsRecoverySpin(player) && K_IsPlayerDamaged(player) && player->wipeoutslow == 0)
if (K_RecoveryDashActive() && K_PlayerWantsRecoverySpin(player) && K_IsPlayerDamaged(player) && player->wipeoutslow == 0)
{
player->wipeoutslow = max(wipeoutslowtime + 1, player->spinouttimer + 4);
}
@ -10148,6 +10155,10 @@ static void K_RecoveryDash(player_t *player)
travelangle = player->mo->angle;
for (INT32 i = 0; i < 2; i++)
{
if (player->karttilt && ((player->karttilt < 0 && !(i & 1)) || (player->karttilt > 0 && (i & 1))))
{
continue;
}
newx = P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(28*FRACUNIT, player->mo->scale));
newy = P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(28*FRACUNIT, player->mo->scale));
skid = P_SpawnMobjFromMobj(player->mo, newx, newy, (10*player->mo->scale), MT_OVERLAY);
@ -10189,6 +10200,14 @@ static void K_RecoveryDash(player_t *player)
sparkangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
for (INT32 i = 0; i < 2; i++)
{
if (P_IsObjectOnGround(player->mo) && player->karttilt &&
((player->karttilt < 0 && !(i & 1)) || (player->karttilt > 0 && (i & 1)))
)
{
// when the kart is tilting don't spawn the sparks for the tyre that is lifted off the ground
// player has to be grounded in general for this to make sense, so never skip spawning sparks if player is airborne
continue;
}
newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale));
newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale));
spark = P_SpawnMobj(newx, newy, player->mo->z, MT_DRIFTSPARK);
@ -12143,6 +12162,7 @@ void K_KartAttractHomingAttack(player_t *player)
{
mobj_t *mo;
angle_t effectangle = player->mo->angle;
boolean raceFirst = ((gametypes[gametype]->rules & GTR_CIRCUIT) && player->position <= 1);
if (player->speed > 0)
{
effectangle = R_PointToAngle2(0,0, player->mo->momx, player->mo->momy);
@ -12156,7 +12176,7 @@ void K_KartAttractHomingAttack(player_t *player)
P_SpawnGhostMobj(player->mo);
if (player->attractionattack_hipower && player->position > 1 && lastTarg >= 0)
if (player->attractionattack_hipower && lastTarg >= 0 && (!raceFirst))
{
player->attractionboost = Easing_InCubic(influence, ATTRACTIONSPEEDHIMIN, ATTRACTIONSPEEDHIMAX);

View file

@ -391,6 +391,7 @@ boolean K_ItemListActive(void);
boolean K_ItemPushingActive(void);
boolean K_TrailSlowActive(void);
INT32 K_GetBumpSpark(void);
playerbumptype_t K_GetPlayerBump(void);
boolean K_BoostChain(player_t *player, INT32 timer, boolean chainsound);
INT32 K_ChainOrDeincrementTime(player_t *player, INT32 timer, INT32 deincrement, boolean chainsound);
boolean K_UsingLegacyCheckpoints(void);

View file

@ -37,6 +37,7 @@
#include "console.h" // cons_menuhighlight
#include "k_itemlist.hpp"
#include "k_vote.h"
#include "k_hud.h"
static INT32 votetimer;
@ -601,9 +602,11 @@ void Y_VoteDrawer(void)
if (players[i].skincolor)
{
UINT8 *colormap = R_GetTranslationColormap(players[i].skin, players[i].skincolor, GTC_CACHE);
patch_t *facerank = faceprefix[players[i].skin][FACE_RANK];
boolean hires = K_UseHighResPortraits();
patch_t *facerank = faceprefix[players[i].skin][hires ? FACE_WANTED : FACE_RANK];
fixed_t hiresscale = hires ? FRACUNIT/2 : FRACUNIT;
V_DrawMappedPatch(x+24+facerank->leftoffset, y+(highplayers ? smallfaceheight : bigfaceheight)+facerank->topoffset, V_SNAPTOLEFT, facerank, colormap);
V_DrawFixedPatch((x+24+facerank->leftoffset)<<FRACBITS, (y+(highplayers ? smallfaceheight : bigfaceheight)+facerank->topoffset)<<FRACBITS, hiresscale, V_SNAPTOLEFT, facerank, colormap);
}
if (!splitscreen && i == consoleplayer)

View file

@ -4602,6 +4602,13 @@ static int lib_kGetBumpSpark(lua_State *L)
return 1;
}
// Gets the currently active playerbump type.
static int lib_kGetPlayerBump(lua_State *L)
{
lua_pushinteger(L, K_GetPlayerBump());
return 1;
}
// Checks if current map is using legacy boss3 bassed checkpoints. Useful for map compat.
static int lib_kUsingLegacyCheckpoints(lua_State *L)
{
@ -5961,6 +5968,7 @@ static luaL_Reg lib[] = {
{"K_ItemListActive", lib_kItemListActive},
{"K_TrailSlowActive", lib_kTrailSlowActive},
{"K_GetBumpSpark",lib_kGetBumpSpark},
{"K_GetPlayerBump",lib_kGetPlayerBump},
{"K_UsingLegacyCheckpoints",lib_kUsingLegacyCheckpoints},
{"K_DoBoost",lib_kDoBoost},
{"K_ClearBoost",lib_kClearBoost},

View file

@ -388,28 +388,28 @@ int LUA_PushGlobals(lua_State *L, const char *word)
lua_pushinteger(L, numgotboxes);
return 1;
} else if (fastcmp(word,"itembreaker")) {
lua_pushinteger(L, itembreaker);
lua_pushboolean(L, itembreaker);
return 1;
} else if (fastcmp(word,"battleprisons")) {
lua_pushinteger(L, itembreaker);
lua_pushboolean(L, itembreaker);
return 1;
} else if (fastcmp(word,"ringsactive")) {
lua_pushinteger(L, ringsactive);
lua_pushboolean(L, ringsactive);
return 1;
} else if (fastcmp(word,"stackingactive")) {
lua_pushinteger(L, stackingactive);
lua_pushboolean(L, stackingactive);
return 1;
} else if (fastcmp(word,"chainingactive")) {
lua_pushinteger(L, chainingactive);
lua_pushboolean(L, chainingactive);
return 1;
} else if (fastcmp(word,"slipdashactive")) {
lua_pushinteger(L, slipdashactive);
lua_pushboolean(L, slipdashactive);
return 1;
} else if (fastcmp(word,"slopeboostactive")) {
lua_pushinteger(L, slopeboostactive);
lua_pushboolean(L, slopeboostactive);
return 1;
} else if (fastcmp(word,"draftingactive")) {
lua_pushinteger(L, draftingactive);
lua_pushboolean(L, draftingactive);
return 1;
} else if (fastcmp(word,"airdropactive")) {
lua_pushinteger(L, airdropactive);
@ -418,22 +418,25 @@ int LUA_PushGlobals(lua_State *L, const char *word)
lua_pushinteger(L, bumpsparkactive);
return 1;
} else if (fastcmp(word,"purpledriftactive")) {
lua_pushinteger(L, purpledriftactive);
lua_pushboolean(L, purpledriftactive);
return 1;
} else if (fastcmp(word,"recoverydashactive")) {
lua_pushinteger(L, recoverydashactive);
lua_pushboolean(L, recoverydashactive);
return 1;
} else if (fastcmp(word,"keepstuffactive")) {
lua_pushinteger(L, keepstuffactive);
lua_pushboolean(L, keepstuffactive);
return 1;
} else if (fastcmp(word,"itemlittering")) {
lua_pushinteger(L, itemlittering);
lua_pushboolean(L, itemlittering);
return 1;
} else if (fastcmp(word,"itempushing")) {
lua_pushboolean(L, itempushing); // hmm... i think this should be a boolean
return 1;
} else if (fastcmp(word,"trailslow_active") || fastcmp(word,"trailslowactive")) {
lua_pushinteger(L, trailslow_active);
lua_pushboolean(L, trailslow_active); // I agree!
return 1;
} else if (fastcmp(word,"playerbumpactive")) {
lua_pushinteger(L, playerbumpactive);
return 1;
} else if (fastcmp(word,"hyubgone")) {
lua_pushinteger(L, K_GetKartResult("hyudoro")->cooldown);
@ -588,39 +591,41 @@ int LUA_WriteGlobals(lua_State *L, const char *word)
else if (fastcmp(word,"introtime"))
introtime = (tic_t)luaL_checkinteger(L, 2);
else if (fastcmp(word,"itembreaker"))
itembreaker = (boolean)luaL_checkinteger(L, 2);
itembreaker = luaL_checkboolean(L, 2);
else if (fastcmp(word,"battleprisons"))
itembreaker = (boolean)luaL_checkinteger(L, 2);
itembreaker = luaL_checkboolean(L, 2);
else if (fastcmp(word,"ringsactive"))
ringsactive = (boolean)luaL_checkinteger(L, 2);
ringsactive = luaL_checkboolean(L, 2);
else if (fastcmp(word,"stackingactive"))
stackingactive = (boolean)luaL_checkinteger(L, 2);
stackingactive = luaL_checkboolean(L, 2);
else if (fastcmp(word,"chainingactive"))
chainingactive = (boolean)luaL_checkinteger(L, 2);
chainingactive = luaL_checkboolean(L, 2);
else if (fastcmp(word,"slipdashactive"))
slipdashactive = (boolean)luaL_checkinteger(L, 2);
slipdashactive = luaL_checkboolean(L, 2);
else if (fastcmp(word,"slopeboostactive"))
slopeboostactive = (boolean)luaL_checkinteger(L, 2);
slopeboostactive = luaL_checkboolean(L, 2);
else if (fastcmp(word,"draftingactive"))
draftingactive = (boolean)luaL_checkinteger(L, 2);
draftingactive = luaL_checkboolean(L, 2);
else if (fastcmp(word,"airdropactive"))
airdropactive = luaL_checkinteger(L, 2);
else if (fastcmp(word,"bumpsparkactive"))
bumpsparkactive = luaL_checkinteger(L, 2);
else if (fastcmp(word,"purpledriftactive"))
purpledriftactive = (boolean)luaL_checkinteger(L, 2);
purpledriftactive = luaL_checkboolean(L, 2);
else if (fastcmp(word,"recoverydashactive"))
recoverydashactive = (boolean)luaL_checkinteger(L, 2);
recoverydashactive = luaL_checkboolean(L, 2);
else if (fastcmp(word,"itemlittering"))
itemlittering = (boolean)luaL_checkinteger(L, 2);
itemlittering = luaL_checkboolean(L, 2);
else if (fastcmp(word,"keepstuffactive"))
keepstuffactive = (boolean)luaL_checkinteger(L, 2);
keepstuffactive = luaL_checkboolean(L, 2);
else if (fastcmp(word,"itempushing"))
itempushing = (boolean)luaL_checkinteger(L, 2);
itempushing = luaL_checkboolean(L, 2);
else if (fastcmp(word,"trailslowactive"))
trailslow_active = (boolean)luaL_checkinteger(L, 2);
trailslow_active = luaL_checkboolean(L, 2);
else if (fastcmp(word,"trailslow_active"))
trailslow_active = (boolean)luaL_checkinteger(L, 2);
trailslow_active = luaL_checkboolean(L, 2);
else if (fastcmp(word,"playerbumpactive"))
playerbumpactive = (playerbumptype_t)luaL_checkinteger(L, 2);
else if (fastcmp(word,"gamespeed"))
gamespeed = (UINT8)luaL_checkinteger(L, 2);
else if (fastcmp(word,"nummapboxes"))

View file

@ -75,6 +75,7 @@
#include "k_color.h"
#include "k_grandprix.h"
#include "k_follower.h"
#include "r_voicepreference.hpp"
#include "r_fps.h"
#include "m_easing.h"
@ -811,6 +812,7 @@ void Moviemode_option_Onchange(void)
#define SKINGRIDWIDTH 9
#define SKINGRIDHEIGHT 6
static boolean gridcss_active;
static INT32 gridcss_skinydrag;
static INT32 gridcss_skinmemory;
static INT32 gridcss_row;
@ -1327,14 +1329,15 @@ static INT32 RemapGamepadButton(event_t *ev)
static INT32 joyremap[][2] = {
{ gc_accelerate, KEY_ENTER }, // these two first
{ gc_brake, KEY_ESCAPE }, // in case of conflicts
{ gc_fire, KEY_BACKSPACE },
{ gc_drift, KEY_SPACE },
{ gc_systemmenu, KEY_ESCAPE },
{ gc_aimforward, KEY_UPARROW },
{ gc_aimbackward, KEY_DOWNARROW },
{ gc_turnleft, KEY_LEFTARROW },
{ gc_turnright, KEY_RIGHTARROW },
{ gc_lookback, KEY_TAB },
{ gc_fire, KEY_BACKSPACE },
{ gc_horncode, KEY_BACKSPACE },
{ gc_drift, KEY_SPACE },
{ gc_lookback, KEY_TAB },
};
for (size_t r = 0; r < sizeof(joyremap)/sizeof(*joyremap); r++)
@ -4862,8 +4865,8 @@ static void DrawReplayHutReplayInfo(void)
V_DrawThinString(x, y+9, V_SNAPTOTOP|V_ALLOWLOWERCASE, va("(%d laps)", demolist[dir_on[menudepthleft]].numlaps));
const char *gametypename = gametypes[demolist[dir_on[menudepthleft]].gametype] ? gametypes[demolist[dir_on[menudepthleft]].gametype]->name : "Unknown";
const char *gamespeed = kartspeed_cons_t[(demolist[dir_on[menudepthleft]].kartspeed & ~DF_ENCORE) + 1].strvalue;
const char *gamemodestring = va("%s (%s speed)", gametypename, gamespeed);
const char *displaygamespeed = kartspeed_cons_t[(demolist[dir_on[menudepthleft]].kartspeed & ~DF_ENCORE) + 1].strvalue;
const char *gamemodestring = va("%s (%s speed)", gametypename, displaygamespeed);
V_DrawString(x, y+20, V_SNAPTOTOP|V_ALLOWLOWERCASE, gamemodestring);
@ -8009,7 +8012,7 @@ void MD_DrawGridCssSelector(void)
}
// draw wanted portrait and cursor
if (M_IsItemOn(MN_MP_PLAYERSETUP, "SKIN"))
if (M_IsItemOn(MN_MP_PLAYERSETUP, "SKIN") && gridcss_active)
{
face = faceprefix[skintodisplay][FACE_WANTED];
colmap = R_GetTranslationColormap(skintodisplay, cv_dummycolor.value, GTC_MENUCACHE);
@ -8046,10 +8049,39 @@ static inline INT32 MapGridSelectToSkin(INT32 row, INT32 column)
INT32 MR_HandleSetupMultiPlayerMenu(INT32 choice)
{
INT32 sortedIndex = 0;
// don't consume input if we're not interacting with the grid CSS
if (!(M_IsItemOn(MN_MP_PLAYERSETUP, "SKIN") && cv_skinselectstyle.value))
if (M_IsItemOn(MN_MP_PLAYERSETUP, "FOLLOWER") && (choice == KEY_BACKSPACE || choice == KEY_ENTER))
{
return false;
if (cv_dummyfollower.value > -1)
{
follower_t fl = followers[cv_dummyfollower.value];
S_StopSounds();
S_StartSound(NULL, fl.hornsound);
}
return true;
}
// don't consume input if we're not interacting with the grid CSS
if (cv_skinselectstyle.value != 1) return false;
if (!M_IsItemOn(MN_MP_PLAYERSETUP, "SKIN")) return false;
if (!gridcss_active)
{
if (choice == KEY_ENTER)
{
gridcss_active = true;
S_StartSound(NULL, sfx_menu1);
return true;
}
if (choice == KEY_LEFTARROW || choice == KEY_RIGHTARROW)
{
gridcss_active = true;
// fall-thru into navigation
}
else
{
return false;
}
}
switch (choice)
@ -8062,6 +8094,8 @@ INT32 MR_HandleSetupMultiPlayerMenu(INT32 choice)
sortedIndex = FindSortedSkinIndex(cv_chooseskin.value);
gridcss_row = sortedIndex % SKINGRIDWIDTH;
gridcss_column = sortedIndex / SKINGRIDWIDTH;
gridcss_active = false;
gridcss_skinydrag = CLAMP(gridcss_column - SKINGRIDHEIGHT + 1, 0, ((numskins - 1) / SKINGRIDWIDTH) - SKINGRIDHEIGHT + 1);
}
@ -8088,6 +8122,8 @@ INT32 MR_HandleSetupMultiPlayerMenu(INT32 choice)
sortedIndex = FindSortedSkinIndex(cv_chooseskin.value);
gridcss_row = sortedIndex % SKINGRIDWIDTH;
gridcss_column = sortedIndex / SKINGRIDWIDTH;
gridcss_active = false;
gridcss_skinydrag = CLAMP(gridcss_column - SKINGRIDHEIGHT + 1, 0, ((numskins - 1) / SKINGRIDWIDTH) - SKINGRIDHEIGHT + 1);
}
@ -8164,24 +8200,42 @@ INT32 MR_HandleSetupMultiPlayerMenu(INT32 choice)
S_StartSound(NULL, sfx_menu1);
}
break;
case KEY_TAB:
M_SetItemOn(MN_MP_PLAYERSETUP, "FOLLOWER");
case KEY_ENTER:
if (cv_chooseskin.value < numskins)
{
skin_t *skin = &skins[cv_chooseskin.value];
INT32 voxid = R_GetLocalPreferredVoiceForSkin(setupplayer, skin->name);
kartvoice_t *voice = NULL;
gridcss_skinmemory = cv_chooseskin.value;
gridcss_active = false;
if (voxid == MAXSKINVOICES)
{
// Couldn't find a voice.
// try current setting
voxid = R_FindIDForVoice(skin, localvoicedata[setupplayer].name);
// sorry nothing
if (voxid == MAXSKINVOICES) voxid = 0;
}
voice = &skin->voices[voxid];
S_StopSounds();
S_StartSound(NULL, sfx_s221);
if (voice != NULL)
S_StartSound(NULL, R_GetLegacySkinSound(voice, sfx_kslow));
}
break;
case KEY_ESCAPE:
CV_SetValue(&cv_chooseskin, gridcss_skinmemory);
sortedIndex = FindSortedSkinIndex(cv_chooseskin.value);
gridcss_row = sortedIndex % SKINGRIDWIDTH;
gridcss_column = sortedIndex / SKINGRIDWIDTH;
gridcss_skinydrag = CLAMP(gridcss_column - SKINGRIDHEIGHT + 1, 0, ((numskins - 1) / SKINGRIDWIDTH) - SKINGRIDHEIGHT - 1);
gridcss_skinydrag = CLAMP(gridcss_column - SKINGRIDHEIGHT + 1, 0, ((numskins - 1) / SKINGRIDWIDTH) - SKINGRIDHEIGHT + 1);
gridcss_active = false;
S_StartSound(NULL, sfx_menu1);
break;
case KEY_ENTER:
if (cv_chooseskin.value < numskins)
{
gridcss_skinmemory = cv_chooseskin.value;
S_StartSound(NULL, sfx_s221);
}
break;
default:
return false;
}
@ -8229,6 +8283,7 @@ INT32 MR_SetupMultiPlayer(INT32 arg)
CV_SetValue(&cv_dummycolor, cv_playercolor[arg].value);
G_SetPlayerControllerIndicatorColor(arg, cv_playercolor[arg].value);
dummycolorplayer = arg;
gridcss_active = false;
Skinsort_option_Onchange();

View file

@ -2557,6 +2557,20 @@ UINT32 FNV1a_QuickCaseHash(const char *message, size_t size)
return hash;
}
UINT32 FNV1a_HashLowercaseString(const char *message)
{
UINT32 hash = FNV1A_OFFSET_BASIS;
while (*message)
{
hash ^= tolower(*message);
hash *= FNV1A_PRIME;
message++;
}
return hash;
}
// Returns true if the string is empty.
boolean M_IsStringEmpty(const char *s)
{

View file

@ -126,6 +126,8 @@ FUNCMATH UINT8 M_CountBits(UINT32 num, UINT8 size);
// Hashes some message using FNV-1a
UINT32 FNV1a_QuickCaseHash(const char *message, size_t size);
UINT32 FNV1a_HashLowercaseString(const char *message);
boolean M_IsStringEmpty(const char *s);
int M_RoundUp(double number);

View file

@ -1948,7 +1948,8 @@ void EV_DoMoveFloorByHeight(mtag_t tag, fixed_t height, fixed_t speed, mtag_t ch
// chained linedef executing ability
// Only set it on one of the moving sectors (the smallest numbered)
if (chain)
boolean kartcheck = (mapnamespace == MNS_SRB2KART) ? firstone : true;
if (kartcheck && chain)
dofloor->tag = firstone ? (INT16)chain : -1;
// flat changing ability

View file

@ -1356,6 +1356,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
// Damage other players when possible.
// Handle before bumpcode to prevent lower weights from getting affected.
boolean pvp_hit = false;
if (g_tm.thing->player && thing->player
// Make sure they aren't able to damage you ANYWHERE along the Z axis, you have to be TOUCHING the person.
&& !(thing->z + thing->height < g_tm.thing->z || thing->z > g_tm.thing->z + g_tm.thing->height))
@ -1363,7 +1364,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
mobj_t *mo1 = g_tm.thing;
mobj_t *mo2 = thing;
K_PvPTouchDamage(mo1, mo2);
pvp_hit = K_PvPTouchDamage(mo1, mo2);
}
if (thing->player)
@ -1402,6 +1403,21 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
}
else if (thing->player) // bounce when players collide
{
boolean cannotbump = false;
switch(playerbumpactive)
{
case PBUMP_GNDONLY:
cannotbump = ((!P_IsObjectOnGround(thing) || !P_IsObjectOnGround(g_tm.thing)) && !pvp_hit);
break;
case PBUMP_NONE:
cannotbump = !pvp_hit;
break;
case PBUMP_STANDARD:
default:
break;
}
// see if it went over / under
if (g_tm.thing->z > thing->z + thing->height)
return BMIT_CONTINUE; // overhead
@ -1436,6 +1452,14 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
return BMIT_CONTINUE;
}
if (cannotbump) // Unable to actually bump; don't process things further
{
// Just in case...
g_tm.thing->player->justbumped = min(24, g_tm.thing->player->justbumped + 6);
thing->player->justbumped = min(24, thing->player->justbumped + 6);
return BMIT_CONTINUE;
}
if (thing->player->squishedtimer || thing->player->hyudorotimer
|| thing->player->justbumped || thing->scale > g_tm.thing->scale + (mapobjectscale/8)
|| g_tm.thing->player->squishedtimer || g_tm.thing->player->hyudorotimer

View file

@ -4352,6 +4352,7 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending)
SYNC(airdropactive);
SYNC(bumpsparkactive);
SYNC(antibumptime);
SYNC(playerbumpactive);
for (i = 0; i < sizeof(g_voteLevels)/sizeof(*g_voteLevels); i++)
{

View file

@ -175,6 +175,7 @@ boolean itempushing;
UINT8 bumpsparkactive;
boolean itemlistactive;
boolean trailslow_active = false;
playerbumptype_t playerbumpactive = PBUMP_STANDARD;
UINT16 bossdisabled;
boolean stoppedclock;
boolean levelloading;
@ -8255,6 +8256,7 @@ static void P_InitLevelSettings(boolean reloadinggamestate)
trailslow_active = false;
bumpsparkactive = 0;
antibumptime = 0;
playerbumpactive = PBUMP_STANDARD;
if (cv_kartrings.value)
ringsactive = true;
@ -8304,6 +8306,7 @@ static void P_InitLevelSettings(boolean reloadinggamestate)
trailslow_active = true;
bumpsparkactive = (UINT8)cv_kartbumpspark.value;
playerbumpactive = (playerbumptype_t)cv_playerbump.value;
antibumptime = (tic_t)cv_kartantibump.value * TICRATE;

View file

@ -2637,15 +2637,6 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha
return false;
}
if (mapnamespace == MNS_SRB2KART)
{
if (line->args[2] != TMP_CEILING)
EV_DoFloor(line->args[1], line, moveFloorByFrontSector);
if (line->args[2] != TMP_FLOOR)
EV_DoCeiling(line->args[1], line, moveCeilingByFrontSector);
break;
}
copySector = line->frontsector;
}
else

View file

@ -50,6 +50,11 @@
// Thok camera snap (ctrl-f "chalupa")
#include "g_input.h"
#include "m_menu.h" // menustack
#include "screen.h"
#ifdef HAVE_VR
#include "vr/vr_main.h"
#endif
// SRB2kart
#include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems
@ -192,11 +197,20 @@ fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move)
//
boolean P_AutoPause(void)
{
boolean focuspause;
// Don't pause even on menu-up or focus-lost in netgames or record attack
if (netgame || modeattacking || gamestate == GS_TITLESCREEN)
return false;
return ((menustack[0] && !demo.playback) || ( window_notinfocus && cv_pauseifunfocused.value ));
focuspause = (window_notinfocus && cv_pauseifunfocused.value);
#ifdef HAVE_VR
if (vr_started)
focuspause = false;
#endif
return ((menustack[0] && !demo.playback) || focuspause);
}
//
@ -3187,10 +3201,17 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
const INT32 timeovercam = max(0, min(180, (player->karthud[khud_timeovercam] - 2*TICRATE)*15));
camrotate += timeovercam;
}
else if (leveltime < introtime && !(modeattacking && !demo.playback)) // Whoooshy camera! (don't do this in RA when we PLAY, still do it in replays however~)
else if (leveltime < introtime && !(modeattacking && !demo.playback)
#ifdef HAVE_VR
&& (!vr_started || cv_vrtrackintro.value)
#endif
) // Whoooshy camera! (don't do this in RA when we PLAY, still do it in replays however~)
{
const INT32 introcam = (introtime - leveltime);
camrotate += introcam*5;
#ifdef HAVE_VR
if (!vr_started || cv_vrtrackintro.value > 1)
#endif
camrotate += introcam*5;
camdist += (introcam * mapobjectscale)*3;
camheight += (introcam * mapobjectscale)*2;
}

View file

@ -28,6 +28,9 @@
#ifdef HWRENDER
#include "hardware/hw_main.h"
#endif
#ifdef HAVE_VR
#include "vr/vr_main.h"
#endif
// The fraction of a tic being drawn (for interpolation between two tics)
static fixed_t rendertimefrac;
@ -55,6 +58,15 @@ UINT32 R_GetFramerateCap(void)
return TICRATE;
}
#ifdef HAVE_VR
if (vr_started)
{
// In VR, OpenXR xrWaitFrame is the display clock. Returning zero keeps
// the legacy desktop cap from reducing rendering to TICRATE or half-rate.
return 0;
}
#endif
if (cv_fpscap.value == 0)
{
// 0: Match refresh rate

View file

@ -353,7 +353,7 @@ extern "C"
return false;
}
fprintf(f, va("// BlanKart preferences file for local player %d.\n", snum + 1));
fprintf(f, "%s", va("// BlanKart preferences file for local player %d.\n", snum + 1));
fprintf(
f,
"// Due to the nature of the unordered map system, data here may be shuffled!\n");
@ -398,7 +398,7 @@ extern "C"
file_output.copy(voicepref_buffer, VOICEPREFBUFSIZE);
voicepref_buffer[VOICEPREFBUFSIZE - 1] = 0;
fprintf(f, voicepref_buffer);
fprintf(f, "%s",voicepref_buffer);
}
fclose(f);
@ -432,7 +432,7 @@ extern "C"
INT32 strikes = 0;
std::vector<std::string> data_to_load[2];
char name_buffer[2][VOICENAMESIZE] = {0};
char name_buffer[2][VOICENAMESIZE] = {{0}};
for (snum = 0; snum < MAXSPLITSCREENPLAYERS; snum++)
{

View file

@ -36,6 +36,10 @@
#include "p_local.h" // P_AutoPause()
#include "m_menu.h"
#ifdef HAVE_VR
#include "vr/vr_main.h"
#endif
#ifdef HWRENDER
#include "hardware/hw_main.h"
#include "hardware/hw_glob.h"
@ -72,6 +76,46 @@ static void Highreshudscale_OnChange(void);
static CV_PossibleValue_t highreshudscale_cons_t[] = {{4*FRACUNIT/5, "MIN"}, {6*FRACUNIT/5, "MAX"}, {0, NULL}};
consvar_t cv_highreshudscale = CVAR_INIT ("highreshudscale", "1", CV_SAVE|CV_FLOAT|CV_CALL|CV_NOINIT|CV_NOSHOWHELP, highreshudscale_cons_t, Highreshudscale_OnChange);
static void SCR_ChangeVR(void);
static void SCR_ChangeScaleVR(void);
static void SCR_ChangeResolutionVR(void);
static CV_PossibleValue_t vrviewmode_cons_t[] = {{-1, "Hide"}, {0, "Left Eye"}, {1, "Right Eye"}, {0, NULL}};
consvar_t cv_vrviewmode = CVAR_INIT ("vrviewmode", "0", CV_SAVE, vrviewmode_cons_t, NULL);
static CV_PossibleValue_t vrtrackintro_cons_t[] = {{0, "Off"}, {1, "Gentle"}, {2, "Standard"}, {0, NULL}};
consvar_t cv_vrtrackintro = CVAR_INIT ("vrtrackintro", "1", CV_SAVE, vrtrackintro_cons_t, NULL);
consvar_t cv_vrcomfortmode = CVAR_INIT ("vrcomfortmode", "No", CV_SAVE, CV_YesNo, NULL);
consvar_t cv_vrdisableskystereo = CVAR_INIT ("vrdisableskystereo", "No", CV_SAVE, CV_YesNo, NULL);
consvar_t cv_vrplayerscale = CVAR_INIT ("vrplayerscale", "Yes", CV_SAVE, CV_YesNo, NULL);
consvar_t cv_vrspriterotate = CVAR_INIT ("vrspriterotate", "Yes", CV_SAVE, CV_YesNo, NULL);
consvar_t cv_vruidistance = CVAR_INIT ("vruidistance", "105", CV_SAVE, CV_Unsigned, NULL);
consvar_t cv_vruiscale = CVAR_INIT ("vruiscale", "50", CV_SAVE, CV_Unsigned, NULL);
static CV_PossibleValue_t vruimode_cons_t[] = {{0, "InEye"}, {1, "Quad"}, {0, NULL}};
consvar_t cv_vruimode = CVAR_INIT ("vruimode", "Quad", CV_SAVE, vruimode_cons_t, NULL);
static CV_PossibleValue_t vrposemode_cons_t[] = {
{0, "OpenXRView"},
{1, "OpenVRSplit"},
{2, "OpenXRDebugPose"},
{3, "OpenVRDebugPose"},
{0, NULL}
};
consvar_t cv_vrposemode = CVAR_INIT ("vrposemode", "OpenXRView", CV_SAVE, vrposemode_cons_t, NULL);
static CV_PossibleValue_t vrresolution_cons_t[] = {
{0, "50%"}, {1, "75%"}, {2, "100%"}, {3, "125%"},
{4, "150%"}, {5, "175%"}, {6, "200%"}, {0, NULL}
};
consvar_t cv_vrresolution = CVAR_INIT ("vrresolution", "2", CV_SAVE|CV_NOINIT|CV_CALL, vrresolution_cons_t, SCR_ChangeResolutionVR);
static CV_PossibleValue_t vrscale_cons_t[] = {{0, "Small"}, {1, "Standard"}, {2, "Large"}, {0, NULL}};
consvar_t cv_vrscale = CVAR_INIT ("vrscale", "1", CV_SAVE|CV_NOINIT|CV_CALL, vrscale_cons_t, SCR_ChangeScaleVR);
consvar_t cv_vrenabled = CVAR_INIT ("vrenabled", "Off", CV_SAVE|CV_CALL, CV_OnOff, SCR_ChangeVR);
static void Highreshudscale_OnChange(void)
{
SCR_Recalc();
@ -514,6 +558,40 @@ void SCR_ChangeFullscreen(void)
return;
}
static void SCR_ChangeVR(void)
{
#ifdef HAVE_VR
vr_enabled = (cv_vrenabled.value != 0);
if (!graphics_started)
return;
if (cv_vrenabled.value)
VR_Init();
else
VR_Shutdown();
#else
if (cv_vrenabled.value)
CONS_Alert(CONS_ERROR, "This build does not include OpenXR support.\n");
#endif
}
static void SCR_ChangeScaleVR(void)
{
#ifdef HAVE_VR
if (vr_started)
CONS_Printf("VR: world scale changed; new tracking scale applies next frame.\n");
#endif
}
static void SCR_ChangeResolutionVR(void)
{
#ifdef HAVE_VR
if (vr_started)
CONS_Printf("VR: vrresolution changes require restarting VR so OpenXR swapchains can be recreated.\n");
#endif
}
void SCR_ChangeRenderer(void)
{
if (chosenrendermode != render_none

View file

@ -101,6 +101,9 @@ extern CV_PossibleValue_t cv_renderer_t[];
extern consvar_t cv_scr_width, cv_scr_height, cv_scr_depth, cv_renderview, cv_renderer, cv_renderhitbox, cv_fullscreen;
extern consvar_t cv_highreshudscale;
extern consvar_t cv_vhseffect, cv_shittyscreen, cv_votebgscaling;
extern consvar_t cv_vrviewmode, cv_vrcomfortmode, cv_vrenabled, cv_vrresolution, cv_vrscale;
extern consvar_t cv_vruidistance, cv_vruiscale, cv_vruimode, cv_vrposemode, cv_vrplayerscale, cv_vrspriterotate;
extern consvar_t cv_vrdisableskystereo, cv_vrtrackintro;
extern consvar_t cv_parallelsoftware;
extern consvar_t cv_accuratefps;

View file

@ -1281,6 +1281,69 @@ void I_Sleep(UINT32 ms)
SDL_Delay(ms);
}
void I_SleepDuration(precise_t duration)
{
#if defined(__linux__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__OpenBSD__)
UINT64 precision = I_GetPrecisePrecision();
precise_t dest = I_GetPreciseTime() + duration;
#ifdef __OpenBSD__
precise_t slack = (precision / 50); // 20 ms slack
#else
precise_t slack = (precision / 5000); // 0.2 ms slack
#endif
if (duration > slack)
{
duration -= slack;
struct timespec ts = {
.tv_sec = static_cast<__time_t>(duration / precision),
.tv_nsec = static_cast<__syscall_slong_t>(duration * 1000000000 / precision % 1000000000),
};
int status;
#ifdef __OpenBSD__
do status = nanosleep(&ts, &ts);
#else
do status = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, &ts);
#endif
while (status == EINTR);
}
// busy-wait the rest
while (((INT64)dest - (INT64)I_GetPreciseTime()) > 0);
#elif defined (MIN_SLEEP_DURATION_MS)
UINT64 precision = I_GetPrecisePrecision();
INT32 sleepvalue = cv_sleep.value;
UINT64 delaygranularity;
precise_t cur;
precise_t dest;
{
double gran = round(((double)(precision / 1000) * sleepvalue * MIN_SLEEP_DURATION_MS));
delaygranularity = (UINT64)gran;
}
cur = I_GetPreciseTime();
dest = cur + duration;
// the reason this is not dest > cur is because the precise counter may wrap
// two's complement arithmetic is our friend here, though!
// e.g. cur 0xFFFFFFFFFFFFFFFE = -2, dest 0x0000000000000001 = 1
// 0x0000000000000001 - 0xFFFFFFFFFFFFFFFE = 3
while ((INT64)(dest - cur) > 0)
{
// If our cv_sleep value exceeds the remaining sleep duration, use the
// hard sleep function.
if (sleepvalue > 0 && (dest - cur) > delaygranularity)
{
I_Sleep(sleepvalue);
}
// Otherwise, this is a spinloop.
cur = I_GetPreciseTime();
}
#endif
}
#ifdef NEWSIGNALHANDLER
static void newsignalhandler_Warn(const char *pr)
{
@ -1321,6 +1384,18 @@ static void I_Fork(void)
I_RegisterChildSignals();
break;
default:
// ignore those, those are handled by child process
// otherwise parent might exit before it
// and the below stuff wont run and your terminal will be left in an awkward state
#ifdef SIGINT
signal(SIGINT, SIG_IGN);
#endif
#ifdef SIGBREAK
signal(SIGBREAK, SIG_IGN);
#endif
#ifdef SIGTERM
signal(SIGTERM, SIG_IGN);
#endif
if (logstream)
fclose(logstream);/* the child has this */
@ -1386,6 +1461,7 @@ INT32 I_StartupSystem(void)
#if SDL_VERSION_ATLEAST(2,0,22)
SDL_SetHint(SDL_HINT_APP_NAME, "BlanKart");
#endif
SDL_SetHint("SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS", "1");
if (!SDL_Init(0))
I_Error("SRB2Kart: SDL System Error: %s", SDL_GetError()); //Alam: Oh no....
#ifndef NOMUMBLE
@ -1444,7 +1520,6 @@ void I_Quit(void)
if (myargmalloc)
free(myargv); // Deallocate allocated memory
W_Shutdown();
exit(0);
}
@ -1522,7 +1597,6 @@ FUNCIERROR void ATTRNORETURN I_Error(const char *error, ...)
"SRB2Kart V2 " VERSIONSTRING " Recursive Error",
buffer, NULL);
W_Shutdown();
exit(-1); // recursive errors detected
}
}
@ -1570,8 +1644,6 @@ FUNCIERROR void ATTRNORETURN I_Error(const char *error, ...)
I_ShutdownSystem();
SDL_Quit();
W_Shutdown();
#if defined (PARANOIA) || defined (DEVELOP)
*(volatile INT32 *)0 = 4; //Alam: Debug!
#endif

View file

@ -54,6 +54,11 @@
#include "../d_main.h"
#include "../s_sound.h"
#include "../i_sound.h" // midi pause/unpause
#ifdef HAVE_VR
#include "../vr/vr_main.h"
#endif
#include "../i_gamepad.h"
#include "../st_stuff.h"
#include "../hu_stuff.h"
@ -545,6 +550,11 @@ static void VID_Command_Mode_f (void)
static void Impl_SetFocused(boolean focused)
{
#ifdef HAVE_VR
if (vr_started)
focused = true;
#endif
window_notinfocus = !focused;
if (window_notinfocus)
@ -1316,51 +1326,60 @@ void I_FinishUpdate(void)
if (rendermode == render_none)
return; //Alam: No software or OpenGl surface
SCR_CalculateFPS();
#ifdef HAVE_VR
const boolean vr_draw_ui = (!vr_started || vr_render_pass == VR_PASS_NONE || vr_render_pass == VR_PASS_UI);
#else
const boolean vr_draw_ui = true;
#endif
if (st_overlay)
if (vr_draw_ui)
{
if (cv_ticrate.value)
SCR_DisplayTicRate();
SCR_CalculateFPS();
if (cv_showping.value && netgame &&
( consoleplayer != serverplayer || (server_lagless == 0 || server_lagless == -1) ))
if (st_overlay)
{
if (server_lagless == 1)
if (cv_ticrate.value)
SCR_DisplayTicRate();
if (cv_showping.value && netgame &&
( consoleplayer != serverplayer || (server_lagless == 0 || server_lagless == -1) ))
{
if (consoleplayer != serverplayer)
SCR_DisplayLocalPing();
}
else
{
for (
player = 1;
player < MAXPLAYERS;
player++
){
if (D_IsPlayerHumanAndGaming(player))
{
if (server_lagless == 1)
{
if (consoleplayer != serverplayer)
SCR_DisplayLocalPing();
break;
}
else
{
for (
player = 1;
player < MAXPLAYERS;
player++
){
if (D_IsPlayerHumanAndGaming(player))
{
SCR_DisplayLocalPing();
break;
}
}
}
}
if (cv_mindelay.value && consoleplayer == serverplayer && Playing())
SCR_DisplayLocalPing();
}
if (cv_mindelay.value && consoleplayer == serverplayer && Playing())
SCR_DisplayLocalPing();
}
if (marathonmode)
SCR_DisplayMarathonInfo();
if (marathonmode)
SCR_DisplayMarathonInfo();
// draw captions if enabled
if (cv_closedcaptioning.value)
SCR_ClosedCaptions();
// draw captions if enabled
if (cv_closedcaptioning.value)
SCR_ClosedCaptions();
#ifdef HAVE_DISCORDRPC
if (discordRequestList != NULL)
ST_AskToJoinEnvelope();
if (discordRequestList != NULL)
ST_AskToJoinEnvelope();
#endif
}
if (rendermode == render_soft && vid.screens[0])
{
@ -1389,7 +1408,12 @@ void I_FinishUpdate(void)
else if (rendermode == render_opengl)
{
// Final postprocess step of palette rendering, after everything else has been drawn.
if (HWR_ShouldUsePaletteRendering())
#ifdef HAVE_VR
const boolean vr_ui_pass = (vr_started && vr_render_pass == VR_PASS_UI);
#else
const boolean vr_ui_pass = false;
#endif
if (!vr_ui_pass && HWR_ShouldUsePaletteRendering())
{
GL_MakeScreenTexture(HWD_SCREENTEXTURE_GENERIC2);
GL_SetShader(HWR_GetShaderFromTarget(SHADER_PALETTE_POSTPROCESS));
@ -1736,7 +1760,12 @@ void I_StartupGraphics(void)
return;
disable_mouse = static_cast<bool>(M_CheckParm("-nomouse"));
disable_fullscreen = M_CheckParm("-win") ? true : false;
#ifdef HAVE_VR
const bool wantVR = (M_CheckParm("-openvr") || M_CheckParm("-vr") || cv_vrenabled.value);
#else
const bool wantVR = false;
#endif
disable_fullscreen = (M_CheckParm("-win") || wantVR) ? true : false;
keyboard_started = true;
@ -1786,6 +1815,8 @@ void I_StartupGraphics(void)
// Choose OpenGL renderer
else if (M_CheckParm("-opengl"))
chosenrendermode = render_opengl;
else if (wantVR)
chosenrendermode = render_opengl;
// Don't startup OpenGL
if (M_CheckParm("-nogl"))
@ -1823,6 +1854,10 @@ void I_StartupGraphics(void)
VID_SetMode(VID_GetModeForSize(vid.width, vid.height));
#ifdef HAVE_VR
VR_Init();
#endif
SDLdoUngrabMouse();
SDL_RaiseWindow(window);
@ -1902,5 +1937,3 @@ void I_SetBorderlessWindow(void)
SDL_SetWindowBordered(window, bordered);
}
#endif

View file

@ -27,6 +27,7 @@
#include "../doomdef.h"
#include "../d_main.h"
#include "../screen.h"
#ifdef HWRENDER
#include "../hardware/r_opengl/r_opengl.h"
@ -171,34 +172,80 @@ boolean OglSdlSurface(INT32 w, INT32 h)
\return void
*/
#ifdef HAVE_VR
#include "../vr/vr_main.h"
#include "../vr/vr_render.h"
#endif
void OglSdlFinishUpdate(boolean waitvbl)
{
static boolean oldwaitvbl = false;
static int oldwaitvbl = -1;
int sdlw, sdlh;
if (oldwaitvbl != waitvbl)
#ifdef HAVE_VR
const boolean effective_waitvbl = (vr_started ? false : waitvbl);
#else
const boolean effective_waitvbl = waitvbl;
#endif
if (oldwaitvbl != (int)effective_waitvbl)
{
SDL_GL_SetSwapInterval(waitvbl);
SDL_GL_SetSwapInterval(effective_waitvbl);
}
oldwaitvbl = waitvbl;
oldwaitvbl = (int)effective_waitvbl;
SDL_GetWindowSizeInPixels(window, &sdlw, &sdlh);
HWR_MakeScreenFinalTexture();
if (gl_shadersavailable)
GL_SetShader(HWR_GetShaderFromTarget(SHADER_FINAL_POST_PROCESS));
HWR_DrawScreenFinalTexture(sdlw, sdlh);
if (gl_shadersavailable)
GL_UnSetShader();
SDL_GL_SwapWindow(window);
#ifdef HAVE_VR
if (vr_started && vr_render_pass != VR_PASS_NONE)
{
if (vr_render_pass == VR_PASS_UI)
return;
GL_GClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
if (cv_vruimode.value == 0)
{
if (vr_current_eye == cv_vrviewmode.value)
{
if (VR_MirrorEyeToDefaultFramebuffer(sdlw, sdlh))
SDL_GL_SwapWindow(window);
}
// Sryder: We need to draw the final screen texture again into the other buffer in the original position so that
// effects that want to take the old screen can do so after this
// Generic2 has the screen image without palette rendering brightness adjustments.
// Using that here will prevent brightness adjustments being applied twice.
GL_DrawScreenTexture(HWD_SCREENTEXTURE_GENERIC2, NULL, 0);
return;
}
// Optional desktop mirror of one eye. Use an FBO blit so the mirror
// does not copy a 4096x4096 screen texture or write back into the
// headset swapchain.
if (vr_current_eye == cv_vrviewmode.value)
{
if (VR_MirrorEyeToDefaultFramebuffer(sdlw, sdlh))
SDL_GL_SwapWindow(window);
}
return;
}
else
#endif
{
HWR_MakeScreenFinalTexture();
if (gl_shadersavailable)
GL_SetShader(HWR_GetShaderFromTarget(SHADER_FINAL_POST_PROCESS));
HWR_DrawScreenFinalTexture(sdlw, sdlh);
if (gl_shadersavailable)
GL_UnSetShader();
SDL_GL_SwapWindow(window);
GL_GClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
// Sryder: We need to draw the final screen texture again into the other buffer in the original position so that
// effects that want to take the old screen can do so after this
// Generic2 has the screen image without palette rendering brightness adjustments.
// Using that here will prevent brightness adjustments being applied twice.
if (gl_shadersavailable)
GL_SetShader(HWR_GetShaderFromTarget(SHADER_FINAL_POST_PROCESS));
HWR_DrawScreenFinalTexture(sdlw, sdlh);
if (gl_shadersavailable)
GL_UnSetShader();
}
}
#endif //HWRENDER

View file

@ -190,7 +190,7 @@ void ST_LoadGraphics(void)
static boolean ST_IconCanSpinout(UINT8 icon)
{
return ((icon == FACE_MINIMAP)||(icon == FACE_RANK));
return ((icon == FACE_MINIMAP)||(icon == FACE_RANK)||(icon == FACE_WANTED));
}
// made separate so that skins code can reload custom face graphics
@ -215,7 +215,7 @@ void ST_LoadFaceGraphics(INT32 skinnum)
if ((ST_IconCanSpinout(i)) && (!alreadycentered))
{
// Auto-center the pivots of all minimap and rank patches with
// Auto-center the pivots of all minimap ,rank and wanted patches with
// the ability to spinout.
// This is a shitty, hacky solution... but it's less work for spinout
// rotations!
@ -982,6 +982,8 @@ void ST_AskToJoinEnvelope(void)
static fixed_t ST_CalculateFadeIn(player_t *player)
{
(void)player;
const tic_t length = TICRATE/4;
tic_t timer = lt_exitticker;

View file

@ -920,11 +920,41 @@ affine_bounding_t* V_GetAffineBounds(const affine_t* transform,
return out;
}
static UINT32 V_GetAlphaLevel(INT32 scrn)
{
switch (scrn & V_ALPHAMASK)
{
case V_HUDTRANSHALF:
return hudminusalpha[V_GetHUDTranslucency(scrn)];
case V_HUDTRANS:
return 10 - V_GetHUDTranslucency(scrn);
case V_HUDTRANSDOUBLE:
return hudplusalpha[V_GetHUDTranslucency(scrn)];
default:
return (scrn & V_ALPHAMASK) >> V_ALPHASHIFT;
}
}
void V_DrawAffinePatch(fixed_t x, fixed_t y, const affine_t *transform, INT32 scrn, patch_t *patch, const UINT8 *colormap)
{
UINT32 alphalevel = 0, blendmode = 0;
UINT8 patchdrawtype = STANDARDDRAW;
if (rendermode == render_none)
return;
if ((blendmode = ((scrn & V_BLENDMASK) >> V_BLENDSHIFT)))
blendmode++; // realign to constants
if ((alphalevel = V_GetAlphaLevel(scrn)) >= 10)
return;
if ((v_translevel = R_GetBlendTable(blendmode, alphalevel)))
patchdrawtype = TRANSLUCENTDRAW;
INT32 dup;
switch (scrn & V_SCALEPATCHMASK)
{
@ -1031,6 +1061,7 @@ void V_DrawAffinePatch(fixed_t x, fixed_t y, const affine_t *transform, INT32 sc
UINT8 * const destbase = (UINT8 * const)(dest_y + xx);
INT32 dx = 0, dy = 0;
UINT8 mypixel = TRANSPARENTPIXEL;
for (dy = 0; dy < ymax; dy++)
{
// yoinked from NovaSquirrel's mode 7 preview
@ -1056,9 +1087,20 @@ void V_DrawAffinePatch(fixed_t x, fixed_t y, const affine_t *transform, INT32 sc
continue;
if (colormap != NULL)
*dest = colormap[pixel & 0xff];
mypixel = colormap[pixel & 0xff];
else
*dest = (UINT8)(pixel & 0xff);
mypixel = (UINT8)(pixel & 0xff);
if (patchdrawtype == TRANSLUCENTDRAW)
{
// Translucent
*dest = *(v_translevel + (((mypixel)<<8)&0xff00) + (*dest&0xff));
}
else
{
// Opaque
*dest = mypixel;
}
}
}
@ -1118,24 +1160,6 @@ UINT32 V_GetHUDTranslucency(INT32 scrn)
return st_translucency;
}
static UINT32 V_GetAlphaLevel(INT32 scrn)
{
switch (scrn & V_ALPHAMASK)
{
case V_HUDTRANSHALF:
return hudminusalpha[V_GetHUDTranslucency(scrn)];
case V_HUDTRANS:
return 10 - V_GetHUDTranslucency(scrn);
case V_HUDTRANSDOUBLE:
return hudplusalpha[V_GetHUDTranslucency(scrn)];
default:
return (scrn & V_ALPHAMASK) >> V_ALPHASHIFT;
}
}
// Draws a patch scaled to arbitrary size.
void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap)
{

601
src/vr/vr_main.c Normal file
View file

@ -0,0 +1,601 @@
#include "vr_main.h"
#ifdef HAVE_VR
#include "vr_render.h"
#include "vr_math.h"
#include "../m_argv.h"
#include "../console.h"
#include "../screen.h"
#include "../i_video.h"
#include "../r_fps.h"
#include "../v_video.h"
#include <stdbool.h>
#include <SDL3/SDL.h>
#include "../sdl/sdlmain.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef __linux__
#include <GL/glx.h>
#endif
int vr_current_eye = 0;
boolean vr_started = false;
boolean vr_enabled = false;
boolean vr_drawing_ui = false;
vr_render_pass_t vr_render_pass = VR_PASS_NONE;
XrInstance vr_instance = XR_NULL_HANDLE;
XrSession vr_session = XR_NULL_HANDLE;
XrSystemId vr_system_id = XR_NULL_SYSTEM_ID;
XrSpace vr_local_space = XR_NULL_HANDLE;
XrSpace vr_view_space = XR_NULL_HANDLE;
float vrHMDPoseMatrix[16];
float vrHMDScaledPoseMatrix[16];
float vrHMDPoseSkyboxMatrix[16];
float vrEyeViewMatrix[2][16];
float vrScaledEyeViewMatrix[2][16];
float vrEyeSkyboxViewMatrix[2][16];
float vrEyeProjMatrix[2][16];
int vrWorldScale[3] = {400, 250, 100};
float vrPlayerScale = 1.0f;
uint32_t vr_render_width = 0;
uint32_t vr_render_height = 0;
float* vrVisibleAreaVertices[2] = {NULL, NULL};
float* vrVisibleAreaUVs[2] = {NULL, NULL};
uint32_t vrVisibleAreaVertexCount[2] = {0, 0};
static PFN_xrGetOpenGLGraphicsRequirementsKHR pfnGetOpenGLGraphicsRequirementsKHR = NULL;
static PFN_xrGetVisibilityMaskKHR pfnGetVisibilityMaskKHR = NULL;
static PFN_xrEnumerateDisplayRefreshRatesFB pfnEnumerateDisplayRefreshRatesFB = NULL;
static PFN_xrGetDisplayRefreshRateFB pfnGetDisplayRefreshRateFB = NULL;
static PFN_xrRequestDisplayRefreshRateFB pfnRequestDisplayRefreshRateFB = NULL;
static boolean displayRefreshRateExtensionEnabled = false;
static float VR_RenderScaleForMode(int mode)
{
static const float multipliers[] = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
if (mode < 0)
mode = 0;
else if (mode > 6)
mode = 6;
return multipliers[mode];
}
static int VR_StartupResolutionMode(void)
{
int mode = cv_vrresolution.string ? cv_vrresolution.value : 2;
if (M_CheckParm("+vrresolution") && M_IsNextParm()) {
const char* value = M_GetNextParm();
if (!strcasecmp(value, "50%"))
mode = 0;
else if (!strcasecmp(value, "75%"))
mode = 1;
else if (!strcasecmp(value, "100%"))
mode = 2;
else if (!strcasecmp(value, "125%"))
mode = 3;
else if (!strcasecmp(value, "150%"))
mode = 4;
else if (!strcasecmp(value, "175%"))
mode = 5;
else if (!strcasecmp(value, "200%"))
mode = 6;
else
mode = atoi(value);
}
if (mode < 0)
mode = 0;
else if (mode > 6)
mode = 6;
return mode;
}
static boolean VR_RuntimeExtensionSupported(const char* extensionName)
{
uint32_t extensionCount = 0;
XrResult res = xrEnumerateInstanceExtensionProperties(NULL, 0, &extensionCount, NULL);
if (XR_FAILED(res) || extensionCount == 0)
return false;
XrExtensionProperties* extensions =
(XrExtensionProperties*)malloc(extensionCount * sizeof(XrExtensionProperties));
if (!extensions)
return false;
for (uint32_t i = 0; i < extensionCount; i++) {
extensions[i].type = XR_TYPE_EXTENSION_PROPERTIES;
extensions[i].next = NULL;
}
res = xrEnumerateInstanceExtensionProperties(NULL, extensionCount, &extensionCount, extensions);
if (XR_FAILED(res)) {
free(extensions);
return false;
}
for (uint32_t i = 0; i < extensionCount; i++) {
if (strcmp(extensions[i].extensionName, extensionName) == 0) {
free(extensions);
return true;
}
}
free(extensions);
return false;
}
static void VR_ConfigureDisplayRefreshRate(void)
{
if (!displayRefreshRateExtensionEnabled || !pfnGetDisplayRefreshRateFB)
return;
float currentRefreshRate = 0.0f;
if (XR_SUCCEEDED(pfnGetDisplayRefreshRateFB(vr_session, &currentRefreshRate)) &&
currentRefreshRate > 0.0f) {
CONS_Printf("VR: Runtime display refresh rate is %.2f Hz.\n", currentRefreshRate);
if (pfnRequestDisplayRefreshRateFB) {
XrResult res = pfnRequestDisplayRefreshRateFB(vr_session, currentRefreshRate);
if (XR_SUCCEEDED(res))
CONS_Printf("VR: Requested runtime refresh pacing at %.2f Hz.\n", currentRefreshRate);
}
}
if (!pfnEnumerateDisplayRefreshRatesFB)
return;
uint32_t refreshRateCount = 0;
if (XR_FAILED(pfnEnumerateDisplayRefreshRatesFB(vr_session, 0, &refreshRateCount, NULL)) ||
refreshRateCount == 0)
return;
float* refreshRates = (float*)malloc(refreshRateCount * sizeof(float));
if (!refreshRates)
return;
if (XR_SUCCEEDED(pfnEnumerateDisplayRefreshRatesFB(vr_session, refreshRateCount, &refreshRateCount, refreshRates))) {
char buffer[256];
size_t used = 0;
buffer[0] = '\0';
for (uint32_t i = 0; i < refreshRateCount && used < sizeof(buffer); i++) {
int written = snprintf(buffer + used, sizeof(buffer) - used,
"%s%.2f", i ? ", " : "", refreshRates[i]);
if (written < 0)
break;
used += (size_t)written;
}
CONS_Printf("VR: Runtime supported refresh rates: %s Hz.\n", buffer);
}
free(refreshRates);
}
static void openxr_process_visibility_mesh(int eye, uint32_t width, uint32_t height)
{
if (!pfnGetVisibilityMaskKHR) return;
XrVisibilityMaskKHR mask = {XR_TYPE_VISIBILITY_MASK_KHR};
XrResult res = pfnGetVisibilityMaskKHR(vr_session, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, eye, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &mask);
if (XR_SUCCEEDED(res) && mask.vertexCapacityInput == 0) {
mask.vertexCapacityInput = mask.vertexCountOutput;
mask.indexCapacityInput = mask.indexCountOutput;
mask.vertices = (XrVector2f*)malloc(mask.vertexCapacityInput * sizeof(XrVector2f));
mask.indices = (uint32_t*)malloc(mask.indexCapacityInput * sizeof(uint32_t));
res = pfnGetVisibilityMaskKHR(vr_session, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, eye, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &mask);
if (XR_SUCCEEDED(res) && mask.indexCountOutput > 0) {
vrVisibleAreaVertexCount[eye] = mask.indexCountOutput;
float* vertex = vrVisibleAreaVertices[eye] = (float*)malloc(mask.indexCountOutput * 3 * sizeof(float));
float* uv = vrVisibleAreaUVs[eye] = (float*)malloc(mask.indexCountOutput * 2 * sizeof(float));
uint32_t texsize = 512;
while (texsize < width || texsize < height) texsize <<= 1;
float xfix = 1.0f / ((float)texsize / (float)width);
float yfix = 1.0f / ((float)texsize / (float)height);
for (uint32_t i = 0; i < mask.indexCountOutput; i++) {
uint32_t idx = mask.indices[i];
vertex[i*3] = (mask.vertices[idx].x - 0.5f) * 2.0f;
vertex[i*3+1] = (mask.vertices[idx].y - 0.5f) * 2.0f;
vertex[i*3+2] = 1.0f;
uv[i*2] = mask.vertices[idx].x * xfix;
uv[i*2+1] = mask.vertices[idx].y * yfix;
}
}
free(mask.vertices);
free(mask.indices);
}
}
boolean VR_Init(void)
{
if (vr_started) return true;
// Check for explicit VR command-line flags
boolean wantVR = (M_CheckParm("-openvr") || M_CheckParm("-vr") || vr_enabled);
if (!wantVR)
{
CONS_Printf("VR: No -vr flag detected, skipping VR initialization.\n");
CONS_Printf("VR: Use '-vr' command-line flag to enable OpenXR.\n");
return false;
}
CONS_Printf("VR: Initializing OpenXR...\n");
XrResult res;
const boolean visibilityMaskSupported =
VR_RuntimeExtensionSupported(XR_KHR_VISIBILITY_MASK_EXTENSION_NAME);
const boolean displayRefreshRateSupported =
VR_RuntimeExtensionSupported(XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME);
const char* enabledExtensions[3];
uint32_t enabledExtensionCount = 0;
enabledExtensions[enabledExtensionCount++] = XR_KHR_OPENGL_ENABLE_EXTENSION_NAME;
if (visibilityMaskSupported)
enabledExtensions[enabledExtensionCount++] = XR_KHR_VISIBILITY_MASK_EXTENSION_NAME;
if (displayRefreshRateSupported)
enabledExtensions[enabledExtensionCount++] = XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME;
XrInstanceCreateInfo createInfo = {XR_TYPE_INSTANCE_CREATE_INFO};
createInfo.next = NULL;
strncpy(createInfo.applicationInfo.applicationName, "SRB2Kart Blankart", XR_MAX_APPLICATION_NAME_SIZE);
createInfo.applicationInfo.applicationVersion = 1;
strncpy(createInfo.applicationInfo.engineName, "SRB2Kart", XR_MAX_ENGINE_NAME_SIZE);
createInfo.applicationInfo.engineVersion = 1;
createInfo.applicationInfo.apiVersion = XR_CURRENT_API_VERSION;
createInfo.enabledExtensionCount = enabledExtensionCount;
createInfo.enabledExtensionNames = enabledExtensions;
createInfo.createFlags = 0;
createInfo.enabledApiLayerCount = 0;
createInfo.enabledApiLayerNames = NULL;
res = xrCreateInstance(&createInfo, &vr_instance);
if (XR_FAILED(res)) {
CONS_Printf("VR: xrCreateInstance with optional extensions failed (XrResult=%d), retrying with OpenGL only...\n", (int)res);
createInfo.enabledExtensionCount = 1;
createInfo.enabledExtensionNames = enabledExtensions;
displayRefreshRateExtensionEnabled = false;
res = xrCreateInstance(&createInfo, &vr_instance);
} else {
displayRefreshRateExtensionEnabled = displayRefreshRateSupported;
}
if (XR_FAILED(res)) {
CONS_Printf("VR: Failed to create OpenXR instance (XrResult=%d).\n", (int)res);
CONS_Printf("VR: Is an OpenXR runtime (Monado, SteamVR, etc.) installed and active?\n");
CONS_Printf("VR: Check that XR_RUNTIME_JSON is set or /etc/xdg/openxr/1/active_runtime.json exists.\n");
return false;
}
CONS_Printf("VR: OpenXR instance created successfully.\n");
// Print runtime info
XrInstanceProperties instanceProps = {XR_TYPE_INSTANCE_PROPERTIES};
instanceProps.next = NULL;
if (XR_SUCCEEDED(xrGetInstanceProperties(vr_instance, &instanceProps))) {
CONS_Printf("VR: Runtime: %s (version %u.%u.%u)\n",
instanceProps.runtimeName,
XR_VERSION_MAJOR(instanceProps.runtimeVersion),
XR_VERSION_MINOR(instanceProps.runtimeVersion),
XR_VERSION_PATCH(instanceProps.runtimeVersion));
}
xrGetInstanceProcAddr(vr_instance, "xrGetOpenGLGraphicsRequirementsKHR", (PFN_xrVoidFunction*)&pfnGetOpenGLGraphicsRequirementsKHR);
xrGetInstanceProcAddr(vr_instance, "xrGetVisibilityMaskKHR", (PFN_xrVoidFunction*)&pfnGetVisibilityMaskKHR);
if (displayRefreshRateExtensionEnabled) {
xrGetInstanceProcAddr(vr_instance, "xrEnumerateDisplayRefreshRatesFB", (PFN_xrVoidFunction*)&pfnEnumerateDisplayRefreshRatesFB);
xrGetInstanceProcAddr(vr_instance, "xrGetDisplayRefreshRateFB", (PFN_xrVoidFunction*)&pfnGetDisplayRefreshRateFB);
xrGetInstanceProcAddr(vr_instance, "xrRequestDisplayRefreshRateFB", (PFN_xrVoidFunction*)&pfnRequestDisplayRefreshRateFB);
}
XrSystemGetInfo systemInfo = {XR_TYPE_SYSTEM_GET_INFO};
systemInfo.next = NULL;
systemInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
res = xrGetSystem(vr_instance, &systemInfo, &vr_system_id);
if (XR_FAILED(res)) {
CONS_Printf("VR: Failed to get OpenXR system (XrResult=%d). Is a headset connected?\n", (int)res);
xrDestroyInstance(vr_instance);
vr_instance = XR_NULL_HANDLE;
return false;
}
CONS_Printf("VR: OpenXR system acquired (systemId=%llu).\n", (unsigned long long)vr_system_id);
if (pfnGetOpenGLGraphicsRequirementsKHR) {
XrGraphicsRequirementsOpenGLKHR graphicsRequirements = {XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR};
graphicsRequirements.next = NULL;
res = pfnGetOpenGLGraphicsRequirementsKHR(vr_instance, vr_system_id, &graphicsRequirements);
if (XR_SUCCEEDED(res)) {
CONS_Printf("VR: OpenGL requirements: min=%u.%u max=%u.%u\n",
XR_VERSION_MAJOR(graphicsRequirements.minApiVersionSupported),
XR_VERSION_MINOR(graphicsRequirements.minApiVersionSupported),
XR_VERSION_MAJOR(graphicsRequirements.maxApiVersionSupported),
XR_VERSION_MINOR(graphicsRequirements.maxApiVersionSupported));
}
}
// Query the runtime's recommended per-eye resolution before creating the
// OpenXR session, then resize Blankart's render surface to match it.
uint32_t viewCount = 0;
xrEnumerateViewConfigurationViews(vr_instance, vr_system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &viewCount, NULL);
if (viewCount < 2) {
CONS_Printf("VR: ERROR - Expected 2 views for stereo, got %u.\n", viewCount);
VR_Shutdown();
return false;
}
XrViewConfigurationView* configViews = (XrViewConfigurationView*)malloc(viewCount * sizeof(XrViewConfigurationView));
for (uint32_t i = 0; i < viewCount; i++) {
configViews[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW;
configViews[i].next = NULL;
}
xrEnumerateViewConfigurationViews(vr_instance, vr_system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, viewCount, &viewCount, configViews);
const uint32_t recommendedWidth = configViews[0].recommendedImageRectWidth;
const uint32_t recommendedHeight = configViews[0].recommendedImageRectHeight;
const uint32_t maxWidth = configViews[0].maxImageRectWidth;
const uint32_t maxHeight = configViews[0].maxImageRectHeight;
const int resolutionMode = VR_StartupResolutionMode();
const float renderScale = VR_RenderScaleForMode(resolutionMode);
vr_render_width = (uint32_t)((float)recommendedWidth * renderScale + 0.5f);
vr_render_height = (uint32_t)((float)recommendedHeight * renderScale + 0.5f);
if (maxWidth > 0 && vr_render_width > maxWidth)
vr_render_width = maxWidth;
if (maxHeight > 0 && vr_render_height > maxHeight)
vr_render_height = maxHeight;
if (vr_render_width < 1)
vr_render_width = 1;
if (vr_render_height < 1)
vr_render_height = 1;
CONS_Printf("VR: Recommended render size: %ux%u (max: %ux%u); using %ux%u (%d%%).\n",
recommendedWidth, recommendedHeight, maxWidth, maxHeight,
vr_render_width, vr_render_height, (int)(renderScale * 100.0f + 0.5f));
free(configViews);
if ((uint32_t)vid.width != vr_render_width || (uint32_t)vid.height != vr_render_height) {
CONS_Printf("VR: Setting game render size to OpenXR eye size %ux%u.\n", vr_render_width, vr_render_height);
VID_SetMode(VID_GetModeForSize((INT32)vr_render_width, (INT32)vr_render_height));
}
SDL_PropertiesID props = SDL_GetWindowProperties(window);
if (!props) {
CONS_Printf("VR: Failed to get SDL window properties!\n");
xrDestroyInstance(vr_instance);
vr_instance = XR_NULL_HANDLE;
return false;
}
#if defined(_WIN32)
XrGraphicsBindingOpenGLWin32KHR graphicsBinding = {XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR};
graphicsBinding.next = NULL;
graphicsBinding.hDC = (HDC)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HDC_POINTER, NULL);
graphicsBinding.hGLRC = (HGLRC)SDL_GL_GetCurrentContext();
CONS_Printf("VR: Win32 graphics binding: hDC=%p, hGLRC=%p\n", (void*)graphicsBinding.hDC, (void*)graphicsBinding.hGLRC);
#elif defined(__linux__)
XrGraphicsBindingOpenGLXlibKHR graphicsBinding = {XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR};
graphicsBinding.next = NULL;
graphicsBinding.xDisplay = (Display*)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
// Get the GLX context and drawable from the current context
graphicsBinding.glxContext = glXGetCurrentContext();
graphicsBinding.glxDrawable = glXGetCurrentDrawable();
// Get the visual ID and FB config from the current context
if (graphicsBinding.xDisplay && graphicsBinding.glxContext) {
// Query the FB config from the current context
int fbConfigId = 0;
glXQueryContext(graphicsBinding.xDisplay, graphicsBinding.glxContext, GLX_FBCONFIG_ID, &fbConfigId);
// Find the matching FB config
int numConfigs = 0;
int attribs[] = { GLX_FBCONFIG_ID, fbConfigId, None };
GLXFBConfig* configs = glXChooseFBConfig(graphicsBinding.xDisplay,
DefaultScreen(graphicsBinding.xDisplay), attribs, &numConfigs);
if (configs && numConfigs > 0) {
graphicsBinding.glxFBConfig = configs[0];
// Get visual ID from the FB config
XVisualInfo* vi = glXGetVisualFromFBConfig(graphicsBinding.xDisplay, configs[0]);
if (vi) {
graphicsBinding.visualid = vi->visualid;
XFree(vi);
}
XFree(configs);
} else {
CONS_Printf("VR: WARNING - Could not find matching GLX FB config.\n");
graphicsBinding.glxFBConfig = NULL;
graphicsBinding.visualid = 0;
}
}
CONS_Printf("VR: Xlib graphics binding: display=%p, context=%p, drawable=%lu, visualid=%lu\n",
(void*)graphicsBinding.xDisplay,
(void*)graphicsBinding.glxContext,
(unsigned long)graphicsBinding.glxDrawable,
(unsigned long)graphicsBinding.visualid);
if (!graphicsBinding.xDisplay || !graphicsBinding.glxContext) {
CONS_Printf("VR: ERROR - X11 display or GLX context is NULL! Is the game running under X11?\n");
CONS_Printf("VR: Wayland-native is not yet supported; try running with SDL_VIDEODRIVER=x11\n");
xrDestroyInstance(vr_instance);
vr_instance = XR_NULL_HANDLE;
return false;
}
#endif
XrSessionCreateInfo sessionCreateInfo = {XR_TYPE_SESSION_CREATE_INFO};
sessionCreateInfo.next = &graphicsBinding;
sessionCreateInfo.systemId = vr_system_id;
sessionCreateInfo.createFlags = 0;
res = xrCreateSession(vr_instance, &sessionCreateInfo, &vr_session);
if (XR_FAILED(res)) {
CONS_Printf("VR: Failed to create OpenXR session (XrResult=%d).\n", (int)res);
xrDestroyInstance(vr_instance);
vr_instance = XR_NULL_HANDLE;
return false;
}
CONS_Printf("VR: OpenXR session created successfully.\n");
// Begin the session
XrSessionBeginInfo beginInfo = {XR_TYPE_SESSION_BEGIN_INFO};
beginInfo.next = NULL;
beginInfo.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
res = xrBeginSession(vr_session, &beginInfo);
if (XR_FAILED(res)) {
CONS_Printf("VR: Failed to begin OpenXR session (XrResult=%d).\n", (int)res);
xrDestroySession(vr_session);
vr_session = XR_NULL_HANDLE;
xrDestroyInstance(vr_instance);
vr_instance = XR_NULL_HANDLE;
return false;
}
CONS_Printf("VR: OpenXR session begun.\n");
VR_ConfigureDisplayRefreshRate();
XrReferenceSpaceCreateInfo spaceCreateInfo = {XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
spaceCreateInfo.next = NULL;
spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
spaceCreateInfo.poseInReferenceSpace.orientation.w = 1.0f;
spaceCreateInfo.poseInReferenceSpace.orientation.x = 0.0f;
spaceCreateInfo.poseInReferenceSpace.orientation.y = 0.0f;
spaceCreateInfo.poseInReferenceSpace.orientation.z = 0.0f;
spaceCreateInfo.poseInReferenceSpace.position.x = 0.0f;
spaceCreateInfo.poseInReferenceSpace.position.y = 0.0f;
spaceCreateInfo.poseInReferenceSpace.position.z = 0.0f;
res = xrCreateReferenceSpace(vr_session, &spaceCreateInfo, &vr_local_space);
if (XR_FAILED(res)) {
CONS_Printf("VR: Failed to create local reference space.\n");
VR_Shutdown();
return false;
}
spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
res = xrCreateReferenceSpace(vr_session, &spaceCreateInfo, &vr_view_space);
if (XR_FAILED(res)) {
CONS_Printf("VR: Failed to create view reference space.\n");
VR_Shutdown();
return false;
}
if (!VR_InitSwapchains()) {
CONS_Printf("VR: Failed to initialize swapchains!\n");
VR_Shutdown();
return false;
}
CONS_Printf("VR: Swapchains initialized.\n");
if (pfnGetVisibilityMaskKHR) {
openxr_process_visibility_mesh(0, vr_render_width, vr_render_height);
openxr_process_visibility_mesh(1, vr_render_width, vr_render_height);
CONS_Printf("VR: Visibility masks processed.\n");
} else {
CONS_Printf("VR: Visibility mask extension not available (cosmetic only).\n");
}
vr_started = true;
vr_enabled = true;
CV_StealthSet(&cv_vidwait, "Off");
CV_StealthSetValue(&cv_fpscap, -1);
CV_StealthSet(&cv_ticrate, "Compact");
CONS_Printf("VR: Desktop vsync disabled and FPS cap set to Unlimited; OpenXR xrWaitFrame will pace rendering.\n");
CONS_Printf("VR: OpenXR initialization complete! VR is now active.\n");
return true;
}
void VR_Shutdown(void)
{
if (!vr_started && vr_instance == XR_NULL_HANDLE) return;
if (vrVisibleAreaVertices[0]) { free(vrVisibleAreaVertices[0]); vrVisibleAreaVertices[0] = NULL; }
if (vrVisibleAreaVertices[1]) { free(vrVisibleAreaVertices[1]); vrVisibleAreaVertices[1] = NULL; }
if (vrVisibleAreaUVs[0]) { free(vrVisibleAreaUVs[0]); vrVisibleAreaUVs[0] = NULL; }
if (vrVisibleAreaUVs[1]) { free(vrVisibleAreaUVs[1]); vrVisibleAreaUVs[1] = NULL; }
VR_DestroySwapchains();
if (vr_local_space != XR_NULL_HANDLE) xrDestroySpace(vr_local_space);
if (vr_view_space != XR_NULL_HANDLE) xrDestroySpace(vr_view_space);
if (vr_session != XR_NULL_HANDLE) xrDestroySession(vr_session);
if (vr_instance != XR_NULL_HANDLE) xrDestroyInstance(vr_instance);
vr_local_space = XR_NULL_HANDLE;
vr_view_space = XR_NULL_HANDLE;
vr_session = XR_NULL_HANDLE;
vr_instance = XR_NULL_HANDLE;
vr_started = false;
vr_enabled = false;
vr_drawing_ui = false;
displayRefreshRateExtensionEnabled = false;
pfnEnumerateDisplayRefreshRatesFB = NULL;
pfnGetDisplayRefreshRateFB = NULL;
pfnRequestDisplayRefreshRateFB = NULL;
}
void VR_ScaleViewMatrices(float player_scale, int skybox_scale)
{
memcpy(vrHMDScaledPoseMatrix, vrHMDPoseMatrix, sizeof(float) * 16);
vrHMDScaledPoseMatrix[12] *= player_scale;
vrHMDScaledPoseMatrix[13] *= player_scale;
vrHMDScaledPoseMatrix[14] *= player_scale;
memcpy(vrScaledEyeViewMatrix[0], vrEyeViewMatrix[0], sizeof(float) * 16);
memcpy(vrScaledEyeViewMatrix[1], vrEyeViewMatrix[1], sizeof(float) * 16);
vrScaledEyeViewMatrix[0][12] *= player_scale;
vrScaledEyeViewMatrix[0][13] *= player_scale;
vrScaledEyeViewMatrix[0][14] *= player_scale;
vrScaledEyeViewMatrix[1][12] *= player_scale;
vrScaledEyeViewMatrix[1][13] *= player_scale;
vrScaledEyeViewMatrix[1][14] *= player_scale;
vrPlayerScale = player_scale;
memcpy(vrEyeSkyboxViewMatrix[0], vrScaledEyeViewMatrix[0], sizeof(float) * 16);
memcpy(vrEyeSkyboxViewMatrix[1], vrScaledEyeViewMatrix[1], sizeof(float) * 16);
if(skybox_scale > 0)
{
vrEyeSkyboxViewMatrix[0][12] /= skybox_scale;
vrEyeSkyboxViewMatrix[0][13] /= skybox_scale;
vrEyeSkyboxViewMatrix[0][14] /= skybox_scale;
vrEyeSkyboxViewMatrix[1][12] /= skybox_scale;
vrEyeSkyboxViewMatrix[1][13] /= skybox_scale;
vrEyeSkyboxViewMatrix[1][14] /= skybox_scale;
}
else
{
vrEyeSkyboxViewMatrix[0][12] =
vrEyeSkyboxViewMatrix[0][13] =
vrEyeSkyboxViewMatrix[0][14] =
vrEyeSkyboxViewMatrix[1][12] =
vrEyeSkyboxViewMatrix[1][13] =
vrEyeSkyboxViewMatrix[1][14] = 0;
}
}
#endif // HAVE_VR

76
src/vr/vr_main.h Normal file
View file

@ -0,0 +1,76 @@
#ifndef __VR_MAIN_H__
#define __VR_MAIN_H__
#ifdef HAVE_VR
#include "../doomdef.h"
#if defined(_WIN32)
#define XR_USE_PLATFORM_WIN32
#define XR_USE_GRAPHICS_API_OPENGL
#include <windows.h>
#elif defined(__linux__)
#define XR_USE_PLATFORM_XLIB
#define XR_USE_GRAPHICS_API_OPENGL
#include <X11/Xlib.h>
#include <GL/glx.h>
#endif
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#ifdef __cplusplus
extern "C" {
#endif
// Global state
extern int vr_current_eye;
extern boolean vr_started;
extern boolean vr_enabled;
extern boolean vr_drawing_ui;
typedef enum {
VR_PASS_NONE,
VR_PASS_3D_LEFT,
VR_PASS_3D_RIGHT,
VR_PASS_UI
} vr_render_pass_t;
extern vr_render_pass_t vr_render_pass;
extern XrInstance vr_instance;
extern XrSession vr_session;
extern XrSystemId vr_system_id;
extern XrSpace vr_local_space;
extern XrSpace vr_view_space;
// Global matrices (size 16)
extern float vrHMDPoseMatrix[16];
extern float vrHMDScaledPoseMatrix[16];
extern float vrHMDPoseSkyboxMatrix[16];
extern float vrEyeViewMatrix[2][16];
extern float vrScaledEyeViewMatrix[2][16];
extern float vrEyeSkyboxViewMatrix[2][16];
extern float vrEyeProjMatrix[2][16];
extern int vrWorldScale[3];
extern float vrPlayerScale;
extern uint32_t vr_render_width;
extern uint32_t vr_render_height;
// Visibility mask
extern float* vrVisibleAreaVertices[2];
extern float* vrVisibleAreaUVs[2];
extern uint32_t vrVisibleAreaVertexCount[2];
// Lifecycle
boolean VR_Init(void);
void VR_Shutdown(void);
void VR_ScaleViewMatrices(float player_scale, int skybox_scale);
#ifdef __cplusplus
}
#endif
#endif // HAVE_VR
#endif // __VR_MAIN_H__

126
src/vr/vr_math.c Normal file
View file

@ -0,0 +1,126 @@
#include "vr_math.h"
#ifdef HAVE_VR
#include <math.h>
void VR_MatrixInv(float* a, const float* b)
{
float x, y, z;
// transpose of rotation matrix
a[ 0] = b[ 0];
a[ 5] = b[ 5];
a[10] = b[10];
x=b[1]; a[1]=b[4]; a[4]=x;
x=b[2]; a[2]=b[8]; a[8]=x;
x=b[6]; a[6]=b[9]; a[9]=x;
// copy projection part
a[ 3] = b[ 3];
a[ 7] = b[ 7];
a[11] = b[11];
a[15] = b[15];
// convert origin: new_pos = - new_rotation_matrix * old_pos
x = (a[ 0]*b[12]) + (a[ 4]*b[13]) + (a[ 8]*b[14]);
y = (a[ 1]*b[12]) + (a[ 5]*b[13]) + (a[ 9]*b[14]);
z = (a[ 2]*b[12]) + (a[ 6]*b[13]) + (a[10]*b[14]);
a[12] = -x;
a[13] = -y;
a[14] = -z;
}
void VR_MatrixMultiply(float* out, const float* a, const float* b)
{
float r[16];
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 4; row++) {
r[row + col * 4] =
a[row + 0 * 4] * b[0 + col * 4] +
a[row + 1 * 4] * b[1 + col * 4] +
a[row + 2 * 4] * b[2 + col * 4] +
a[row + 3 * 4] * b[3 + col * 4];
}
}
for (int i = 0; i < 16; i++)
out[i] = r[i];
}
void VR_RotateVecByMat(const float* m, float* v)
{
float x = (m[0]*v[0]) + (m[4]*v[1]) + (m[ 8]*v[2]); // Note: corrected from original openvr_common.c
float y = (m[1]*v[0]) + (m[5]*v[1]) + (m[ 9]*v[2]); // which used v[0] in all y places etc.
float z = (m[2]*v[0]) + (m[6]*v[1]) + (m[10]*v[2]); // Wait, I should check how it was exactly.
v[0] = x;
v[1] = y;
v[2] = z;
}
void VR_PoseToMatrix(const XrPosef* pose, float* glMatrix)
{
// Convert quaternion to 3x3 rotation matrix
float xx = pose->orientation.x * pose->orientation.x;
float yy = pose->orientation.y * pose->orientation.y;
float zz = pose->orientation.z * pose->orientation.z;
float xy = pose->orientation.x * pose->orientation.y;
float xz = pose->orientation.x * pose->orientation.z;
float yz = pose->orientation.y * pose->orientation.z;
float wx = pose->orientation.w * pose->orientation.x;
float wy = pose->orientation.w * pose->orientation.y;
float wz = pose->orientation.w * pose->orientation.z;
glMatrix[ 0] = 1.0f - 2.0f * (yy + zz);
glMatrix[ 1] = 2.0f * (xy + wz);
glMatrix[ 2] = 2.0f * (xz - wy);
glMatrix[ 3] = 0.0f;
glMatrix[ 4] = 2.0f * (xy - wz);
glMatrix[ 5] = 1.0f - 2.0f * (xx + zz);
glMatrix[ 6] = 2.0f * (yz + wx);
glMatrix[ 7] = 0.0f;
glMatrix[ 8] = 2.0f * (xz + wy);
glMatrix[ 9] = 2.0f * (yz - wx);
glMatrix[10] = 1.0f - 2.0f * (xx + yy);
glMatrix[11] = 0.0f;
glMatrix[12] = pose->position.x;
glMatrix[13] = pose->position.y;
glMatrix[14] = pose->position.z;
glMatrix[15] = 1.0f;
}
void VR_FovToProjection(const XrFovf* fov, float nearZ, float farZ, float* glMatrix)
{
float tanLeft = tanf(fov->angleLeft);
float tanRight = tanf(fov->angleRight);
float tanDown = tanf(fov->angleDown);
float tanUp = tanf(fov->angleUp);
float tanWidth = tanRight - tanLeft;
float tanHeight = tanUp - tanDown;
glMatrix[ 0] = 2.0f / tanWidth;
glMatrix[ 1] = 0.0f;
glMatrix[ 2] = 0.0f;
glMatrix[ 3] = 0.0f;
glMatrix[ 4] = 0.0f;
glMatrix[ 5] = 2.0f / tanHeight;
glMatrix[ 6] = 0.0f;
glMatrix[ 7] = 0.0f;
glMatrix[ 8] = (tanRight + tanLeft) / tanWidth;
glMatrix[ 9] = (tanUp + tanDown) / tanHeight;
glMatrix[10] = -(farZ + nearZ) / (farZ - nearZ);
glMatrix[11] = -1.0f;
glMatrix[12] = 0.0f;
glMatrix[13] = 0.0f;
glMatrix[14] = -(2.0f * farZ * nearZ) / (farZ - nearZ);
glMatrix[15] = 0.0f;
}
#endif // HAVE_VR

33
src/vr/vr_math.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef __VR_MATH_H__
#define __VR_MATH_H__
#ifdef HAVE_VR
#include <openxr/openxr.h>
#ifdef __cplusplus
extern "C" {
#endif
// Invert a 4x4 matrix (assumes it's a rigid body transform: rotation + translation)
// safe to use same pointer for a and b (a = inv(b))
void VR_MatrixInv(float* a, const float* b);
// Multiply two OpenGL 4x4 matrices (column-major): out = a * b
void VR_MatrixMultiply(float* out, const float* a, const float* b);
// Rotate a 3D vector by a 4x4 matrix
void VR_RotateVecByMat(const float* m, float* v);
// Convert an OpenXR XrPosef to an OpenGL 4x4 matrix (column-major)
void VR_PoseToMatrix(const XrPosef* pose, float* glMatrix);
// Create an asymmetric OpenGL projection matrix from OpenXR FOV tangents
void VR_FovToProjection(const XrFovf* fov, float nearZ, float farZ, float* glMatrix);
#ifdef __cplusplus
}
#endif
#endif // HAVE_VR
#endif // __VR_MATH_H__

795
src/vr/vr_render.c Normal file
View file

@ -0,0 +1,795 @@
#include "vr_render.h"
#ifdef HAVE_VR
#include "vr_math.h"
#include "../console.h"
#include "../screen.h"
#include "../hardware/r_opengl/r_opengl.h"
#if defined(_WIN32)
#include <windows.h>
#include <GL/gl.h>
#include <GL/glext.h>
#elif defined(__linux__)
#include <GL/gl.h>
#include <GL/glext.h>
#endif
#include <SDL3/SDL.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#ifndef GL_FRAMEBUFFER
#define GL_FRAMEBUFFER 0x8D40
#define GL_COLOR_ATTACHMENT0 0x8CE0
#define GL_DEPTH_ATTACHMENT 0x8D00
#define GL_DEPTH_COMPONENT24 0x81A6
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
#endif
#ifndef GL_RENDERBUFFER
#define GL_RENDERBUFFER 0x8D41
#endif
#ifndef GL_READ_FRAMEBUFFER
#define GL_READ_FRAMEBUFFER 0x8CA8
#endif
#ifndef GL_DRAW_FRAMEBUFFER
#define GL_DRAW_FRAMEBUFFER 0x8CA9
#endif
typedef void (APIENTRY * PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers);
typedef void (APIENTRY * PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer);
typedef void (APIENTRY * PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
typedef void (APIENTRY * PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers);
typedef void (APIENTRY * PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer);
typedef void (APIENTRY * PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
typedef void (APIENTRY * PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
typedef void (APIENTRY * PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers);
typedef void (APIENTRY * PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers);
typedef GLenum (APIENTRY * PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target);
typedef void (APIENTRY * PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
static PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers = NULL;
static PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer = NULL;
static PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D = NULL;
static PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers = NULL;
static PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer = NULL;
static PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage = NULL;
static PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer = NULL;
static PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers = NULL;
static PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers = NULL;
static PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus = NULL;
static PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer = NULL;
XrSwapchain vr_swapchains[2] = {XR_NULL_HANDLE, XR_NULL_HANDLE};
uint32_t vr_swapchain_lengths[2] = {0, 0};
XrSwapchainImageOpenGLKHR* vr_swapchain_images[2] = {NULL, NULL};
uint32_t* vr_framebuffers[2] = {NULL, NULL};
uint32_t* vr_depthbuffers[2] = {NULL, NULL};
XrSwapchain vr_ui_swapchain = XR_NULL_HANDLE;
uint32_t vr_ui_swapchain_length = 0;
XrSwapchainImageOpenGLKHR* vr_ui_swapchain_images = NULL;
uint32_t* vr_ui_framebuffers = NULL;
uint32_t vr_ui_width = 0;
uint32_t vr_ui_height = 0;
enum {
VR_DEFAULT_UI_SWAPCHAIN_WIDTH = 1920,
VR_DEFAULT_UI_SWAPCHAIN_HEIGHT = 1080
};
static void LoadFBOFunctions(void) {
if (glGenFramebuffers) return;
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)SDL_GL_GetProcAddress("glGenFramebuffers");
glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)SDL_GL_GetProcAddress("glBindFramebuffer");
glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)SDL_GL_GetProcAddress("glFramebufferTexture2D");
glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)SDL_GL_GetProcAddress("glGenRenderbuffers");
glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)SDL_GL_GetProcAddress("glBindRenderbuffer");
glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)SDL_GL_GetProcAddress("glRenderbufferStorage");
glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)SDL_GL_GetProcAddress("glFramebufferRenderbuffer");
glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)SDL_GL_GetProcAddress("glDeleteFramebuffers");
glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)SDL_GL_GetProcAddress("glDeleteRenderbuffers");
glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)SDL_GL_GetProcAddress("glCheckFramebufferStatus");
glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)SDL_GL_GetProcAddress("glBlitFramebuffer");
}
boolean VR_InitSwapchains(void)
{
LoadFBOFunctions();
if (!glGenFramebuffers) {
CONS_Printf("VR_InitSwapchains: FBO extensions not found!\n");
return false;
}
for (int eye = 0; eye < 2; eye++) {
XrSwapchainCreateInfo swapchainCreateInfo = {XR_TYPE_SWAPCHAIN_CREATE_INFO};
swapchainCreateInfo.next = NULL;
swapchainCreateInfo.createFlags = 0;
swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
swapchainCreateInfo.format = GL_SRGB8_ALPHA8;
swapchainCreateInfo.sampleCount = 1;
swapchainCreateInfo.width = vr_render_width;
swapchainCreateInfo.height = vr_render_height;
swapchainCreateInfo.faceCount = 1;
swapchainCreateInfo.arraySize = 1;
swapchainCreateInfo.mipCount = 1;
XrResult res = xrCreateSwapchain(vr_session, &swapchainCreateInfo, &vr_swapchains[eye]);
if (XR_FAILED(res)) {
CONS_Printf("VR: Failed to create swapchain %d (XrResult=%d).\n", eye, (int)res);
return false;
}
xrEnumerateSwapchainImages(vr_swapchains[eye], 0, &vr_swapchain_lengths[eye], NULL);
CONS_Printf("VR: Swapchain %d has %u images.\n", eye, vr_swapchain_lengths[eye]);
vr_swapchain_images[eye] = (XrSwapchainImageOpenGLKHR*)malloc(vr_swapchain_lengths[eye] * sizeof(XrSwapchainImageOpenGLKHR));
for (uint32_t i = 0; i < vr_swapchain_lengths[eye]; i++) {
vr_swapchain_images[eye][i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
vr_swapchain_images[eye][i].next = NULL;
}
xrEnumerateSwapchainImages(vr_swapchains[eye], vr_swapchain_lengths[eye], &vr_swapchain_lengths[eye], (XrSwapchainImageBaseHeader*)vr_swapchain_images[eye]);
vr_framebuffers[eye] = (uint32_t*)malloc(vr_swapchain_lengths[eye] * sizeof(uint32_t));
vr_depthbuffers[eye] = (uint32_t*)malloc(vr_swapchain_lengths[eye] * sizeof(uint32_t));
for (uint32_t i = 0; i < vr_swapchain_lengths[eye]; i++) {
glGenFramebuffers(1, &vr_framebuffers[eye][i]);
glGenRenderbuffers(1, &vr_depthbuffers[eye][i]);
glBindFramebuffer(GL_FRAMEBUFFER, vr_framebuffers[eye][i]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, vr_swapchain_images[eye][i].image, 0);
glBindRenderbuffer(GL_RENDERBUFFER, vr_depthbuffers[eye][i]);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, vr_render_width, vr_render_height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, vr_depthbuffers[eye][i]);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
CONS_Printf("VR: WARNING - FBO %u for eye %d is incomplete (status=0x%x)!\n", i, eye, status);
}
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Keep UI at a stable 16:9 panel size. Eye targets can be square on canted HMDs.
vr_ui_width = VR_DEFAULT_UI_SWAPCHAIN_WIDTH;
vr_ui_height = VR_DEFAULT_UI_SWAPCHAIN_HEIGHT;
XrSwapchainCreateInfo uiSwapchainCreateInfo = {XR_TYPE_SWAPCHAIN_CREATE_INFO};
uiSwapchainCreateInfo.arraySize = 1;
uiSwapchainCreateInfo.format = GL_SRGB8_ALPHA8;
uiSwapchainCreateInfo.width = vr_ui_width;
uiSwapchainCreateInfo.height = vr_ui_height;
uiSwapchainCreateInfo.mipCount = 1;
uiSwapchainCreateInfo.faceCount = 1;
uiSwapchainCreateInfo.sampleCount = 1;
uiSwapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT;
XrResult res = xrCreateSwapchain(vr_session, &uiSwapchainCreateInfo, &vr_ui_swapchain);
if (XR_FAILED(res)) {
CONS_Printf("VR: Failed to create UI swapchain.\n");
return false;
}
xrEnumerateSwapchainImages(vr_ui_swapchain, 0, &vr_ui_swapchain_length, NULL);
vr_ui_swapchain_images = (XrSwapchainImageOpenGLKHR*)malloc(vr_ui_swapchain_length * sizeof(XrSwapchainImageOpenGLKHR));
for (uint32_t i = 0; i < vr_ui_swapchain_length; i++) {
vr_ui_swapchain_images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
vr_ui_swapchain_images[i].next = NULL;
}
xrEnumerateSwapchainImages(vr_ui_swapchain, vr_ui_swapchain_length, &vr_ui_swapchain_length, (XrSwapchainImageBaseHeader*)vr_ui_swapchain_images);
vr_ui_framebuffers = (uint32_t*)malloc(vr_ui_swapchain_length * sizeof(uint32_t));
for (uint32_t i = 0; i < vr_ui_swapchain_length; i++) {
glGenFramebuffers(1, &vr_ui_framebuffers[i]);
glBindFramebuffer(GL_FRAMEBUFFER, vr_ui_framebuffers[i]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, vr_ui_swapchain_images[i].image, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
CONS_Printf("VR: Failed to create UI framebuffer %d\n", i);
return false;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
CONS_Printf("VR: UI swapchain created (%ux%u, %u images).\n",
vr_ui_width, vr_ui_height, vr_ui_swapchain_length);
return true;
}
static XrPosef cachedUIPose;
static boolean cachedUIPoseValid = false;
void VR_DestroySwapchains(void)
{
for (int eye = 0; eye < 2; eye++) {
if (vr_framebuffers[eye] && glDeleteFramebuffers) {
glDeleteFramebuffers(vr_swapchain_lengths[eye], vr_framebuffers[eye]);
free(vr_framebuffers[eye]);
vr_framebuffers[eye] = NULL;
}
if (vr_depthbuffers[eye] && glDeleteRenderbuffers) {
glDeleteRenderbuffers(vr_swapchain_lengths[eye], vr_depthbuffers[eye]);
free(vr_depthbuffers[eye]);
vr_depthbuffers[eye] = NULL;
}
if (vr_swapchain_images[eye]) {
free(vr_swapchain_images[eye]);
vr_swapchain_images[eye] = NULL;
}
if (vr_swapchains[eye] != XR_NULL_HANDLE) {
xrDestroySwapchain(vr_swapchains[eye]);
vr_swapchains[eye] = XR_NULL_HANDLE;
}
vr_swapchain_lengths[eye] = 0;
}
if (vr_ui_framebuffers && glDeleteFramebuffers) {
glDeleteFramebuffers(vr_ui_swapchain_length, vr_ui_framebuffers);
free(vr_ui_framebuffers);
vr_ui_framebuffers = NULL;
}
if (vr_ui_swapchain_images) {
free(vr_ui_swapchain_images);
vr_ui_swapchain_images = NULL;
}
if (vr_ui_swapchain != XR_NULL_HANDLE) {
xrDestroySwapchain(vr_ui_swapchain);
vr_ui_swapchain = XR_NULL_HANDLE;
}
vr_ui_swapchain_length = 0;
vr_ui_width = 0;
vr_ui_height = 0;
cachedUIPoseValid = false;
}
// Cached per-frame state
static XrFrameState frameState = {XR_TYPE_FRAME_STATE};
static uint32_t swapchainImageIndex[2];
static uint32_t uiSwapchainImageIndex;
static boolean vr_frame_begun = false;
static boolean eyeSwapchainAcquired[2] = {false, false};
static boolean eyeSwapchainRendered[2] = {false, false};
static boolean uiSwapchainAcquired = false;
static XrView cachedViews[2]; // Cache the located views for EndFrame
static boolean cachedViewsValid = false;
static XrPosef cachedHeadPose;
static boolean cachedHeadPoseValid = false;
static XrSessionState vr_session_state = XR_SESSION_STATE_UNKNOWN;
static boolean vr_session_running = false;
// Poll and handle OpenXR events (session state machine)
static void VR_PollEvents(void)
{
if (!vr_started) return;
XrEventDataBuffer eventData = {XR_TYPE_EVENT_DATA_BUFFER};
eventData.next = NULL;
while (xrPollEvent(vr_instance, &eventData) == XR_SUCCESS) {
switch (eventData.type) {
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
XrEventDataSessionStateChanged* stateChanged = (XrEventDataSessionStateChanged*)&eventData;
vr_session_state = stateChanged->state;
CONS_Printf("VR: Session state changed to %d\n", (int)vr_session_state);
switch (vr_session_state) {
case XR_SESSION_STATE_READY:
// Session is already begun in VR_Init, but handle if needed
vr_session_running = true;
break;
case XR_SESSION_STATE_SYNCHRONIZED:
case XR_SESSION_STATE_VISIBLE:
case XR_SESSION_STATE_FOCUSED:
vr_session_running = true;
break;
case XR_SESSION_STATE_STOPPING:
xrEndSession(vr_session);
vr_session_running = false;
break;
case XR_SESSION_STATE_LOSS_PENDING:
case XR_SESSION_STATE_EXITING:
vr_session_running = false;
break;
default:
break;
}
break;
}
default:
break;
}
// Reset for next event
eventData.type = XR_TYPE_EVENT_DATA_BUFFER;
eventData.next = NULL;
}
}
static void VR_SetIdentityMatrix(float* m)
{
memset(m, 0, sizeof(float) * 16);
m[0] = 1.0f;
m[5] = 1.0f;
m[10] = 1.0f;
m[15] = 1.0f;
}
static void VR_ScaleMatrixTranslation(float* m, float scale)
{
m[12] *= scale;
m[13] *= scale;
m[14] *= scale;
}
static float VR_YawFromQuaternion(const XrQuaternionf* q)
{
return atan2f(2.0f * (q->w * q->y + q->x * q->z),
1.0f - 2.0f * (q->y * q->y + q->x * q->x));
}
static float VR_RollFromQuaternion(const XrQuaternionf* q)
{
return atan2f(2.0f * (q->w * q->z + q->x * q->y),
1.0f - 2.0f * (q->z * q->z + q->y * q->y));
}
static XrQuaternionf VR_QuaternionMultiply(const XrQuaternionf* a, const XrQuaternionf* b)
{
XrQuaternionf q;
q.x = a->w * b->x + a->x * b->w + a->y * b->z - a->z * b->y;
q.y = a->w * b->y - a->x * b->z + a->y * b->w + a->z * b->x;
q.z = a->w * b->z + a->x * b->y - a->y * b->x + a->z * b->w;
q.w = a->w * b->w - a->x * b->x - a->y * b->y - a->z * b->z;
return q;
}
static XrQuaternionf VR_QuaternionFromYaw(float yaw)
{
XrQuaternionf q;
const float halfYaw = yaw * 0.5f;
q.x = 0.0f;
q.y = sinf(halfYaw);
q.z = 0.0f;
q.w = cosf(halfYaw);
return q;
}
static XrQuaternionf VR_QuaternionFromRoll(float roll)
{
XrQuaternionf q;
const float halfRoll = roll * 0.5f;
q.x = 0.0f;
q.y = 0.0f;
q.z = sinf(halfRoll);
q.w = cosf(halfRoll);
return q;
}
static XrQuaternionf VR_QuaternionFromYawRoll(float yaw, float roll)
{
XrQuaternionf yawQ = VR_QuaternionFromYaw(yaw);
XrQuaternionf rollQ = VR_QuaternionFromRoll(roll);
return VR_QuaternionMultiply(&yawQ, &rollQ);
}
static XrPosef VR_MakeUIPose(float distance)
{
XrPosef pose;
memset(&pose, 0, sizeof(pose));
pose.orientation.w = 1.0f;
pose.position.z = -distance;
if (!cachedHeadPoseValid)
return pose;
const float yaw = VR_YawFromQuaternion(&cachedHeadPose.orientation);
const float roll = VR_RollFromQuaternion(&cachedHeadPose.orientation);
pose.orientation = VR_QuaternionFromYawRoll(yaw, roll);
pose.position = cachedHeadPose.position;
pose.position.x += -sinf(yaw) * distance;
pose.position.z += -cosf(yaw) * distance;
return pose;
}
static int VR_WorldScale(void)
{
int mode = cv_vrscale.string ? cv_vrscale.value : 1;
if (mode < 0)
mode = 0;
else if (mode > 2)
mode = 2;
return vrWorldScale[mode];
}
boolean VR_BeginFrame(void)
{
if (!vr_started) return false;
// Poll events first — this drives the session state machine
VR_PollEvents();
if (!vr_session_running) return false;
cachedViewsValid = false;
cachedHeadPoseValid = false;
vr_frame_begun = false;
eyeSwapchainAcquired[0] = false;
eyeSwapchainAcquired[1] = false;
eyeSwapchainRendered[0] = false;
eyeSwapchainRendered[1] = false;
uiSwapchainAcquired = false;
XrFrameWaitInfo frameWaitInfo = {XR_TYPE_FRAME_WAIT_INFO};
frameWaitInfo.next = NULL;
frameState.type = XR_TYPE_FRAME_STATE;
frameState.next = NULL;
XrResult res = xrWaitFrame(vr_session, &frameWaitInfo, &frameState);
if (XR_FAILED(res)) {
CONS_Printf("VR: xrWaitFrame failed (XrResult=%d)\n", (int)res);
return false;
}
XrFrameBeginInfo frameBeginInfo = {XR_TYPE_FRAME_BEGIN_INFO};
frameBeginInfo.next = NULL;
res = xrBeginFrame(vr_session, &frameBeginInfo);
if (XR_FAILED(res)) {
CONS_Printf("VR: xrBeginFrame failed (XrResult=%d)\n", (int)res);
return false;
}
vr_frame_begun = true;
// Locate views to get per-eye poses and FOVs
XrViewLocateInfo viewLocateInfo = {XR_TYPE_VIEW_LOCATE_INFO};
viewLocateInfo.next = NULL;
viewLocateInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
viewLocateInfo.displayTime = frameState.predictedDisplayTime;
viewLocateInfo.space = vr_local_space;
XrViewState viewState = {XR_TYPE_VIEW_STATE};
viewState.next = NULL;
uint32_t viewCountOutput = 0;
cachedViews[0].type = XR_TYPE_VIEW; cachedViews[0].next = NULL;
cachedViews[1].type = XR_TYPE_VIEW; cachedViews[1].next = NULL;
res = xrLocateViews(vr_session, &viewLocateInfo, &viewState, 2, &viewCountOutput, cachedViews);
if (XR_FAILED(res)) {
CONS_Printf("VR: xrLocateViews failed (XrResult=%d)\n", (int)res);
return true;
}
if (viewCountOutput == 2 &&
(viewState.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) &&
(viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT)) {
cachedViewsValid = true;
XrSpaceLocation headLocation = {XR_TYPE_SPACE_LOCATION};
res = xrLocateSpace(vr_view_space, vr_local_space, frameState.predictedDisplayTime, &headLocation);
if (XR_SUCCEEDED(res) &&
(headLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) &&
(headLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)) {
cachedHeadPose = headLocation.pose;
cachedHeadPoseValid = true;
} else {
cachedHeadPose = cachedViews[0].pose;
cachedHeadPose.position.x = (cachedViews[0].pose.position.x + cachedViews[1].pose.position.x) * 0.5f;
cachedHeadPose.position.y = (cachedViews[0].pose.position.y + cachedViews[1].pose.position.y) * 0.5f;
cachedHeadPose.position.z = (cachedViews[0].pose.position.z + cachedViews[1].pose.position.z) * 0.5f;
cachedHeadPoseValid = true;
}
float headPoseMatrix[16];
float headInverseMeters[16];
VR_PoseToMatrix(&cachedHeadPose, headPoseMatrix);
VR_MatrixInv(headInverseMeters, headPoseMatrix);
const float worldScale = VR_WorldScale();
const int poseMode = cv_vrposemode.value;
if (poseMode == 3)
memcpy(vrHMDPoseMatrix, headPoseMatrix, sizeof(float) * 16);
else
memcpy(vrHMDPoseMatrix, headInverseMeters, sizeof(float) * 16);
VR_ScaleMatrixTranslation(vrHMDPoseMatrix, worldScale);
memcpy(vrHMDScaledPoseMatrix, vrHMDPoseMatrix, sizeof(float) * 16);
VR_ScaleMatrixTranslation(vrHMDScaledPoseMatrix, vrPlayerScale);
memcpy(vrHMDPoseSkyboxMatrix, vrHMDPoseMatrix, sizeof(float) * 16);
vrHMDPoseSkyboxMatrix[12] = 0.0f;
vrHMDPoseSkyboxMatrix[13] = 0.0f;
vrHMDPoseSkyboxMatrix[14] = 0.0f;
for (int i = 0; i < 2; i++) {
VR_FovToProjection(&cachedViews[i].fov, 0.9f, 32768.0f, vrEyeProjMatrix[i]);
float eyePoseMatrix[16];
VR_PoseToMatrix(&cachedViews[i].pose, eyePoseMatrix);
if (poseMode == 1 || poseMode == 3) {
float eyeToHeadMatrix[16];
// Match the old OpenVR path: keep the HMD pose and per-eye
// head-to-eye transform separate, then multiply them in shader.
VR_MatrixMultiply(eyeToHeadMatrix, headInverseMeters, eyePoseMatrix);
VR_MatrixInv(vrEyeViewMatrix[i], eyeToHeadMatrix);
} else if (poseMode == 2) {
memcpy(vrEyeViewMatrix[i], eyePoseMatrix, sizeof(float) * 16);
} else {
// SDK-style path: render each eye from the full located view.
VR_MatrixInv(vrEyeViewMatrix[i], eyePoseMatrix);
}
VR_ScaleMatrixTranslation(vrEyeViewMatrix[i], worldScale);
}
VR_ScaleViewMatrices(vrPlayerScale, cv_vrdisableskystereo.value ? 0 : 1);
} else {
VR_SetIdentityMatrix(vrHMDPoseMatrix);
VR_SetIdentityMatrix(vrHMDScaledPoseMatrix);
VR_SetIdentityMatrix(vrHMDPoseSkyboxMatrix);
VR_SetIdentityMatrix(vrEyeViewMatrix[0]);
VR_SetIdentityMatrix(vrEyeViewMatrix[1]);
VR_SetIdentityMatrix(vrScaledEyeViewMatrix[0]);
VR_SetIdentityMatrix(vrScaledEyeViewMatrix[1]);
VR_SetIdentityMatrix(vrEyeSkyboxViewMatrix[0]);
VR_SetIdentityMatrix(vrEyeSkyboxViewMatrix[1]);
}
return true;
}
boolean VR_SetEye(int eye)
{
if (!vr_started || !vr_session_running || !vr_frame_begun) return false;
if (eye < 0 || eye > 1) return false;
vr_current_eye = eye;
vr_drawing_ui = false;
vr_render_pass = (eye == 0) ? VR_PASS_3D_LEFT : VR_PASS_3D_RIGHT;
XrSwapchainImageAcquireInfo acquireInfo = {XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO};
acquireInfo.next = NULL;
XrResult res = xrAcquireSwapchainImage(vr_swapchains[eye], &acquireInfo, &swapchainImageIndex[eye]);
if (XR_FAILED(res)) {
CONS_Printf("VR: xrAcquireSwapchainImage failed for eye %d (XrResult=%d)\n", eye, (int)res);
return false;
}
eyeSwapchainAcquired[eye] = true;
XrSwapchainImageWaitInfo waitInfo = {XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO};
waitInfo.next = NULL;
waitInfo.timeout = XR_INFINITE_DURATION;
res = xrWaitSwapchainImage(vr_swapchains[eye], &waitInfo);
if (XR_FAILED(res)) {
CONS_Printf("VR: xrWaitSwapchainImage failed for eye %d (XrResult=%d)\n", eye, (int)res);
XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
releaseInfo.next = NULL;
xrReleaseSwapchainImage(vr_swapchains[eye], &releaseInfo);
eyeSwapchainAcquired[eye] = false;
return false;
}
glBindFramebuffer(GL_FRAMEBUFFER, vr_framebuffers[eye][swapchainImageIndex[eye]]);
glViewport(0, 0, vr_render_width, vr_render_height);
SetModelView((GLint)vr_render_width, (GLint)vr_render_height);
return true;
}
void VR_ReleaseEye(int eye)
{
if (!vr_started || !vr_session_running || eye < 0 || eye > 1 || !eyeSwapchainAcquired[eye]) return;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
releaseInfo.next = NULL;
xrReleaseSwapchainImage(vr_swapchains[eye], &releaseInfo);
eyeSwapchainAcquired[eye] = false;
eyeSwapchainRendered[eye] = true;
}
void VR_BindDefaultFramebuffer(void)
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
boolean VR_MirrorEyeToDefaultFramebuffer(int width, int height)
{
if (!vr_started || !vr_session_running || vr_current_eye < 0 || vr_current_eye > 1)
return false;
if (!eyeSwapchainAcquired[vr_current_eye] || !glBlitFramebuffer)
return false;
if (width <= 0 || height <= 0)
return false;
glBindFramebuffer(GL_READ_FRAMEBUFFER, vr_framebuffers[vr_current_eye][swapchainImageIndex[vr_current_eye]]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDisable(GL_SCISSOR_TEST);
glViewport(0, 0, width, height);
glBlitFramebuffer(0, 0, (GLint)vr_render_width, (GLint)vr_render_height,
0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
return true;
}
void VR_BeginInEyeUI(void)
{
if (!vr_started || !vr_session_running || !vr_frame_begun) return;
if (vr_current_eye < 0 || vr_current_eye > 1 || !eyeSwapchainAcquired[vr_current_eye]) return;
vr_drawing_ui = true;
glBindFramebuffer(GL_FRAMEBUFFER, vr_framebuffers[vr_current_eye][swapchainImageIndex[vr_current_eye]]);
glViewport(0, 0, vr_render_width, vr_render_height);
GL_UnSetShader();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_SCISSOR_TEST);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
}
boolean VR_BindUISwapchain(void)
{
if (!vr_started || !vr_session_running || !vr_frame_begun || vr_ui_swapchain == XR_NULL_HANDLE) return false;
if (!uiSwapchainAcquired) {
XrSwapchainImageAcquireInfo acquireInfo = {XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO};
acquireInfo.next = NULL;
XrResult res = xrAcquireSwapchainImage(vr_ui_swapchain, &acquireInfo, &uiSwapchainImageIndex);
if (XR_FAILED(res)) return false;
XrSwapchainImageWaitInfo waitInfo = {XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO};
waitInfo.next = NULL;
waitInfo.timeout = XR_INFINITE_DURATION;
res = xrWaitSwapchainImage(vr_ui_swapchain, &waitInfo);
if (XR_FAILED(res)) {
XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
releaseInfo.next = NULL;
xrReleaseSwapchainImage(vr_ui_swapchain, &releaseInfo);
return false;
}
uiSwapchainAcquired = true;
}
vr_render_pass = VR_PASS_UI;
vr_drawing_ui = true;
glBindFramebuffer(GL_FRAMEBUFFER, vr_ui_framebuffers[uiSwapchainImageIndex]);
// Keep the hardware 2D renderer's logical screen size unchanged. Its UI
// patch and screen-texture paths key off screen_width/screen_height, while
// the OpenXR swapchain can still use its full physical viewport.
SetModelView((GLint)vid.width, (GLint)vid.height);
glViewport(0, 0, vr_ui_width, vr_ui_height);
GL_UnSetShader();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
return true;
}
void VR_EndFrame(void)
{
if (!vr_started || !vr_session_running || !vr_frame_begun) return;
XrCompositionLayerProjectionView projectionViews[2];
for (int i = 0; i < 2; i++) {
projectionViews[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
projectionViews[i].next = NULL;
// Use the ACTUAL tracked view poses, not hardcoded placeholders
if (cachedViewsValid) {
projectionViews[i].pose = cachedViews[i].pose;
projectionViews[i].fov = cachedViews[i].fov;
} else {
// Fallback identity pose (should rarely happen)
projectionViews[i].pose.orientation.w = 1.0f;
projectionViews[i].pose.orientation.x = 0.0f;
projectionViews[i].pose.orientation.y = 0.0f;
projectionViews[i].pose.orientation.z = 0.0f;
projectionViews[i].pose.position.x = 0.0f;
projectionViews[i].pose.position.y = 0.0f;
projectionViews[i].pose.position.z = 0.0f;
projectionViews[i].fov.angleUp = 0.9f;
projectionViews[i].fov.angleDown = -0.9f;
projectionViews[i].fov.angleLeft = -0.9f;
projectionViews[i].fov.angleRight = 0.9f;
}
projectionViews[i].subImage.swapchain = vr_swapchains[i];
projectionViews[i].subImage.imageRect.offset.x = 0;
projectionViews[i].subImage.imageRect.offset.y = 0;
projectionViews[i].subImage.imageRect.extent.width = vr_render_width;
projectionViews[i].subImage.imageRect.extent.height = vr_render_height;
projectionViews[i].subImage.imageArrayIndex = 0;
}
const XrCompositionLayerBaseHeader* layers[2];
uint32_t layerCount = 0;
XrCompositionLayerProjection projectionLayer = {XR_TYPE_COMPOSITION_LAYER_PROJECTION};
if (cachedViewsValid && eyeSwapchainRendered[0] && eyeSwapchainRendered[1]) {
projectionLayer.layerFlags = 0;
projectionLayer.space = vr_local_space;
projectionLayer.viewCount = 2;
projectionLayer.views = projectionViews;
layers[layerCount++] = (const XrCompositionLayerBaseHeader*)&projectionLayer;
}
XrCompositionLayerQuad uiLayer = {XR_TYPE_COMPOSITION_LAYER_QUAD};
if (uiSwapchainAcquired) {
XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
releaseInfo.next = NULL;
xrReleaseSwapchainImage(vr_ui_swapchain, &releaseInfo);
uiLayer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT |
XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
uiLayer.space = cachedHeadPoseValid ? vr_local_space : vr_view_space;
uiLayer.eyeVisibility = XR_EYE_VISIBILITY_BOTH;
uiLayer.subImage.swapchain = vr_ui_swapchain;
uiLayer.subImage.imageRect.offset.x = 0;
uiLayer.subImage.imageRect.offset.y = 0;
uiLayer.subImage.imageRect.extent.width = vr_ui_width;
uiLayer.subImage.imageRect.extent.height = vr_ui_height;
uiLayer.subImage.imageArrayIndex = 0;
const float uiDistance = cv_vruidistance.value > 0 ? (float)cv_vruidistance.value / 70.0f : 1.5f;
const float uiWidth = cv_vruiscale.value > 0 ? (float)cv_vruiscale.value * 0.040f : 1.0f;
cachedUIPose = VR_MakeUIPose(uiDistance);
cachedUIPoseValid = true;
uiLayer.pose = cachedUIPose;
uiLayer.size.width = uiWidth;
uiLayer.size.height = uiWidth * ((float)vr_ui_height / (float)vr_ui_width);
layers[layerCount++] = (const XrCompositionLayerBaseHeader*)&uiLayer;
uiSwapchainAcquired = false;
}
XrFrameEndInfo frameEndInfo = {XR_TYPE_FRAME_END_INFO};
frameEndInfo.next = NULL;
frameEndInfo.displayTime = frameState.predictedDisplayTime;
frameEndInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
frameEndInfo.layerCount = layerCount;
frameEndInfo.layers = layerCount > 0 ? layers : NULL;
XrResult res = xrEndFrame(vr_session, &frameEndInfo);
if (XR_FAILED(res)) {
static int endFrameErrorCount = 0;
if (endFrameErrorCount < 5) {
CONS_Printf("VR: xrEndFrame failed (XrResult=%d)\n", (int)res);
endFrameErrorCount++;
}
}
vr_frame_begun = false;
vr_render_pass = VR_PASS_NONE;
vr_drawing_ui = false;
}
#endif // HAVE_VR

42
src/vr/vr_render.h Normal file
View file

@ -0,0 +1,42 @@
#ifndef __VR_RENDER_H__
#define __VR_RENDER_H__
#ifdef HAVE_VR
#include "vr_main.h"
#ifdef __cplusplus
extern "C" {
#endif
extern XrSwapchain vr_swapchains[2];
extern uint32_t vr_swapchain_lengths[2];
extern XrSwapchainImageOpenGLKHR* vr_swapchain_images[2];
extern uint32_t* vr_framebuffers[2];
extern uint32_t* vr_depthbuffers[2];
extern XrSwapchain vr_ui_swapchain;
extern uint32_t vr_ui_swapchain_length;
extern XrSwapchainImageOpenGLKHR* vr_ui_swapchain_images;
extern uint32_t* vr_ui_framebuffers;
extern uint32_t vr_ui_width;
extern uint32_t vr_ui_height;
boolean VR_InitSwapchains(void);
void VR_DestroySwapchains(void);
// Frame Loop
boolean VR_BeginFrame(void);
boolean VR_SetEye(int eye);
void VR_ReleaseEye(int eye);
void VR_BindDefaultFramebuffer(void);
void VR_BeginInEyeUI(void);
boolean VR_MirrorEyeToDefaultFramebuffer(int width, int height);
boolean VR_BindUISwapchain(void);
void VR_EndFrame(void);
#ifdef __cplusplus
}
#endif
#endif // HAVE_VR
#endif // __VR_RENDER_H__

View file

@ -125,7 +125,7 @@ wadfile_t *wadfiles[MAX_WADFILES]; // 0 to numwadfiles-1 are valid
// If not done on a Mac then open wad files
// can prevent removable media they are on from
// being ejected
void W_Shutdown(void)
DESTRUCTOR static void W_Shutdown(void)
{
lumpnum_map_cleanup(&lumpnumcache);

View file

@ -152,8 +152,6 @@ extern wadfile_t *wadfiles[MAX_WADFILES];
// =========================================================================
void W_Shutdown(void);
// Opens a WAD file. Returns the FILE * handle for the file, or NULL if not found or could not be opened
FILE *W_OpenWadFile(const char **filename, boolean useerrors);
// Load and add a wadfile to the active wad files, returns numbers of lumps, INT16_MAX on error

View file

@ -547,9 +547,12 @@ void Y_IntermissionDrawer(void)
UINT8 *colormap = R_GetTranslationColormap(*data.character[i], *data.color[i], GTC_CACHE);
patch_t *facerank;
facerank = faceprefix[*data.character[i]][FACE_RANK];
boolean hires = K_UseHighResPortraits() && !manyplayers16 && !displayitemrolls;
facerank = faceprefix[*data.character[i]][hires ? FACE_WANTED : FACE_RANK];
SINT8 hiresoffsetx = hires ? 8: 0;
SINT8 hiresoffsety = hires ? 2: 0;
fixed_t scale = FRACUNIT;
fixed_t scale = hires ? FRACUNIT / 2 : FRACUNIT;
if (manyplayers16)
{
@ -560,8 +563,10 @@ void Y_IntermissionDrawer(void)
else
{
INT32 xoffs, yoffs;
INT16 leftoffset = hires ? facerank->leftoffset / 2 : facerank->leftoffset;
INT16 topoffset = hires ? facerank->topoffset / 2 :facerank->topoffset;
scale = (displayitemrolls) ? FRACUNIT/2 : FRACUNIT;
scale = (hires || displayitemrolls) ? FRACUNIT/2 : FRACUNIT;
xoffs = FixedMul(16, scale);
yoffs = FixedMul(4, scale);
@ -569,8 +574,8 @@ void Y_IntermissionDrawer(void)
if (displayitemrolls)
{
V_DrawFixedPatch(
((x+11-xscroll_px)*FRACUNIT) + ((facerank->leftoffset) * scale),
((y+1)*FRACUNIT) + ((facerank->topoffset) * scale),
((x+11-xscroll_px)*FRACUNIT) + ((leftoffset) * scale),
((y+1)*FRACUNIT) + ((topoffset) * scale),
scale,
0,
facerank,
@ -587,7 +592,7 @@ void Y_IntermissionDrawer(void)
}
else
{
V_DrawFixedPatch((x+xoffs)<<FRACBITS, ((y-yoffs)<<FRACBITS), scale, 0, facerank, colormap);
V_DrawFixedPatch((x+xoffs+hiresoffsetx)<<FRACBITS, ((y-yoffs- +hiresoffsety)<<FRACBITS), scale, 0, facerank, colormap);
}
}
}