Small spectator fixes

- Switch between Follow modes (1st and 3rd person) using the X button
- Switch back to the "Spectate" mode using the A button when in follow mode
- 1st Person follow mode is on a virtual screen (to avoid nausea)
- Slider for haptic intensity: affects both controller and bhaptics level
- Move 3rd person Spectator and Death Cam closer to player's viewpoint
This commit is contained in:
Simon 2022-03-05 19:26:39 +00:00
parent 9426f8605b
commit e4d49b44e6
12 changed files with 133 additions and 39 deletions

View File

@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.drbeef.ioq3quest"
android:installLocation="preferExternal"
android:versionCode="25"
android:versionName="0.18.0">
android:versionCode="26"
android:versionName="0.19.0">
<uses-feature android:name="android.hardware.vr.headtracking" android:version="1" android:required="true" />
<uses-feature android:glEsVersion="0x00030001" />
<!-- <uses-feature android:name="oculus.software.overlay_keyboard" android:required="false"/>-->

View File

@ -2725,33 +2725,43 @@ void CG_DrawActive( stereoFrame_t stereoView ) {
vec3_t baseOrg;
VectorCopy( cg.refdef.vieworg, baseOrg );
float heightOffset = 0.0f;
float worldscale = trap_Cvar_VariableValue("vr_worldscale");
if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW))
if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_THIRDPERSON))
{
worldscale *= SPECTATOR_WORLDSCALE_MULTIPLIER;
//Just move camera down about 20cm
heightOffset = -0.2f;
}
else if (( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) &&
( cg.snap->ps.pm_type != PM_INTERMISSION ))
{
worldscale *= DEATH_WORLDSCALE_MULTIPLIER;
//Just move camera down about 50cm
heightOffset = -0.5f;
}
float ipd = trap_Cvar_VariableValue("r_stereoSeparation") / 1000.0f;
float separation = worldscale * (ipd / 2) * (stereoView == STEREO_LEFT ? -1.0f : 1.0f);
if (cgs.localServer) {
cg.refdef.vieworg[2] -= PLAYER_HEIGHT;
cg.refdef.vieworg[2] += vr->hmdposition[1] * worldscale;
}
else
if (cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_FIRSTPERSON)
{
//Do nothing to view height if we are following in first person
}
else
{
cg.refdef.vieworg[2] -= PLAYER_HEIGHT;
cg.refdef.vieworg[2] += (vr->hmdposition[1] + heightOffset) * worldscale;
}
if (!cgs.localServer)
{
//If connected to external server, allow some amount of faked positional tracking
cg.refdef.vieworg[2] -= PLAYER_HEIGHT;
cg.refdef.vieworg[2] += vr->hmdposition[1] * worldscale;
//If connected to a remote server, allow some amount of faked positional tracking
if (cg.snap->ps.stats[STAT_HEALTH] > 0 &&
//Don't use fake positional if following another player - this is handled in the
//VR third person code
!( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW)))
!( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_THIRDPERSON)))
{
vec3_t pos, hmdposition, vieworg;
VectorClear(pos);

View File

@ -77,7 +77,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define NUM_CROSSHAIRS 10
//multiplying size you go to when dead looking down on the match
#define DEATH_WORLDSCALE_MULTIPLIER 40
#define DEATH_WORLDSCALE_MULTIPLIER 30
#define SPECTATOR_WORLDSCALE_MULTIPLIER 10
#define PLAYER_HEIGHT 48

View File

@ -2030,7 +2030,7 @@ static void CG_PlayerSprites( centity_t *cent ) {
//Put a sprite over the followed player's head
if ( cent->currentState.number == cg.snap->ps.clientNum &&
cg.renderingThirdPerson &&
cg.snap->ps.pm_flags & PMF_FOLLOW) {
(cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_THIRDPERSON)) {
CG_PlayerFloatSprite( cent, cgs.media.friendShader );
return;
}

View File

@ -227,16 +227,15 @@ CG_OffsetVRThirdPersonView
static void CG_OffsetVRThirdPersonView( void ) {
float scale = 1.0f;
if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW))
if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_THIRDPERSON))
{
scale *= SPECTATOR_WORLDSCALE_MULTIPLIER;
//Check to see if the followed player has moved far enough away to mean we should update our location
vec3_t currentOrigin;
VectorCopy(cg.refdef.vieworg, currentOrigin);
VectorSubtract(currentOrigin, cg.vr_vieworigin, currentOrigin);
currentOrigin[2] = 0;
if (VectorLength(currentOrigin) > 400)
vec3_t current;
VectorSubtract(cg.refdef.vieworg, cg.vr_vieworigin, current);
current[2] = 0;
if (VectorLength(current) > 400)
{
VectorCopy(cg.refdef.vieworg, cg.vr_vieworigin);
@ -665,7 +664,7 @@ static int CG_CalcViewValues( stereoFrame_t stereoView ) {
trap_Cvar_Set( "vr_noSkybox", (((ps->stats[STAT_HEALTH] <= 0) &&
( ps->pm_type != PM_INTERMISSION )) ||
cg.demoPlayback ||
(cg.snap->ps.pm_flags & PMF_FOLLOW) ? "1" : "0" ));
(cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_THIRDPERSON) ? "1" : "0" ));
// intermission view
static float hmdYaw = 0;
@ -722,7 +721,7 @@ static int CG_CalcViewValues( stereoFrame_t stereoView ) {
if (( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) &&
( cg.predictedPlayerState.pm_type != PM_INTERMISSION ) ||
( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW)))
( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_THIRDPERSON)))
{
//If dead, or spectating, view the map from above
CG_OffsetVRThirdPersonView();
@ -797,7 +796,7 @@ static int CG_CalcViewValues( stereoFrame_t stereoView ) {
angles[ROLL] = vr->hmdorientation[ROLL];
AnglesToAxis( angles, cg.refdef.viewaxis );
}
else if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW))
else if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_THIRDPERSON))
{
//If we're following someone,
vec3_t angles;
@ -805,7 +804,7 @@ static int CG_CalcViewValues( stereoFrame_t stereoView ) {
angles[YAW] = vr->clientviewangles[YAW];
AnglesToAxis(angles, cg.refdef.viewaxis);
}
else
else if (!(cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_FIRSTPERSON))
{
//We are connected to a multiplayer server, so make the appropriate adjustment to the view
//angles as we send orientation to the server that includes the weapon angles
@ -815,6 +814,10 @@ static int CG_CalcViewValues( stereoFrame_t stereoView ) {
angles[YAW] = deltaYaw + vr->clientviewangles[YAW];
AnglesToAxis(angles, cg.refdef.viewaxis);
}
else
{
AnglesToAxis(cg.refdefViewAngles, cg.refdef.viewaxis);
}
} else {
if (vr->weapon_zoomed) {
vec3_t angles;
@ -823,7 +826,7 @@ static int CG_CalcViewValues( stereoFrame_t stereoView ) {
angles[YAW] = (cg.refdefViewAngles[YAW] - vr->hmdorientation[YAW]) + vr->weaponangles[YAW];
AnglesToAxis(angles, cg.refdef.viewaxis);
}
else if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW))
else if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_THIRDPERSON))
{
//If we're following someone,
vec3_t angles;
@ -954,7 +957,7 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo
// decide on third person view
cg.renderingThirdPerson = cg.predictedPlayerState.pm_type == PM_SPECTATOR ||
cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ||
cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_THIRDPERSON) ||
cg_thirdPerson.integer;
// build cg.refdef

