diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0ecb97bd..b9cfbf69 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="26" + android:versionName="0.19.0"> diff --git a/android/app/src/main/cpp/code/cgame/cg_draw.c b/android/app/src/main/cpp/code/cgame/cg_draw.c index e615f4bd..a9fa57ad 100644 --- a/android/app/src/main/cpp/code/cgame/cg_draw.c +++ b/android/app/src/main/cpp/code/cgame/cg_draw.c @@ -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); diff --git a/android/app/src/main/cpp/code/cgame/cg_local.h b/android/app/src/main/cpp/code/cgame/cg_local.h index faa39c35..adf2a4ff 100644 --- a/android/app/src/main/cpp/code/cgame/cg_local.h +++ b/android/app/src/main/cpp/code/cgame/cg_local.h @@ -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 diff --git a/android/app/src/main/cpp/code/cgame/cg_players.c b/android/app/src/main/cpp/code/cgame/cg_players.c index 5dec2ac6..df56e50d 100644 --- a/android/app/src/main/cpp/code/cgame/cg_players.c +++ b/android/app/src/main/cpp/code/cgame/cg_players.c @@ -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; } diff --git a/android/app/src/main/cpp/code/cgame/cg_view.c b/android/app/src/main/cpp/code/cgame/cg_view.c index fd995102..a73f7b43 100644 --- a/android/app/src/main/cpp/code/cgame/cg_view.c +++ b/android/app/src/main/cpp/code/cgame/cg_view.c @@ -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 diff --git a/android/app/src/main/cpp/code/cgame/cg_weapons.c b/android/app/src/main/cpp/code/cgame/cg_weapons.c index 3bc12bc5..668017fc 100644 --- a/android/app/src/main/cpp/code/cgame/cg_weapons.c +++ b/android/app/src/main/cpp/code/cgame/cg_weapons.c @@ -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); diff --git a/android/app/src/main/cpp/code/q3_ui/ui_vr.c b/android/app/src/main/cpp/code/q3_ui/ui_vr.c index e3a4abc4..8e3823ad 100644 --- a/android/app/src/main/cpp/code/q3_ui/ui_vr.c +++ b/android/app/src/main/cpp/code/q3_ui/ui_vr.c @@ -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 ); diff --git a/android/app/src/main/cpp/code/vr/vr_base.c b/android/app/src/main/cpp/code/vr/vr_base.c index 504d5aba..be87c966 100644 --- a/android/app/src/main/cpp/code/vr/vr_base.c +++ b/android/app/src/main/cpp/code/vr/vr_base.c @@ -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! diff --git a/android/app/src/main/cpp/code/vr/vr_clientinfo.h b/android/app/src/main/cpp/code/vr/vr_clientinfo.h index 85f2d49a..c442acb9 100644 --- a/android/app/src/main/cpp/code/vr/vr_clientinfo.h +++ b/android/app/src/main/cpp/code/vr/vr_clientinfo.h @@ -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 diff --git a/android/app/src/main/cpp/code/vr/vr_input.c b/android/app/src/main/cpp/code/vr/vr_input.c index 190f02ce..d768166d 100644 --- a/android/app/src/main/cpp/code/vr/vr_input.c +++ b/android/app/src/main/cpp/code/vr/vr_input.c @@ -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)) { diff --git a/android/app/src/main/cpp/code/vr/vr_renderer.c b/android/app/src/main/cpp/code/vr/vr_renderer.c index 95973596..d3985cc3 100644 --- a/android/app/src/main/cpp/code/vr/vr_renderer.c +++ b/android/app/src/main/cpp/code/vr/vr_renderer.c @@ -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 ) ); diff --git a/android/deploy.bat b/android/deploy.bat new file mode 100644 index 00000000..88373285 --- /dev/null +++ b/android/deploy.bat @@ -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