View File

@ -1714,6 +1714,7 @@ void CG_AddViewWeapon( playerState_t *ps ) {
//Scale / Move gun etc
float scale = 1.0f;
if (!(cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_FIRSTPERSON))
{
char cvar_name[64];
Com_sprintf(cvar_name, sizeof(cvar_name), "vr_weapon_adjustment_%i", ps->weapon);

View File

@ -55,8 +55,9 @@ VR OPTIONS MENU
#define ID_SENDROLL 141
#define ID_LASERSIGHT 142
#define ID_GORE 143
#define ID_HAPTICINTENSITY 144
#define ID_BACK 144
#define ID_BACK 145
#define NUM_HUDDEPTH 6
#define NUM_DIRECTIONMODE 2
@ -86,6 +87,7 @@ typedef struct {
menuslider_s hudyoffset;
menuradiobutton_s sendroll;
menuradiobutton_s lasersight;
menuslider_s hapticintensity;
menulist_s gore;
menubitmap_s back;
@ -219,6 +221,10 @@ static void VR_Event( void* ptr, int notification ) {
trap_Cvar_SetValue( "vr_lasersight", s_VR.lasersight.curvalue);
break;
case ID_HAPTICINTENSITY:
trap_Cvar_SetValue( "vr_hapticIntensity", s_VR.hapticintensity.curvalue);
break;
case ID_GORE: {
switch ((int)s_VR.gore.curvalue) {
case 0:
@ -478,6 +484,17 @@ static void VR_MenuInit( void ) {
s_VR.lasersight.generic.x = VR_X_POS;
s_VR.lasersight.generic.y = y;
y += BIGCHAR_HEIGHT;
s_VR.hapticintensity.generic.type = MTYPE_SLIDER;
s_VR.hapticintensity.generic.x = VR_X_POS;
s_VR.hapticintensity.generic.y = y;
s_VR.hapticintensity.generic.flags = QMF_PULSEIFFOCUS|QMF_SMALLFONT;
s_VR.hapticintensity.generic.name = "Haptic Intensity:";
s_VR.hapticintensity.generic.id = ID_HAPTICINTENSITY;
s_VR.hapticintensity.generic.callback = VR_Event;
s_VR.hapticintensity.minvalue = 0;
s_VR.hapticintensity.maxvalue = 1.0;
y += BIGCHAR_HEIGHT + 10;
s_VR.gore.generic.type = MTYPE_SPINCONTROL;
s_VR.gore.generic.flags = QMF_PULSEIFFOCUS|QMF_SMALLFONT;
@ -518,6 +535,7 @@ static void VR_MenuInit( void ) {
Menu_AddItem( &s_VR.menu, &s_VR.hudyoffset );
Menu_AddItem( &s_VR.menu, &s_VR.sendroll );
Menu_AddItem( &s_VR.menu, &s_VR.lasersight );
Menu_AddItem( &s_VR.menu, &s_VR.hapticintensity );
Menu_AddItem( &s_VR.menu, &s_VR.gore );
Menu_AddItem( &s_VR.menu, &s_VR.back );

View File

@ -33,6 +33,7 @@ cvar_t *vr_rollWhenHit = NULL;
cvar_t *vr_hudYOffset = NULL;
cvar_t *vr_sendRollToServer = NULL;
cvar_t *vr_lasersight = NULL;
cvar_t *vr_hapticIntensity = NULL;
engine_t* VR_Init( ovrJava java )
{
@ -67,6 +68,7 @@ void VR_InitCvars( void )
vr_hudYOffset = Cvar_Get ("vr_hudYOffset", "0", CVAR_ARCHIVE);
vr_sendRollToServer = Cvar_Get ("vr_sendRollToServer", "1", CVAR_ARCHIVE);
vr_lasersight = Cvar_Get ("vr_lasersight", "0", CVAR_ARCHIVE);
vr_hapticIntensity = Cvar_Get ("vr_hapticIntensity", "1.0", CVAR_ARCHIVE);
// Values are: scale,right,up,forward,pitch,yaw,roll
// VALUES PROVIDED BY SkillFur - Thank-you!

View File

@ -5,6 +5,11 @@
#define NUM_WEAPON_SAMPLES 10
typedef enum {
VRFM_THIRDPERSON,
VRFM_FIRSTPERSON
} followMode_t;
typedef struct {
qboolean weapon_stabilised;
qboolean weapon_zoomed;
@ -13,6 +18,7 @@ typedef struct {
qboolean right_handed;
qboolean virtual_screen;
qboolean local_server; // used in bg_pmove.c
followMode_t follow_mode;
int realign; // used to realign the fake 6DoF playspace in a multiplayer game

View File

@ -71,6 +71,7 @@ extern cvar_t *vr_heightAdjust;
extern cvar_t *vr_twoHandedWeapons;
extern cvar_t *vr_refreshrate;
extern cvar_t *vr_weaponScope;
extern cvar_t *vr_hapticIntensity;
jclass callbackClass;
jmethodID android_haptic_event;
@ -206,7 +207,7 @@ void VR_Vibrate( int duration, int chan, float intensity )
return;
vibration_channel_duration[channel-1] = duration;
vibration_channel_intensity[channel-1] = intensity;
vibration_channel_intensity[channel-1] = intensity * vr_hapticIntensity->value;
}
}
}
@ -268,9 +269,14 @@ static void IN_SendButtonAction(const char* action, qboolean pressed)
void VR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight )
{
if (vr_hapticIntensity->value == 0.0f)
{
return;
}
engine_t* engine = VR_GetEngine();
jstring StringArg1 = (*(engine->java.Env))->NewStringUTF(engine->java.Env, event);
(*(engine->java.Env))->CallVoidMethod(engine->java.Env, engine->java.ActivityObject, android_haptic_event, StringArg1, position, flags, intensity, angle, yHeight);
(*(engine->java.Env))->CallVoidMethod(engine->java.Env, engine->java.ActivityObject, android_haptic_event, StringArg1, position, flags, intensity * vr_hapticIntensity->value, angle, yHeight);
//Controller Haptic Support
int weaponFireChannel = vr.weapon_stabilised ? 3 : (vr_righthanded->integer ? 2 : 1);
@ -714,14 +720,21 @@ static void IN_VRButtonsChanged( qboolean isRightController, uint32_t buttons )
//Jump
if ((buttons & ovrButton_A) && !(controller->buttons & ovrButton_A))
{
if (IN_GetButtonAction("A", action))
if (cl.snap.ps.pm_flags & PMF_FOLLOW)
{
IN_SendButtonAction(action, qtrue);
Cbuf_AddText("cmd team spectator\n");
}
else
{
if (IN_GetButtonAction("A", action))
{
IN_SendButtonAction(action, qtrue);
}
}
}
else if (!(buttons & ovrButton_A) && (controller->buttons & ovrButton_A))
{
if (IN_GetButtonAction("A", action))
if (IN_GetButtonAction("A", action) && !(cl.snap.ps.pm_flags & PMF_FOLLOW))
{
IN_SendButtonAction(action, qfalse);
}
@ -741,17 +754,28 @@ static void IN_VRButtonsChanged( qboolean isRightController, uint32_t buttons )
}
//X default is "use item"
if ((buttons & ovrButton_X) && !(controller->buttons & ovrButton_X)) {
if (IN_GetButtonAction("X", action))
if ((buttons & ovrButton_X) && !(controller->buttons & ovrButton_X))
{
if (cl.snap.ps.pm_flags & PMF_FOLLOW)
{
IN_SendButtonAction(action, qtrue);
//Switch follow mode
vr.follow_mode = 1 - vr.follow_mode;
}
} else if (!(buttons & ovrButton_X) && (controller->buttons & ovrButton_X)) {
if (IN_GetButtonAction("X", action))
else
{
if (IN_GetButtonAction("X", action))
{
IN_SendButtonAction(action, qtrue);
}
}
}
else if (!(buttons & ovrButton_X) && (controller->buttons & ovrButton_X))
{
if (IN_GetButtonAction("X", action) && !(cl.snap.ps.pm_flags & PMF_FOLLOW))
{
IN_SendButtonAction(action, qfalse);
}
}
}
//Y default is Gesture
if ((buttons & ovrButton_Y) && !(controller->buttons & ovrButton_Y)) {

View File

@ -268,7 +268,8 @@ void VR_DrawFrame( engine_t* engine ) {
int eyeW, eyeH;
VR_GetResolution(engine, &eyeW, &eyeH);
if (VR_useScreenLayer())
if (VR_useScreenLayer() ||
(cl.snap.ps.pm_flags & PMF_FOLLOW && vr.follow_mode == VRFM_FIRSTPERSON))
{
static ovrLayer_Union2 cylinderLayer;
memset( &cylinderLayer, 0, sizeof( ovrLayer_Union2 ) );

29
android/deploy.bat Normal file
View File

@ -0,0 +1,29 @@
@echo off
setlocal
set BUILD_TYPE=release
set VERSION=0.19.0
set adb="%ANDROID_SDK_ROOT%\platform-tools\adb.exe"
pushd android
set PACKAGE_NAME=com.drbeef.ioq3quest
set ANDROID_STORAGE_LOCATION=/sdcard/ioquake3quest/
set APK_LOCATION=.\app\build\outputs\apk\%BUILD_TYPE%\ioq3quest-%BUILD_TYPE%-%VERSION%.apk
%adb% install -r %APK_LOCATION%
if %ERRORLEVEL% NEQ 0 (
%adb% uninstall %PACKAGE_NAME%
%adb% install %APK_LOCATION%
if %ERRORLEVEL% NEQ 0 (
popd
popd
echo "Failed to install apk."
exit /b 1
)
)
:END
endlocal