From 0a97fa4b4ff2c7473d4381ac8e563cdb38aba790 Mon Sep 17 00:00:00 2001 From: Grant Bagwell Date: Sat, 1 May 2021 07:56:52 +0200 Subject: [PATCH 1/8] Start of bHaptics work --- .../Android/jni/RTCWVR/RTCWVR_SurfaceView.c | 37 +++++++ Projects/Android/jni/RTCWVR/VrInputDefault.c | 2 + Projects/Android/libs/hapticsservice.aar | Bin 0 -> 28012 bytes .../drbeef/rtcwquest/GLES3JNIActivity.java | 93 ++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 Projects/Android/libs/hapticsservice.aar diff --git a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c index 8683ec1..3e323e5 100644 --- a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c +++ b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c @@ -908,6 +908,43 @@ void RTCWVR_Vibrate( int duration, int channel, float intensity ) vibration_channel_intensity[channel] = intensity; } +void jni_haptic_event(const char* event, int position, int flags, int intensity, float angle, float yHeight); +void jni_haptic_updateevent(const char* event, int intensity, float angle); +void jni_haptic_stopevent(const char* event); +void jni_haptic_endframe(); +void jni_haptic_enable(); +void jni_haptic_disable(); + +void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ) +{ + jni_haptic_event(event, position, flags, intensity, angle, yHeight); +} + +void RTCWVR_HapticUpdateEvent(const char* event, int intensity, float angle ) +{ + jni_haptic_updateevent(event, intensity, angle); +} + +void RTCWVR_HapticEndFrame() +{ + jni_haptic_endframe(); +} + +void RTCWVR_HapticStopEvent(const char* event) +{ + jni_haptic_stopevent(event); +} + +void RTCWVR_HapticEnable() +{ + jni_haptic_enable(); +} + +void RTCWVR_HapticDisable() +{ + jni_haptic_disable(); +} + void RTCWVR_GetMove(float *forward, float *side, float *pos_forward, float *pos_side, float *up, float *yaw, float *pitch, float *roll) { diff --git a/Projects/Android/jni/RTCWVR/VrInputDefault.c b/Projects/Android/jni/RTCWVR/VrInputDefault.c index 74d6542..2889044 100644 --- a/Projects/Android/jni/RTCWVR/VrInputDefault.c +++ b/Projects/Android/jni/RTCWVR/VrInputDefault.c @@ -25,6 +25,8 @@ Authors : Simon Brown void SV_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, int capsule ); +void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ); + void HandleInput_Default( ovrInputStateTrackedRemote *pDominantTrackedRemoteNew, ovrInputStateTrackedRemote *pDominantTrackedRemoteOld, ovrTracking* pDominantTracking, ovrInputStateTrackedRemote *pOffTrackedRemoteNew, ovrInputStateTrackedRemote *pOffTrackedRemoteOld, ovrTracking* pOffTracking, int domButton1, int domButton2, int offButton1, int offButton2 ) diff --git a/Projects/Android/libs/hapticsservice.aar b/Projects/Android/libs/hapticsservice.aar new file mode 100644 index 0000000000000000000000000000000000000000..d07a795e177dc64644b2b60f1e712d8717281e93 GIT binary patch literal 28012 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 Date: Mon, 3 May 2021 21:58:50 +0200 Subject: [PATCH 2/8] JNI side finished --- Projects/Android/AndroidManifest.xml | 9 +- Projects/Android/build.gradle | 9 +- .../Android/jni/RTCWVR/RTCWVR_SurfaceView.c | 95 +++++++++++++- Projects/Android/jni/RTCWVR/VrCommon.h | 6 + .../Android/jni/rtcw/src/client/cl_cgame.c | 7 +- .../Android/jni/rtcw/src/server/sv_game.c | 6 + Projects/Android/libs/haptic_service.aar | Bin 0 -> 10085 bytes Projects/Android/libs/hapticsservice.aar | Bin 28012 -> 0 bytes .../drbeef/rtcwquest/GLES3JNIActivity.java | 117 ++++++++++-------- 9 files changed, 186 insertions(+), 63 deletions(-) create mode 100644 Projects/Android/libs/haptic_service.aar delete mode 100644 Projects/Android/libs/hapticsservice.aar diff --git a/Projects/Android/AndroidManifest.xml b/Projects/Android/AndroidManifest.xml index 3bfadba..42196b3 100644 --- a/Projects/Android/AndroidManifest.xml +++ b/Projects/Android/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="48" + android:versionName="1.2.0" android:installLocation="auto" > @@ -10,12 +10,13 @@ - + - + + diff --git a/Projects/Android/build.gradle b/Projects/Android/build.gradle index 75c4eb8..cdd6c61 100644 --- a/Projects/Android/build.gradle +++ b/Projects/Android/build.gradle @@ -23,7 +23,7 @@ android { abiFilters 'armeabi-v7a' } } - minSdkVersion 24 + minSdkVersion 26 targetSdkVersion 26 } @@ -40,13 +40,14 @@ android { sourceCompatibility = '1.8' targetCompatibility = '1.8' } - compileSdkVersion = 24 + compileSdkVersion = 26 buildToolsVersion = '29.0.1' } dependencies { - implementation "com.android.support:support-compat:24.2.0" - implementation "com.android.support:support-core-utils:24.2.0" + implementation "com.android.support:support-compat:26.1.0" + implementation "com.android.support:support-core-utils:26.1.0" + implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) } repositories { diff --git a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c index 3e323e5..5679caa 100644 --- a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c +++ b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c @@ -1882,8 +1882,14 @@ Activity lifecycle jmethodID android_shutdown; +jmethodID android_haptic_event; +jmethodID android_haptic_updateevent; +jmethodID android_haptic_stopevent; +jmethodID android_haptic_endframe; +jmethodID android_haptic_enable; +jmethodID android_haptic_disable; static JavaVM *jVM; -static jobject shutdownCallbackObj=0; +static jobject jniCallbackObj=0; void jni_shutdown() { @@ -1894,7 +1900,88 @@ void jni_shutdown() { (*jVM)->AttachCurrentThread(jVM,&env, NULL); } - return (*env)->CallVoidMethod(env, shutdownCallbackObj, android_shutdown); + return (*env)->CallVoidMethod(env, jniCallbackObj, android_shutdown); +} + +void jni_haptic_event(const char* event, int position, int flags, int intensity, float angle, float yHeight) +{ + JNIEnv *env; + jobject tmp; + if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0) + { + (*jVM)->AttachCurrentThread(jVM,&env, NULL); + } + + jstring StringArg1 = (*env)->NewStringUTF(env, event); + + return (*env)->CallVoidMethod(env, jniCallbackObj, android_haptic_event, StringArg1, position, flags, intensity, angle, yHeight); +} + +void jni_haptic_updateevent(const char* event, int intensity, float angle) +{ + JNIEnv *env; + jobject tmp; + if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0) + { + (*jVM)->AttachCurrentThread(jVM,&env, NULL); + } + + jstring StringArg1 = (*env)->NewStringUTF(env, event); + + return (*env)->CallVoidMethod(env, jniCallbackObj, android_haptic_updateevent, StringArg1, intensity, angle); +} + +void jni_haptic_stopevent(const char* event) +{ + ALOGV("Calling: jni_haptic_stopevent"); + JNIEnv *env; + jobject tmp; + if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0) + { + (*jVM)->AttachCurrentThread(jVM,&env, NULL); + } + + jstring StringArg1 = (*env)->NewStringUTF(env, event); + + return (*env)->CallVoidMethod(env, jniCallbackObj, android_haptic_stopevent, StringArg1); +} + +void jni_haptic_endframe() +{ + JNIEnv *env; + jobject tmp; + if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0) + { + (*jVM)->AttachCurrentThread(jVM,&env, NULL); + } + + return (*env)->CallVoidMethod(env, jniCallbackObj, android_haptic_endframe); +} + +void jni_haptic_enable() +{ + ALOGV("Calling: jni_haptic_enable"); + JNIEnv *env; + jobject tmp; + if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0) + { + (*jVM)->AttachCurrentThread(jVM,&env, NULL); + } + + return (*env)->CallVoidMethod(env, jniCallbackObj, android_haptic_enable); +} + +void jni_haptic_disable() +{ + ALOGV("Calling: jni_haptic_disable"); + JNIEnv *env; + jobject tmp; + if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0) + { + (*jVM)->AttachCurrentThread(jVM,&env, NULL); + } + + return (*env)->CallVoidMethod(env, jniCallbackObj, android_haptic_disable); } int JNI_OnLoad(JavaVM* vm, void* reserved) @@ -1990,8 +2077,8 @@ JNIEXPORT void JNICALL Java_com_drbeef_rtcwquest_GLES3JNILib_onStart( JNIEnv * e ALOGV( " GLES3JNILib::onStart()" ); - shutdownCallbackObj = (jobject)(*env)->NewGlobalRef(env, obj1); - jclass callbackClass = (*env)->GetObjectClass(env, shutdownCallbackObj); + jniCallbackObj = (jobject)(*env)->NewGlobalRef(env, obj1); + jclass callbackClass = (*env)->GetObjectClass(env, jniCallbackObj); android_shutdown = (*env)->GetMethodID(env,callbackClass,"shutdown","()V"); diff --git a/Projects/Android/jni/RTCWVR/VrCommon.h b/Projects/Android/jni/RTCWVR/VrCommon.h index 3164d2b..9ab7f7a 100644 --- a/Projects/Android/jni/RTCWVR/VrCommon.h +++ b/Projects/Android/jni/RTCWVR/VrCommon.h @@ -60,6 +60,12 @@ int GetRefresh(); qboolean RTCWVR_useScreenLayer(); void RTCWVR_GetScreenRes(int *width, int *height); void RTCWVR_Vibrate(int duration, int channel, float intensity ); +void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ); +void RTCWVR_HapticUpdateEvent(const char* event, int intensity, float angle ); +void RTCWVR_HapticEndFrame(); +void RTCWVR_HapticStopEvent(const char* event); +void RTCWVR_HapticEnable(); +void RTCWVR_HapticDisable(); qboolean RTCWVR_processMessageQueue(); void RTCWVR_FrameSetup(); void RTCWVR_setUseScreenLayer(qboolean use); diff --git a/Projects/Android/jni/rtcw/src/client/cl_cgame.c b/Projects/Android/jni/rtcw/src/client/cl_cgame.c index 903733c..52b8123 100644 --- a/Projects/Android/jni/rtcw/src/client/cl_cgame.c +++ b/Projects/Android/jni/rtcw/src/client/cl_cgame.c @@ -45,7 +45,12 @@ extern qboolean getCameraInfo( int camNum, int time, vec3_t *origin, vec3_t *ang extern void SV_SendMoveSpeedsToGame( int entnum, char *text ); extern qboolean SV_GetModelInfo( int clientNum, char *modelName, animModelInfo_t **modelInfo ); void RTCWVR_Vibrate(int duration, int channel, float intensity ); - +void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ); +void RTCWVR_HapticUpdateEvent(const char* event, int intensity, float angle ); +void RTCWVR_HapticEndFrame(); +void RTCWVR_HapticStopEvent(const char* event); +void RTCWVR_HapticEnable(); +void RTCWVR_HapticDisable(); /* ==================== diff --git a/Projects/Android/jni/rtcw/src/server/sv_game.c b/Projects/Android/jni/rtcw/src/server/sv_game.c index 951af6f..8eb0a6d 100644 --- a/Projects/Android/jni/rtcw/src/server/sv_game.c +++ b/Projects/Android/jni/rtcw/src/server/sv_game.c @@ -304,6 +304,12 @@ static int FloatAsInt( float f ) { } void RTCWVR_Vibrate(int duration, int channel, float intensity ); +void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ); +void RTCWVR_HapticUpdateEvent(const char* event, int intensity, float angle ); +void RTCWVR_HapticEndFrame(); +void RTCWVR_HapticStopEvent(const char* event); +void RTCWVR_HapticEnable(); +void RTCWVR_HapticDisable(); /* ==================== diff --git a/Projects/Android/libs/haptic_service.aar b/Projects/Android/libs/haptic_service.aar new file mode 100644 index 0000000000000000000000000000000000000000..9c2dfc7ee944862adf4b02dafa2ba54e751a4705 GIT binary patch literal 10085 zcmaKyV{j%w*QPVU#CD!InK+r)nb@{%+qP{d6Wg|J+jwFdJMUMuwYy*K?&<2jPgkFF zs=9ylkM2uO5)2#$1Oy5S1O$ZtUk3pJ{jU)O1n!^OIoO&y>pK|HIk`E3fyn(oV|W{!@M%BL`b^BWZmba}#4nCptH4E9{a!+Rq6d^`OLxyu>E^zBqS8KhU;u&-KLJUcx{Hb+v;PesF z>?F;gw#b2jb)l3v!a;i$XeNTqsjtIbD`TBn->Qgu1m$9!mL@}yWpqKz7u~TC-SHp0-(|k~=LsHM&A0v&uxLxejRq!wZRdr}px6Cnj^}l3 zIRP69qpWiFxDQs6w zTN>{Y&YA`eFN-!hJ4*^rn>Jb+7_3EOEfsWmIY~1GjIxHy&Pv!Xx;z1o3*A0()AJ#t z;8&$K2J2_uH{h5J=QwI)Rvar#DnyjCX0qN$h+4Xibgl9i=-`pt=pQt3WOn_slVd=< z*kt3Nrmm?G6@4JYY#JycHl*&eknxlV+ryo$)ULAZ@HAR-#9JJsViOJOu>{5ZLC5S}m!D2E_;CX<#DE#yjK z`XnRAgNywF`=JAdfU>;3QWL2Y#)p{rif?==gdU`4Yt(X@MaTONpLfHS@e*Q0F7=5` z`IR5B-y@6~{>`EEbt1MpJij=desS#>!pbTVGQ9jvDzCy^qm>KdaHCT0f13j?NrxYQ zS_y3fw3kLI?+MdSlQm_a&DtZX;_SS}RwARFTH-@MckC<{GN`)gzZ|+lVad`7a)bycomf^=(o!#e#}#|O5PAR2L88{6=6EHR-G%}w4%G+=>FRsY z^ZP-u=q(X&^+SXl2;~n6E6Pk&J*R->)2YU7?T%cUBF-I(i!6>Og@U8uP$Tr@&`*E= z$_}Y+$GDY#zpPc_M@j~8W9&wHq&+^IUbLT{%(`2#aX*M}FXWey@QF0?jE$^5ah$0- z2zsuDlG8Rbcx1$X*(qF5u?&U> z>Qq*^VTKXe*5T5ZoECL#XN%%WbI_0ckuxP2?m+LFux^x{a@#^O@G%dJ0cT@VKCWg1 zDOr`z5N$3pHJ;A1(QaKH`yo~ zK>MUsC{B%JxuhS@D9U@RWw?=6wsA{AN3^f+WQAboYY9S!Ku+xDK&g8cV=OyQAtw4g z&VC14sU3QQZ%11mMpXTh#$pFwx{mQ!E7Vph6~$X&PsV(2ceaeS@pRNW25LYx4@IWP zs5*?=q)wJp*OcbGQd&%!LPX(z%nqTnKCOL}LPova5*amo1@%}PE_VtqXRz!)#mXb2 z%o|sbf!vgdK$iqRS`iYe^|zEe2weJjL9E%BXLvOvW|AbL4SsGU;@5l-;!$1sC>xls z*8!V}kj3&yFu~woZ72hWC6?*Tr7_Fl;G+eCh(kEZDK=qw+yE97yooOvnPkd7qZ#%i zm$1_`>^L(ivU)>Dp#_QFlIaJ>_U12GOb|%H{O)PPN7iD2J7>j14eFfPOBzay>%@L2 zPw2O|zi>`niVs52vu^fYby&H7eoXN$#WZD2;T}{PFFG@{aP%%@sCUrw9x^`>0*%&{ z5s9{JMP|wM0vnX2;yz1EJZV!~Uu%kaE)&Lz;Kb|MnN?|XswC&e!ID#*P;lD|`HHYt z#}KTGBijk-JgF1V_SwbxWZqRNS9MC`QQn#$Pa23syjnooieH=NY)jZh|2{GCT~*I( zyU^u^RpruRP4U*fX(06p-x@Zf|G4mQ-}&j$B16!3b>3K!1n1EI8cQJlXi}5*!0d0S zB%sl5nIM+9UL3mD@K#(h=k?Ovb)@Cgp{Qj`>V(L(q~)wO#O101ZFoK8S&0|i0rm0} z5+In3JJfcBFFoODrJ?O1Pvy7bjDh}Q6ck=qnrU$Ls>a%v+Vo|d*lE@>QEn!WPG#o> z^mA0i`xUB5r>gW3ncaRGO3lXCpc6p%bMKY}O?Pj@+)}f_vt2Aew}2@#U5+7iJHe$9 zw?8HS9Onv6bHVh$F7>E!&mz?=N@C-Ar)N*pniao|P=(R5*z&ww<`_;;IR@`AT8E{> zYjl2hy^hP zW;-!NG3gY8gjO)bxv!zFM#$dI)gw45yz(h9zt|_iFIp&aAH7nL zx^PI{#=C(cX`0D^Tb{-dif-k6o^K$(L|R`iTRbdb9(b&CIEo^*`}P4l9j(j=J?zs0$=Z2bCHu2^-z-@?O>B@8QkwfXjg|i_A3I z&Uk;kAq=+(2mekC?=aTCK}$6gec1NzmGS_js{FQFHSrSRWw)*nP%r(_Q`zaX6SQ}u z>ovE*-*7Q*fW3uZoTU-HX*Zx5mUvm&HEV4FxN`5C3l1X@M8v^Z=c&M(HzC$vhd=A! zh4txRUKfrw^-lM2xAc~mV(3tCup+$-$hex}1uq&QhGW2V{NN-!Nbao~lh${CZcwl_ znX3d7$m5kGIPyqI*E=m_nzY=?U-8^K5JO&XigOX0E2F*#Q__DWCNCy*|Am8ETzMez3)mkDsgL90wBeSV~zs)YLx1;OiOX&*8Rd;-WQ>x z&@ec)ddBU`^+pR&HX(XRpTXUQcJ-itF7<^?f&q}I`p%+|4fK7fu>)YlozeXz>H`J) zwZdL!P*YkbP(P8<0$qtpY-UkSL}r4a1ucF615JXS#`CD%*xWftd0~4>*Q;IQsZ;L+ zOfH&u&$2$0HxCH+f)U))qUp~n<632XQd~>ZVZeH`u3%~5_#y`4eOo^bI-_CMR_&=WTyGzTCi}5aE%v=;Uo)JwQoJolP-V(4Jyo*kp9NQ-4^}pJ)&WAYp%2tEP;QS(T*C5g;vs2Fzz3Y7sKm zb5A}^okVg&^udN9Cf~+^jxV|7d^ViiR~@M|d_s7$j8cfA0oi^($O&^N_1{sHCHzWm z=Hr-K9)EN%-2j_DW=1?{a0P-XoqT0tCwM(cDPiX z_d*HBA1X9n-zkaq|7>vlDMW3n$j*bv*F=Pbh#jXZo@mJ6Ny=c*!XldxzGZ1fNX~>i z;0iug$)YJos@%uorFZ8nT%*FCNL!%Y3$5-|W@x#(NKX`1^xn4Ow#wjEuOVm({#7C3 zPgH^K+Ishxz1A9MX%UpZ0AJb~WvLd|$efA6CMapeNXZHPyj^}5j0$eD87dGV@Sqrg zTJS6gyJJD&NAXo^yd~7Pp|L%`1v}4I_QWqwMpQHsGQEd*nE$IqhzO5~--j&!2Gke_ zL3V?c0`U0r)pHldkua_I3*6>oX5>j1db4M7k#_(Cot%1Q#1A*Cft7mKl8DF8njgPV z-4HGm1+(Hu-6YT2tO7+#?pxLN5hMkqhb73Th`Q`3L@%xw36MH-C8~a+R zpKjjB1BEdX(3f$U{;CYgAddMKh?%8p3k%PrhteSnRPiVv?}8JiI_N^11?>iv=?Qi%yx)I-LL z*pE1hGJMsCr;8@@jC<5?cT{gF23c>zj>4TN=UH*knaS0PgM!!oHsR{AINtz#{GPVU ztUESas2{{);cP-^eKtTiGclz`jIVj|Nc#rB0O}!RO+*z5&Zjy|G6)hzng6~49 z5pl6e2p#LULYcb6`kh(8sh6XjW$PF+71Cdr@Lao;JSgHiBO<$18tl7}-0UqCRx_o?C;wd1F1?F9Rt8IN+Y!3NaTkJCa=_5Zpy(|cd_ZWyWdxb2kSs6YF zh&vA&w6monRI%wd(di&ub_P%1Rk+vDlIQT7xm$K(R=Ta>gwbRIgCG+6ZlwF}#~@4$ z|4%0EJI^vMUg8LQTWerd8m6VHkf%7fE;Q2YbTqj76Y#7-j3FailLq!We@@%Q#Xw)F ze32<8>z9>de+6ry*y%d=b8V;}I}|4J(;;hVqd@k-3wnxG_~a9 zbb(6vVh%Xa-e717EoGt|?N*FUHvG4n2dys3NGjM(pN-1zjS^VBw9Lx2b82@5wT?u> zJGe`>?%SvJ2xh6Vra!2M58XFMM`~rk0=G1v1(27TbSy#+$yDkSboZa!+VLxY$)BOj zaX`VJ&j}R+e#*pODXEl!^QQCPciNUI(sO6kuhrm1BTlZomnSUKV|PJr`S)~|PXrze zKMlj1BXCP!3Wq;?jg$8Z{8nBeCZ1{wt$ zNQK44WRg2NY@`Shaw=+BRG4;dSX7Ts3iT&d;DDx8dGAX#^YsE}vkh1?iV)ymP9M_j z$L;5#ChiHfTOE1r^ly~Fb(u{RrMnbqwK%U0RO1nv&%yO{daJheM|jd`pi*`3Sfj{3K0g>;B*c3GAHZEq zIA}EfGCVTuV@tpE2mL2RDqz#IseMyIVsz!CO7z@Ur{pTqS@wPf{;?Vpzo$~kBt+=P zYWMJwm_7_B2UyrFF#?Kwm!0t0=@4G!lvNMD0%qio-El!mkElcVZWP2V5V)qSMP;N@ z4cT&EKY{{RUz{yRrx_x9t_k<~;bbYxH>hi$J3^l(?s&3pupxB~9~lvJ&R%qULnk_T zGhT#3y^P6DJ966-dgW&emD0oY%2^jZ3v^~4P+7gr$IUQaZ$D}+L)qV(l8=^W$LXX4 z(Au%C_4MzYKEIwSQU;lLoX}>k{`k}wVrL{z`{p&*e`gGnUPw$UQ^WL!9_W8Ez^-tUcQB(0H>40-`N6nw7P?@X%N!7u+dFq5 zVlwBF(ineYb@}i)!upr}#-j~+(vZvBhsoBxJ&A$IA-XUA`e|m8^XMwv-Bdw%B-g6h zOmXzVZX~kJ_a+S;*a{9R9x^>}=Pp``155+f=%JnXC?NaJXBi#y+K9`!<9z zZMh~VpI$VAy>g(f?Sj>Vi%+2rHF>%gmNksdvAv7bt`>~24_9ClKY8&fj!{jj7)KEh z{lkZ7fqD`~aFhm^_01ub==dWuBkwsHZr-1wG6167%#B=CZ=z)xnpiu!Z{?Fl{*_VA zNl;wRzEoNQ5WPU?U)0YbF@4~Nt)#9QjS3?nI`lpoGJulcX=3NTwn+y-O#cTw8R)lY)%a-v(yyf-$!WKQs-2UTJT)QF(Wf&d0qf zVlE}pKS&y%aas z{;2)6cQFH#9RyRu(Q^!xAHMEGJ3Elf{XUC|nFk;{w4?zkzBnWQ_kLnap2DFw<`ldo zstB!WflNi*B~cY8|2=Iz(W)_!W}(!aHVYHxnYcL?;1hZ-`O7jCB;YW7MKQ>HWOEiSI-PmR2 ze}>tY-tP~#A2_dNpeW#>JkS8vWAs6ljb3OPqaMZs!4QR6ZweIfnN;GcC*S>f_m%Nr zZQzHSGeXD~8mA_t@`UoYr35xX6qJgpl0+@HH`DE+f0e&sChJ2Kz$u3#ZisAHIVGm} zIZ^hDHeI6O-V{`+qF>DK)V~ZKw6U_Oaj0_-q|Hk65306$VYHEaMSMznka;r*R#8BJ zb!{8K{flK8Z0^_aUV}xWs!Ll-PlT+}D~akFtkD{5@a{rM=%vX8iCO?bIrp4l{PRoT zW!Gj0^8*oPR0r1D^_i**IVKqLo^~O>G=rr~2HQ))MlpA>xV^y_{9G!35O$S+T$3Nf{TFDvp)c%k&NPVZ$`20;khjkYmcU2&FR>!ICJ=fN6-XK;NS8&!YFu;+K&uzkvJ6@ z=@n$zZU;lHa%#dq_I5j+&tQw}rbEKx)Ru#~7G6c^L9Ta7&t(oA zH^Y`U>vt7bkXNX~{w=XZE2F?tWmX;oOk}Ja*9=h;*J=7>=z0`tXGrXNWu(E1e3(tB z0dxNwygusoDLu^ETXfum&gV@G&s(tP6}-f!a?WAS7c1@rI_DW+_C^w8=d57}f=qSG z%gj8gUup1w%dARf_Kw)Wx{or#`31vCyWXWYwL3niKlJ*!4X6V}_L@8Y%VYMJdbhVV zRA6IY6wX(OPmDaa62b!AcxTcgU=`$7g3sJ@0MYZfT?y1;$gEZYb__=3P%K&hqdtpy zi)iHlQ0|iElCPFcEiwMmVTS^68|+8c3h((%2x}vV1xG{3&$o~Kwbgn4&5!XRIae;Z zzFTh4W&G{X!Z5&EN_u~r=Or|N~1o${v<%X)CAQGv-Qu>kxel!57D0-Zvk#3K-W;)t>#rC{Ky%gIPg zk4iKErvPI8Cb1r%38ecZQSdumabwn^eBAt~sh&J6Q$+l>yCzd%qx`G>wd$xfrrQeG zB!$_WX52JBi~(kuqijltI6T*)lRqBdL6|3&jbX$3IWgF)2BEZcl9^;fDhaVwZW z{&Mk}Z9NC@5(RRVvD=wUg=1^;pLsLGTAvB6L@>*v=}xuwOROz`)(v1dVVR{QRrI~` zmoz_G7{N|0)4qF4g@M!(^a#*!v=Zr96kxM0IV2#2!3GJA#}ywM$XrX#C2mDY1C|x1 z><)nDL`-F1irmd;KNpUqe114PlE!5Jy<}`GryV&bIce*Bu@IpYA{X(DMqa;y zZbw&IU|00a8R<8ka(+NHKNh;K#vkA6OlZbvR+JkJc9!vi61A~t`~)B!>K^>PsaI13 z+G7?dlW$lOgXao`(c+(7LC~6XdLos{F{bWalvlau)FmGk957J+##4%C(pozsG{&kY z8ZB;Mp8bI|VZhif)?tqy%HUiaTIHAT>Gd*@(~jI*ET^r|URbGWt&;)|t1L^>qM71L z8yRiCDL&oP+$VxQpuFG0c0$K0M=*oxQMkUxCR(uaTe^VlC0#_eks~X;zzfs1ep9=- zTH}o~2yTp!Ig95?5xDf0KMN{JOq=;V_M^(<3Y1c{yYor?=jQmR5z$GixCJTEBh%<^ zOnVfAkTV-elgaCHwEd0x#Z2M0cN`Oyc7%Lmr>>>%?`zW)qOfzvQy52y8C4pYUZGx; zq&Q~fbc5}j!mczE#fTV;tDOgalL7(B`=TYbi~c{6xPy_tzhk{5-n5DZ5|1~5r_!r~ zfePjB$%qFZ{WSq&i!LgW#kbpAy&D+v(v3$B^7M_XZ3Imx?HuG#CZC5qJan;07n*AU za?@q98HR9k`vcz9bv;20)|m}FGQ2V-2)7P;>K%f7PKhBL z-622NhPWf9rHj9B#$W{({0bhhi8l-h-z^9%(mPKe<@q+8^%|w8acG#aVnaKTX|~Gs zt8(reK5gXGR5(Js1dsh8xq$w&HBHx&>)Duo$TwyF-nql{xOpbWAw-F50JTNXLF4le znH-qL**cC{WOevRbz5ApZ^-@{&yOxM4#*vxd~%O_@#PZ__pLdYR?J*ODFID78i^vW9tqa4G~wm|6HM`vu{6ku9gxya#c^61 zPO%ilb0{gH%kyG?b>~02{@J9uThTlraY*aAIjj7h!Eho%el8Dj{-_uCIghBHCQ;x# zs3HG8M{&#j*EM7E{=mW;j*0)^?eGKL57Mb8x&rQh-Z(*$Y^FRUVEF@BiTMf``L+}| zg@d0X>b(3z6kS0S*)|I{djcZwv3u`<1wQfTCp`haOae=Q`b}UY;cE&ps5M;x19o+C zIK~iv?$;o_*9Bncv4bHpZ@tohUOGBcajhR?vq?qtH075E3Dasl)DD;8Vn3FCM3rc` z;a{2YIyaYVd}lPfFY()2WlbYUf~mz=32lDfZtunzlO%hwv+#M`vZbV9?%F`6&<&>K zw#*HtQoO~H`Zvh>iIc}yi#|hKu_&gY7XH23rT}az51xns>r^?bo)zK(WABVOhOZtIYm>tJ1|n2uFN9`dXg zEA;_n2cmoYpoK+Bn=f%ed)n+#4w;rCgWhl9 z+mM0>6m3zcu;ff}ov#Rj#Zn~}Vu&KFzH^H{*oJe}A+D1!sHK4sY!Q7b;Wgr80Fo&* zYmKN;ZYhDwVaSIfo{iZ*Ms#IvuhPaHnIr4)F7%BjSE3`mp`mMTILBOVj1>N3^S%Z= zM-AZWY9fxJL>D_M0U+0k!ps_H@o)(ahpCgEqm72Ihw!{_G7}?5(N|f$$qEn?4dAqk zs-mpB7jyQ{&Q~teg-LkNuTxe`OK%`QVVw2{X>kB3I>6@BlIv0kFKrrg6ytbeT1Mw< zQmUpr(KHF0%&;X>)RtBtBW4u}7%5ruUC2$zp%LFQbmrKIRM1k3M*bl0oJI)95B--G zVlqS9>N6|z`x+;Qq`Tim96y|N%z3(@Nw0{vbd;)<^@X$f&5#>eP1S=?D8{mf8 z85Fobd&B!)PH=KZMRITK^e9<+1?0;6wQ8ktOPk8;ZXd5IMCS|{>(0*lm|}VCtl5V= z%ZMVFjCi+K6Kh_Tmt)XpRg;jkIXpi%&irsc8s?)@c-0Iz+BI^;kRWo_TEe4>b06S{ z8_es8MsCLo)7NfOkX=o(na^vNGz2^_N6stWl^1K^)Ld;C8A4uzH(hm5lWUvQ(=fNW zD$XmopfvA0?`UU+oK_A6uRO8G_WO_Mw37yw5d1^9Yc7@!@Tm&dFiW|d6Bjle#B!kOF}K1=&FmNpRCm(VY>S8p9@A*G{M8#q0M~Tzao4g%_Na_dyKenP zfhOHe^RmKA#=WFfG9WIPK>D1`@~y3WZ~49jhKUuAFenxRNW1>59d`Iq`YR^P_Q0)x zAj$oCsT4Qxi2ID#dh$B{))91n!nOb;fW8)E-!COj5tX?pJcHa$exY1Vb6@=m^P+MX zt8(S^$VI=L(Y2KxPLz@zpOQ|-4h8P+g)X&C@9u=txi^cIa!6n^(7k~1I>pLhK`d(V2I z{g}=7iB#yu*Hu=*h1H4mQ;k&~r{NuPW{v#aC|R`)WoS#?iuM;jTwuzlVUMF2g-Cq; zdVCq}LkY{8HHdT@Vl^qXhyDOa5YPVLiBSN(FP+Y$Ok(;B(EM$y`!`Z*t-KWDeLbuB zgjLgUhrK?a^jYDGkwtf>z4=9}jdLl*M#a3tmnap+QVn5N3OxFYtS=spl*dnpLX+o{ zCy3x)uIbnVKwcUz9Jcci7n~%6+^fEb5R6*78~l{rrG$7WEnw$BLr6B+Ob~+Pl z6h2BY2R3HJA!&fsw)RDBcaDGO0LP7eiXUi}#`bYIClQ=8S(R~Ps@U=(GJ(U>W)f+i zs-^$|t-AYuU5e9mjmdGE@c^`#Cz*VCrAYdSak)iWk7sxP&c2MUaSH1)FbB%MN|Va1 zrHSV#0s6g5*kKg{T0`Tvhs-lWJlefNW;9P&xhYpkI*h1DFefnMKv8K0OIagQ^P6$;IfnnJz3`0IP$U z59!+BBiuEVX&`%&-r1R`(I)2%xTNJfI|tu&rhD_fhuAYdOMd4?7lE8u60D1D9?X26 zFYEBPC(+68#lG(nyy--^qVg8bN}iJlwq_uLgm zUX9n*#cxp5HQ)9d-u0JNzZcgJ$jh*Az|y(2xo zR{ig=NqXM|8h3v(z9~e$1z7idsigm9r`5f}0@S{%KB5DdLGB28{NL~0zQbc6hj>N4 z!Q>==fTDr@ze%%yoYa3=q5ps6f9KBrf0_T`&iwgsx{s*@GZ-AGaB*f4EEQ0@QC;!xp>_4;r0eQDWtpET3 literal 0 HcmV?d00001 diff --git a/Projects/Android/libs/hapticsservice.aar b/Projects/Android/libs/hapticsservice.aar deleted file mode 100644 index d07a795e177dc64644b2b60f1e712d8717281e93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28012 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 { + Log.v(APPLICATION, "ExternalHapticsService is:" + desc); + }); + + externalHapticsServiceClient.bindService(); + mNativeHandle = GLES3JNILib.onCreate( this, commandLineParams ); } @@ -338,11 +356,10 @@ import static android.system.Os.setenv; Log.v( TAG, "GLES3JNIActivity::onStart()" ); super.onStart(); - // Bind to the service - Make this a config file thing - bindService(new Intent("com.drbeef.hapticservice.HapticService_bHaptics").setPackage("com.drbeef.hapticservice"), this, - Context.BIND_AUTO_CREATE); - - GLES3JNILib.onStart( mNativeHandle, this ); + if ( mNativeHandle != 0 ) + { + GLES3JNILib.onStart(mNativeHandle, this); + } } @Override protected void onResume() @@ -350,20 +367,29 @@ import static android.system.Os.setenv; Log.v( TAG, "GLES3JNIActivity::onResume()" ); super.onResume(); - GLES3JNILib.onResume( mNativeHandle ); + if ( mNativeHandle != 0 ) + { + GLES3JNILib.onResume(mNativeHandle); + } } @Override protected void onPause() { Log.v( TAG, "GLES3JNIActivity::onPause()" ); - GLES3JNILib.onPause( mNativeHandle ); + if ( mNativeHandle != 0 ) + { + GLES3JNILib.onPause(mNativeHandle); + } super.onPause(); } @Override protected void onStop() { Log.v( TAG, "GLES3JNIActivity::onStop()" ); - GLES3JNILib.onStop( mNativeHandle ); + if ( mNativeHandle != 0 ) + { + GLES3JNILib.onStop(mNativeHandle); + } super.onStop(); } @@ -376,7 +402,12 @@ import static android.system.Os.setenv; GLES3JNILib.onSurfaceDestroyed( mNativeHandle ); } - GLES3JNILib.onDestroy( mNativeHandle ); + if ( mNativeHandle != 0 ) + { + GLES3JNILib.onDestroy(mNativeHandle); + } + + externalHapticsServiceClient.stopBinding(); super.onDestroy(); // Reset everything in case the user re opens the app @@ -413,18 +444,4 @@ import static android.system.Os.setenv; mSurfaceHolder = null; } } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - hapticsService = IHapticsService.Stub.asInterface(service); - hasHapticService = true; - } - - @Override - public void onServiceDisconnected(ComponentName name) { - stopService(new Intent("com.drbeef.hapticservice.HapticService_bHaptics").setPackage("com.drbeef.hapticservice")); - - hasHapticService = false; - hapticsService = null; - } } From 7898c7cdd8cc3061ea15d55523bfe29853bd3663 Mon Sep 17 00:00:00 2001 From: Grant Bagwell Date: Sat, 15 May 2021 17:30:28 +0200 Subject: [PATCH 3/8] Updates to Vibrate (to contain extra information) --- Projects/Android/jni/rtcw/src/cgame/cg_draw.c | 4 ++-- Projects/Android/jni/rtcw/src/cgame/cg_local.h | 2 +- .../Android/jni/rtcw/src/cgame/cg_playerstate.c | 7 +++++++ .../Android/jni/rtcw/src/cgame/cg_syscalls.c | 4 ++-- Projects/Android/jni/rtcw/src/cgame/cg_view.c | 4 ++-- .../Android/jni/rtcw/src/cgame/cg_weapons.c | 12 ++++++------ Projects/Android/jni/rtcw/src/client/cl_cgame.c | 17 +++++++++++++++++ Projects/Android/jni/rtcw/src/game/g_active.c | 4 ++-- Projects/Android/jni/rtcw/src/game/g_local.h | 3 ++- Projects/Android/jni/rtcw/src/game/g_public.h | 1 + Projects/Android/jni/rtcw/src/game/g_syscalls.c | 4 ++-- Projects/Android/jni/rtcw/src/game/g_weapon.c | 13 ++++++------- 12 files changed, 50 insertions(+), 25 deletions(-) diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_draw.c b/Projects/Android/jni/rtcw/src/cgame/cg_draw.c index 0aac793..c620369 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_draw.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_draw.c @@ -3575,8 +3575,8 @@ void CG_ApplyShakeCamera() { if (VectorLength(cg.cameraShakeAngles) > 0.1f) { - trap_Vibrate(10, 0, Com_Clamp(0.0f, 1.0f, fabs(cg.cameraShakeAngles[0]))); - trap_Vibrate(10, 1, Com_Clamp(0.0f, 1.0f, fabs(cg.cameraShakeAngles[1]))); + trap_Vibrate(10, 0, Com_Clamp(0.0f, 1.0f, fabs(cg.cameraShakeAngles[0])),"camera_shake_left",fabs(cg.cameraShakeAngles[0]),fabs(cg.cameraShakeAngles[1])); + trap_Vibrate(10, 0, Com_Clamp(0.0f, 1.0f, fabs(cg.cameraShakeAngles[1])),"camera_shake_right",fabs(cg.cameraShakeAngles[0]),fabs(cg.cameraShakeAngles[1])); } } diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_local.h b/Projects/Android/jni/rtcw/src/cgame/cg_local.h index 13e0df0..d965c97 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_local.h +++ b/Projects/Android/jni/rtcw/src/cgame/cg_local.h @@ -2458,7 +2458,7 @@ int trap_Key_GetCatcher( void ); void trap_Key_SetCatcher( int catcher ); int trap_Key_GetKey( const char *binding ); -int trap_Vibrate(int duration, int channel, float intensity ); +int trap_Vibrate( int duration, int channel, float intensity, char *description, float yaw, float height); // RF void trap_SendMoveSpeedsToGame( int entnum, char *movespeeds ); diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_playerstate.c b/Projects/Android/jni/rtcw/src/cgame/cg_playerstate.c index e19b5a4..910cd32 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_playerstate.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_playerstate.c @@ -221,6 +221,13 @@ void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) { vd->damageDuration = kick * 50 * ( 1 + 2 * ( !vd->damageX && !vd->damageY ) ); cg.damageTime = cg.snap->serverTime; cg.damageIndex = slot; + + //GB - Add a haptic event + //Ensure a decent level of haptic feedback for any damage + //float hapticLevel = 80 + min(damage * 4, 120.0); + + //Indicate head damage if appropriate + //RTCWVR_HapticEvent("damage", 0, 0, hapticLevel, yaw, pitch); } diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c b/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c index a98576f..206e8aa 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c @@ -549,6 +549,6 @@ qboolean trap_GetModelInfo( int clientNum, char *modelName, animModelInfo_t **mo return syscall( CG_GETMODELINFO, clientNum, modelName, modelInfo ); } -int trap_Vibrate( int duration, int channel, float intensity ) { - return syscall( CG_HAPTIC, duration, channel, PASSFLOAT(intensity) ); +int trap_Vibrate( int duration, int channel, float intensity, char *description, float yaw, float height) { + return syscall( CG_HAPTIC, duration, channel, PASSFLOAT(intensity), description, PASSFLOAT(yaw), PASSFLOAT(height)); } diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_view.c b/Projects/Android/jni/rtcw/src/cgame/cg_view.c index 8e9eb70..9274b4a 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_view.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_view.c @@ -1666,8 +1666,8 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo //Don't allow long running haptics to continue once dead if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { - trap_Vibrate(0, 0, 0.0); - trap_Vibrate(0, 1, 0.0); + trap_Vibrate(0, 0, 0.0, "dead_left", 0.0, 0.0); + trap_Vibrate(0, 1, 0.0, "dead_right", 0.0, 0.0); } DEBUGTIME diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c b/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c index 9cb9c13..bbd3ff4 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c @@ -2229,10 +2229,10 @@ static void CG_FlamethrowerFlame( centity_t *cent, vec3_t origin ) { CG_FireFlameChunks(cent, origin, angles, 1.0, qtrue, 1); - trap_Vibrate(-1, cgVR->right_handed ? 1 : 0, 0.6); + trap_Vibrate(-1, cgVR->right_handed ? 1 : 0, 0.6, "fire_flames", 0.0, 0.0); if (cgVR->weapon_stabilised) { - trap_Vibrate(-1, cgVR->right_handed ? 0 : 1, 0.5); + trap_Vibrate(-1, cgVR->right_handed ? 0 : 1, 0.5, "fire_flames", 0.0, 0.0); } } @@ -2776,10 +2776,10 @@ void CG_PlayerTeslaCoilFire( centity_t *cent, vec3_t flashorigin ) { trap_R_AddLightToScene( tr.endpos, 256 + 600 * tr.fraction, 0.2, 0.6, 1, 0 ); } - trap_Vibrate(-1, cgVR->right_handed ? 1 : 0, 0.8); + trap_Vibrate(-1, cgVR->right_handed ? 1 : 0, 0.8, "fire_tesla", 0.0, 0.0); if (cgVR->weapon_stabilised) { - trap_Vibrate(-1, cgVR->right_handed ? 0 : 1, 0.8); + trap_Vibrate(-1, cgVR->right_handed ? 0 : 1, 0.8, "fire_tesla", 0.0, 0.0); } // shake the camera a bit @@ -3378,8 +3378,8 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent if (wasfiring) { //Stop haptics - trap_Vibrate(0, 0, 0.0); - trap_Vibrate(0, 1, 0.0); + trap_Vibrate(0, 0, 0.0, "stop_firing", 0.0, 0.0); + trap_Vibrate(0, 1, 0.0, "stop_firing", 0.0, 0.0); wasfiring = qfalse; } } diff --git a/Projects/Android/jni/rtcw/src/client/cl_cgame.c b/Projects/Android/jni/rtcw/src/client/cl_cgame.c index 52b8123..8f56abd 100644 --- a/Projects/Android/jni/rtcw/src/client/cl_cgame.c +++ b/Projects/Android/jni/rtcw/src/client/cl_cgame.c @@ -862,7 +862,24 @@ int CL_CgameSystemCalls( int *args ) { return SV_GetModelInfo( args[1], VMA( 2 ), VMA( 3 ) ); case CG_HAPTIC: + //VMF(3) = Intensity + //VMA(4) = Description + //VMF(5) = Yaw + //VMF(6) = Height + RTCWVR_Vibrate( args[1], args[2], VMF( 3 ) ); + + float shakeScale = 1.0f - Com_Clamp(0.0f, 1.0f, ( VMF(3) * ( 1.0f / 4000.0f ) ) + 0.25f ); // 0...4000 -> max...min rumble + float highMag = shakeScale; + int highDuration = FloatAsInt(300.0f * shakeScale); + float lowMag = shakeScale * 0.75f; + int lowDuration = FloatAsInt(500.0f * shakeScale); + + //generic rumbling - keep it low + RTCWVR_HapticEvent("rumble_front", 0, 0, 30.0f * Com_Clamp(0.1, 1.0, VMF(3)*2.0f + 0.1f), highDuration, 0); + RTCWVR_HapticEvent("rumble_back", 0, 0, 30.0f * Com_Clamp(0.1, 1.0, VMF(3)*2.0f + 0.1f), highDuration, 0); + + return 0; default: diff --git a/Projects/Android/jni/rtcw/src/game/g_active.c b/Projects/Android/jni/rtcw/src/game/g_active.c index 7e45125..d04abce 100644 --- a/Projects/Android/jni/rtcw/src/game/g_active.c +++ b/Projects/Android/jni/rtcw/src/game/g_active.c @@ -95,8 +95,8 @@ void P_DamageFeedback( gentity_t *player ) { client->ps.damageCount = count; if (!client->ps.aiChar) { - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f); - trap_Vibrate(1000, 0, (count / 255.0) + 0.5f); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 0, (count / 255.0) + 0.5f, "ignore", 0.0, 0.0); } diff --git a/Projects/Android/jni/rtcw/src/game/g_local.h b/Projects/Android/jni/rtcw/src/game/g_local.h index 9558694..df1b850 100644 --- a/Projects/Android/jni/rtcw/src/game/g_local.h +++ b/Projects/Android/jni/rtcw/src/game/g_local.h @@ -1214,7 +1214,8 @@ void trap_GetUsercmd( int clientNum, usercmd_t *cmd ); qboolean trap_GetEntityToken( char *buffer, int bufferSize ); qboolean trap_GetTag( int clientNum, char *tagName, orientation_t * or ); -int trap_Vibrate(int duration, int channel, float intensity ); +//int trap_Vibrate(int duration, int channel, float intensity ); +int trap_Vibrate( int duration, int channel, float intensity, char *description, float yaw, float height); int trap_DebugPolygonCreate( int color, int numPoints, vec3_t *points ); void trap_DebugPolygonDelete( int id ); diff --git a/Projects/Android/jni/rtcw/src/game/g_public.h b/Projects/Android/jni/rtcw/src/game/g_public.h index f2d50dc..003a46e 100644 --- a/Projects/Android/jni/rtcw/src/game/g_public.h +++ b/Projects/Android/jni/rtcw/src/game/g_public.h @@ -250,6 +250,7 @@ typedef enum { G_GETTAG, G_HAPTIC, + G_FULL_HAPTIC, BOTLIB_SETUP = 200, // ( void ); BOTLIB_SHUTDOWN, // ( void ); diff --git a/Projects/Android/jni/rtcw/src/game/g_syscalls.c b/Projects/Android/jni/rtcw/src/game/g_syscalls.c index 7924c69..bb1a293 100644 --- a/Projects/Android/jni/rtcw/src/game/g_syscalls.c +++ b/Projects/Android/jni/rtcw/src/game/g_syscalls.c @@ -255,8 +255,8 @@ qboolean trap_GetTag( int clientNum, char *tagName, orientation_t *or ) { return syscall( G_GETTAG, clientNum, tagName, or ); } -int trap_Vibrate(int duration, int channel, float intensity ) { - return syscall( G_HAPTIC, duration, channel, PASSFLOAT(intensity) ); +int trap_Vibrate( int duration, int channel, float intensity, char *description, float yaw, float height) { + return syscall( G_HAPTIC, duration, channel, PASSFLOAT(intensity), description, PASSFLOAT(yaw), PASSFLOAT(height)); } // BotLib traps start here diff --git a/Projects/Android/jni/rtcw/src/game/g_weapon.c b/Projects/Android/jni/rtcw/src/game/g_weapon.c index 2a73b9f..789048a 100644 --- a/Projects/Android/jni/rtcw/src/game/g_weapon.c +++ b/Projects/Android/jni/rtcw/src/game/g_weapon.c @@ -141,7 +141,7 @@ void Weapon_Knife( gentity_t *ent ) { tent->s.weapon = ent->s.weapon; //we hit something - trap_Vibrate(100, gVR->right_handed ? 1 : 0, 0.9); + trap_Vibrate(100, gVR->right_handed ? 1 : 0, 0.9, "knife_hit", 0.0, 0.0); if ( tr.entityNum == ENTITYNUM_WORLD ) { // don't worry about doing any damage return; @@ -966,12 +966,11 @@ void Bullet_Fire( gentity_t *ent, float spread, int damage ) { if (ent->s.weapon == WP_AKIMBO) { right = BG_AkimboFireSequence(ent->s.weapon, ent->client->ps.ammoclip[WP_AKIMBO], ent->client->ps.ammoclip[WP_COLT] ); - trap_Vibrate(100, right ? 1 : 0, 1.0); - + trap_Vibrate(100, right ? 1 : 0, 1.0, printf("fire_%i", ent->s.weapon), 0.0, 0.0); } else{ - trap_Vibrate(100, right ? 1 : 0, 1.0); + trap_Vibrate(100, right ? 1 : 0, 1.0, printf("fire_%i", ent->s.weapon), 0.0, 0.0); if (gVR->weapon_stabilised) { - trap_Vibrate(100, right ? 0 : 1, 0.7); + trap_Vibrate(100, right ? 0 : 1, 0.7, printf("fire_%i", ent->s.weapon), 0.0, 0.0); } } } @@ -2033,9 +2032,9 @@ void FireWeapon( gentity_t *ent ) { ent->client->ps.classWeaponTime = level.time; // JPW NERVE Weapon_RocketLauncher_Fire( ent, aimSpreadScale ); if (!ent->aiCharacter) { - trap_Vibrate(200, gVR->right_handed ? 1 : 0, 1.0); + trap_Vibrate(200, gVR->right_handed ? 1 : 0, 1.0, "fire_rocket", 0.0, 0.0); if (gVR->weapon_stabilised) { - trap_Vibrate(200, gVR->right_handed ? 0 : 1, 0.7); + trap_Vibrate(200, gVR->right_handed ? 0 : 1, 0.7, "fire_rocket", 0.0, 0.0); } } break; From cf8e4ac64018f429326a44edcb7a28a34f14c87b Mon Sep 17 00:00:00 2001 From: Grant Bagwell Date: Wed, 2 Jun 2021 16:55:46 +0200 Subject: [PATCH 4/8] Haptics for Firing and Damage --- .../Android/jni/RTCWVR/RTCWVR_SurfaceView.c | 94 ++++++++++++++- Projects/Android/jni/RTCWVR/VrCommon.h | 1 + Projects/Android/jni/rtcw/src/cgame/cg_draw.c | 8 +- .../Android/jni/rtcw/src/cgame/cg_local.h | 1 + Projects/Android/jni/rtcw/src/cgame/cg_main.c | 3 + .../Android/jni/rtcw/src/cgame/cg_public.h | 3 +- .../Android/jni/rtcw/src/cgame/cg_syscalls.c | 4 + .../Android/jni/rtcw/src/client/cl_cgame.c | 16 +-- .../Android/jni/rtcw/src/client/cl_scrn.c | 1 + .../Android/jni/rtcw/src/game/bg_public.h | 6 +- Projects/Android/jni/rtcw/src/game/g_active.c | 109 +++++++++++++++++- Projects/Android/jni/rtcw/src/game/g_local.h | 1 + Projects/Android/jni/rtcw/src/game/g_main.c | 3 +- Projects/Android/jni/rtcw/src/game/g_weapon.c | 9 +- .../Android/jni/rtcw/src/server/sv_game.c | 3 + 15 files changed, 239 insertions(+), 23 deletions(-) diff --git a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c index 5679caa..caa4322 100644 --- a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c +++ b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c @@ -908,6 +908,84 @@ void RTCWVR_Vibrate( int duration, int channel, float intensity ) vibration_channel_intensity[channel] = intensity; } +void RTCWVR_Haptic( int duration, int channel, float intensity, char *description, float yaw, float height ) +{ + if(strstr(description,"camera_shake") == NULL) + Com_Printf("Vibrate Description: %s", description); + + if(strstr(description,"damage_") != NULL) { + RTCWVR_HapticEvent(description, 0, 0, 100.0f * intensity, yaw, height); + } + else if(strstr(description,"fire_") != NULL) { + if(strcmp(description,"fire_11") == 0 || strcmp(description,"fire_2") == 0) { + RTCWVR_HapticEvent("fire_pistol", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_3") == 0) { + RTCWVR_HapticEvent("fire_mp40", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_4") == 0) { + RTCWVR_HapticEvent("fire_mauser", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_5") == 0 || strcmp(description,"fire_17") == 0) { + RTCWVR_HapticEvent("fire_fg42", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_6") == 0) { + RTCWVR_HapticEvent("fire_grenadelauncher", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_7") == 0 || strcmp(description,"fire_rocket") == 0) { + RTCWVR_HapticEvent("fire_panzerfaust", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_8") == 0) { + RTCWVR_HapticEvent("fire_venom", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_9") == 0 || strcmp(description,"fire_flames") == 0) { + RTCWVR_HapticEvent("fire_flamethrower", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_10") == 0 || strcmp(description,"fire_tesla") == 0) { + RTCWVR_HapticEvent("fire_tesla", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_12") == 0) { + RTCWVR_HapticEvent("fire_thompson", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_13") == 0) { + RTCWVR_HapticEvent("fire_garand", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_14") == 0) { + RTCWVR_HapticEvent("fire_grenade", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_15") == 0) { + RTCWVR_HapticEvent("fire_sniper", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_16") == 0) { + RTCWVR_HapticEvent("fire_snooperscope", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_18") == 0) { + RTCWVR_HapticEvent("fire_sten", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_19") == 0) { + RTCWVR_HapticEvent("fire_silencer", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"fire_20") == 0) { + //Plays on 0 position (Vest) (not left or right) + RTCWVR_HapticEvent("fire_akimbo", 0, 0, 100.0f * intensity, 0, 0); + } + } + else if(strcmp(description,"knife_hit") == 0) { + RTCWVR_HapticEvent("knife_hit", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); + } + else if(strcmp(description,"camera_shake_left") == 0) { + RTCWVR_HapticEvent("rumble_front", 0, 0, 100.0f * intensity, yaw, height); + } + else if(strcmp(description,"camera_shake_right") == 0) { + RTCWVR_HapticEvent("rumble_back", 0, 0, 100.0f * intensity, yaw, height); + } + else { + if(strstr(description,"ignore") == NULL && + strstr(description,"dead") == NULL) + Com_Printf("Missing Haptic: %s", description); + } +} + void jni_haptic_event(const char* event, int position, int flags, int intensity, float angle, float yHeight); void jni_haptic_updateevent(const char* event, int intensity, float angle); void jni_haptic_stopevent(const char* event); @@ -917,6 +995,7 @@ void jni_haptic_disable(); void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ) { + Com_Printf( "Vibrate Event Fired: %s", event ); jni_haptic_event(event, position, flags, intensity, angle, yHeight); } @@ -937,7 +1016,11 @@ void RTCWVR_HapticStopEvent(const char* event) void RTCWVR_HapticEnable() { - jni_haptic_enable(); + static bool firstTime = true; + if (firstTime) { + jni_haptic_enable(); + firstTime = false; + } } void RTCWVR_HapticDisable() @@ -1609,7 +1692,7 @@ void * AppThreadFunction(void * parm ) { vrapi_GetSystemPropertyFloatArray(&java, VRAPI_SYS_PROP_SUPPORTED_DISPLAY_REFRESH_RATES, &refreshRatesArray[0], numberOfRefreshRates); for (int i = 0; i < numberOfRefreshRates; i++) { - ALOGV("Supported refresh rate : %s Hz", refreshRatesArray[i]); + ALOGV("Supported refresh rate : %d Hz", refreshRatesArray[i]); if (maximumSupportRefresh < refreshRatesArray[i]) { maximumSupportRefresh = refreshRatesArray[i]; } @@ -1846,6 +1929,7 @@ void RTCWVR_submitFrame() RTCWVR_incrementFrameIndex(); + RTCWVR_HapticEndFrame(); } static void ovrAppThread_Create( ovrAppThread * appThread, JNIEnv * env, jobject activityObject, jclass activityClass ) @@ -2081,6 +2165,12 @@ JNIEXPORT void JNICALL Java_com_drbeef_rtcwquest_GLES3JNILib_onStart( JNIEnv * e jclass callbackClass = (*env)->GetObjectClass(env, jniCallbackObj); android_shutdown = (*env)->GetMethodID(env,callbackClass,"shutdown","()V"); + android_haptic_event = (*env)->GetMethodID(env, callbackClass, "haptic_event", "(Ljava/lang/String;IIIFF)V"); + android_haptic_updateevent = (*env)->GetMethodID(env, callbackClass, "haptic_updateevent", "(Ljava/lang/String;IF)V"); + android_haptic_stopevent = (*env)->GetMethodID(env, callbackClass, "haptic_stopevent", "(Ljava/lang/String;)V"); + android_haptic_endframe = (*env)->GetMethodID(env, callbackClass, "haptic_endframe", "()V"); + android_haptic_enable = (*env)->GetMethodID(env, callbackClass, "haptic_enable", "()V"); + android_haptic_disable = (*env)->GetMethodID(env, callbackClass, "haptic_disable", "()V"); ovrAppThread * appThread = (ovrAppThread *)((size_t)handle); ovrMessage message; diff --git a/Projects/Android/jni/RTCWVR/VrCommon.h b/Projects/Android/jni/RTCWVR/VrCommon.h index 9ab7f7a..948ca0d 100644 --- a/Projects/Android/jni/RTCWVR/VrCommon.h +++ b/Projects/Android/jni/RTCWVR/VrCommon.h @@ -60,6 +60,7 @@ int GetRefresh(); qboolean RTCWVR_useScreenLayer(); void RTCWVR_GetScreenRes(int *width, int *height); void RTCWVR_Vibrate(int duration, int channel, float intensity ); +void RTCWVR_Haptic(int duration, int channel, float intensity, char *description, float yaw, float height); void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ); void RTCWVR_HapticUpdateEvent(const char* event, int intensity, float angle ); void RTCWVR_HapticEndFrame(); diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_draw.c b/Projects/Android/jni/rtcw/src/cgame/cg_draw.c index c620369..c6e501c 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_draw.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_draw.c @@ -3575,8 +3575,12 @@ void CG_ApplyShakeCamera() { if (VectorLength(cg.cameraShakeAngles) > 0.1f) { - trap_Vibrate(10, 0, Com_Clamp(0.0f, 1.0f, fabs(cg.cameraShakeAngles[0])),"camera_shake_left",fabs(cg.cameraShakeAngles[0]),fabs(cg.cameraShakeAngles[1])); - trap_Vibrate(10, 0, Com_Clamp(0.0f, 1.0f, fabs(cg.cameraShakeAngles[1])),"camera_shake_right",fabs(cg.cameraShakeAngles[0]),fabs(cg.cameraShakeAngles[1])); + // up/down = cg.cameraShakeAngles[0] + // left/right = cg.cameraShakeAngles[1]; + // roll cg.cameraShakeAngles[2] + + trap_Vibrate(10, 0, Com_Clamp(0.0f, 1.0f, fabs(cg.cameraShakeAngles[0])),"camera_shake_left",270,fabs(cg.cameraShakeAngles[0])); + trap_Vibrate(10, 0, Com_Clamp(0.0f, 1.0f, fabs(cg.cameraShakeAngles[1])),"camera_shake_right",90,fabs(cg.cameraShakeAngles[0])); } } diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_local.h b/Projects/Android/jni/rtcw/src/cgame/cg_local.h index d965c97..1205d82 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_local.h +++ b/Projects/Android/jni/rtcw/src/cgame/cg_local.h @@ -2459,6 +2459,7 @@ void trap_Key_SetCatcher( int catcher ); int trap_Key_GetKey( const char *binding ); int trap_Vibrate( int duration, int channel, float intensity, char *description, float yaw, float height); +int trap_EnableHaptics(); // RF void trap_SendMoveSpeedsToGame( int entnum, char *movespeeds ); diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_main.c b/Projects/Android/jni/rtcw/src/cgame/cg_main.c index 4a034b5..0595d9f 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_main.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_main.c @@ -2454,6 +2454,9 @@ void CG_Init( int serverMessageNum, int serverCommandSequence ) { } // jpw // -NERVE - SMF + + //GB - Turn on haptics here - because reasons + trap_EnableHaptics(); } /* diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_public.h b/Projects/Android/jni/rtcw/src/cgame/cg_public.h index 51b93cc..20e75fa 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_public.h +++ b/Projects/Android/jni/rtcw/src/cgame/cg_public.h @@ -219,7 +219,8 @@ typedef enum { CG_GETMODELINFO, - CG_HAPTIC + CG_HAPTIC, + CG_HAPTICENABLE } cgameImport_t; diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c b/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c index 206e8aa..16c8bd5 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c @@ -552,3 +552,7 @@ qboolean trap_GetModelInfo( int clientNum, char *modelName, animModelInfo_t **mo int trap_Vibrate( int duration, int channel, float intensity, char *description, float yaw, float height) { return syscall( CG_HAPTIC, duration, channel, PASSFLOAT(intensity), description, PASSFLOAT(yaw), PASSFLOAT(height)); } + +int trap_EnableHaptics() { + return syscall( CG_HAPTICENABLE ); +} diff --git a/Projects/Android/jni/rtcw/src/client/cl_cgame.c b/Projects/Android/jni/rtcw/src/client/cl_cgame.c index 8f56abd..84eca9d 100644 --- a/Projects/Android/jni/rtcw/src/client/cl_cgame.c +++ b/Projects/Android/jni/rtcw/src/client/cl_cgame.c @@ -45,6 +45,7 @@ extern qboolean getCameraInfo( int camNum, int time, vec3_t *origin, vec3_t *ang extern void SV_SendMoveSpeedsToGame( int entnum, char *text ); extern qboolean SV_GetModelInfo( int clientNum, char *modelName, animModelInfo_t **modelInfo ); void RTCWVR_Vibrate(int duration, int channel, float intensity ); +void RTCWVR_Haptic(int duration, int channel, float intensity, char *description, float yaw, float height); void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ); void RTCWVR_HapticUpdateEvent(const char* event, int intensity, float angle ); void RTCWVR_HapticEndFrame(); @@ -862,6 +863,7 @@ int CL_CgameSystemCalls( int *args ) { return SV_GetModelInfo( args[1], VMA( 2 ), VMA( 3 ) ); case CG_HAPTIC: + //args[2] = Right or left channel (1 = Right / 0 = left) //VMF(3) = Intensity //VMA(4) = Description //VMF(5) = Yaw @@ -869,17 +871,11 @@ int CL_CgameSystemCalls( int *args ) { RTCWVR_Vibrate( args[1], args[2], VMF( 3 ) ); - float shakeScale = 1.0f - Com_Clamp(0.0f, 1.0f, ( VMF(3) * ( 1.0f / 4000.0f ) ) + 0.25f ); // 0...4000 -> max...min rumble - float highMag = shakeScale; - int highDuration = FloatAsInt(300.0f * shakeScale); - float lowMag = shakeScale * 0.75f; - int lowDuration = FloatAsInt(500.0f * shakeScale); - - //generic rumbling - keep it low - RTCWVR_HapticEvent("rumble_front", 0, 0, 30.0f * Com_Clamp(0.1, 1.0, VMF(3)*2.0f + 0.1f), highDuration, 0); - RTCWVR_HapticEvent("rumble_back", 0, 0, 30.0f * Com_Clamp(0.1, 1.0, VMF(3)*2.0f + 0.1f), highDuration, 0); - + RTCWVR_Haptic( args[1], args[2], VMF( 3 ), VMA(4), VMF(5), VMF(6) ); + return 0; + case CG_HAPTICENABLE: + RTCWVR_HapticEnable(); return 0; default: diff --git a/Projects/Android/jni/rtcw/src/client/cl_scrn.c b/Projects/Android/jni/rtcw/src/client/cl_scrn.c index 56a83bd..8bc46f2 100644 --- a/Projects/Android/jni/rtcw/src/client/cl_scrn.c +++ b/Projects/Android/jni/rtcw/src/client/cl_scrn.c @@ -557,6 +557,7 @@ void SCR_UpdateScreen( void ) { recursive = 1; RTCWVR_FrameSetup(); + RTCWVR_processMessageQueue(); //Get controller state here diff --git a/Projects/Android/jni/rtcw/src/game/bg_public.h b/Projects/Android/jni/rtcw/src/game/bg_public.h index cd98d36..ea24942 100644 --- a/Projects/Android/jni/rtcw/src/game/bg_public.h +++ b/Projects/Android/jni/rtcw/src/game/bg_public.h @@ -543,16 +543,16 @@ typedef enum { WP_VENOM, // 8 WP_FLAMETHROWER, // 9 WP_TESLA, // 10 -// WP_SPEARGUN, // 11 +// WP_SPEARGUN, // __ // weapon keys only go 1-0, so put the alternates above that (since selection will be a double click on the german weapon key) // American equivalents -// WP_KNIFE2, // 12 +// WP_KNIFE2, // __ WP_COLT, // 11 equivalent american weapon to german luger WP_THOMPSON, // 12 equivalent american weapon to german mp40 WP_GARAND, // 13 equivalent american weapon to german mauser -// WP_BAR, // 16 equivalent american weapon to german fg42 +// WP_BAR, // __ equivalent american weapon to german fg42 WP_GRENADE_PINEAPPLE, // 14 // WP_ROCKET_LAUNCHER, // 18 equivalent american weapon to german panzerfaust diff --git a/Projects/Android/jni/rtcw/src/game/g_active.c b/Projects/Android/jni/rtcw/src/game/g_active.c index d04abce..dcc2131 100644 --- a/Projects/Android/jni/rtcw/src/game/g_active.c +++ b/Projects/Android/jni/rtcw/src/game/g_active.c @@ -95,7 +95,114 @@ void P_DamageFeedback( gentity_t *player ) { client->ps.damageCount = count; if (!client->ps.aiChar) { - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage", client->ps.damageYaw, client->ps.damagePitch); + + switch(client->lasthurt_mod) + { + case MOD_SHOTGUN: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_shotgun", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_GRENADE: + case MOD_GRENADE_SPLASH: + case MOD_GRENADE_LAUNCHER: + case MOD_GRENADE_PINEAPPLE: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_frag", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: + case MOD_ROCKET_LAUNCHER: + case MOD_PANZERFAUST: + case MOD_BFG: + case MOD_BFG_SPLASH: + case MOD_MORTAR: + case MOD_MORTAR_SPLASH: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_rocket", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_KNIFE: + case MOD_KNIFE2: + case MOD_KNIFE_STEALTH: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_knife", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_LUGER: + case MOD_COLT: + case MOD_SILENCER: + case MOD_AKIMBO: + case MOD_SPEARGUN_CO2: + case MOD_TARGET_LASER: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_low_bullet", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_THOMPSON: + case MOD_STEN: + case MOD_MAUSER: + case MOD_MP40: + case MOD_GARAND: + case MOD_SPEARGUN: + case MOD_CROSS: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_mid_bullet", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_RAILGUN: + case MOD_SNIPERRIFLE: + case MOD_SNOOPERSCOPE: + case MOD_FG42: + case MOD_FG42SCOPE: + case MOD_BAR: //----(SA) + case MOD_MACHINEGUN: + case MOD_VENOM: + case MOD_VENOM_FULL: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_high_bullet", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_FLAMETHROWER: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_flamethrower", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_LIGHTNING: + case MOD_TESLA: + case MOD_TELEFRAG: + case MOD_LOPER_HIT: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_electric", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_EXPLOSIVE: + case MOD_DYNAMITE: + case MOD_DYNAMITE_SPLASH: + case MOD_AIRSTRIKE: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_explosion", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_GRAPPLE: + case MOD_KICKED: + case MOD_GRABBER: + case MOD_LOPER_LEAP: + case MOD_LOPER_GROUND: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_melee", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_FALLING: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_fall", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_SUICIDE: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_death", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_POISONGAS: + case MOD_WATER: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_gas", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_ZOMBIESPIRIT: + case MOD_ZOMBIESPIRIT_SPLASH: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_zombiespirit", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_SLIME: + case MOD_ZOMBIESPIT: + case MOD_ZOMBIESPIT_SPLASH: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_slime", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_LAVA: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_fire", client->ps.damageYaw, client->ps.damagePitch); + break; + case MOD_CRUSH: + case MOD_TRIGGER_HURT: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_crush", client->ps.damageYaw, client->ps.damagePitch); + break; + + default: + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage", client->ps.damageYaw, client->ps.damagePitch); + break; + } trap_Vibrate(1000, 0, (count / 255.0) + 0.5f, "ignore", 0.0, 0.0); } diff --git a/Projects/Android/jni/rtcw/src/game/g_local.h b/Projects/Android/jni/rtcw/src/game/g_local.h index df1b850..83e791d 100644 --- a/Projects/Android/jni/rtcw/src/game/g_local.h +++ b/Projects/Android/jni/rtcw/src/game/g_local.h @@ -1216,6 +1216,7 @@ qboolean trap_GetTag( int clientNum, char *tagName, orientation_t * or ); //int trap_Vibrate(int duration, int channel, float intensity ); int trap_Vibrate( int duration, int channel, float intensity, char *description, float yaw, float height); +int trap_EnableHaptics(); int trap_DebugPolygonCreate( int color, int numPoints, vec3_t *points ); void trap_DebugPolygonDelete( int id ); diff --git a/Projects/Android/jni/rtcw/src/game/g_main.c b/Projects/Android/jni/rtcw/src/game/g_main.c index 6cfaffa..96f51f4 100644 --- a/Projects/Android/jni/rtcw/src/game/g_main.c +++ b/Projects/Android/jni/rtcw/src/game/g_main.c @@ -29,6 +29,7 @@ If you have questions concerning this license or the applicable additional terms +#include #include "g_local.h" #include "../../../RTCWVR/VrClientInfo.h" @@ -2413,7 +2414,7 @@ void G_RunFrame( int levelTime ) { int msec; //int start, end; - // if we are waiting for the level to restart, do nothing +// if we are waiting for the level to restart, do nothing if ( level.restarted ) { return; } diff --git a/Projects/Android/jni/rtcw/src/game/g_weapon.c b/Projects/Android/jni/rtcw/src/game/g_weapon.c index 789048a..144ce5d 100644 --- a/Projects/Android/jni/rtcw/src/game/g_weapon.c +++ b/Projects/Android/jni/rtcw/src/game/g_weapon.c @@ -963,14 +963,17 @@ void Bullet_Fire( gentity_t *ent, float spread, int damage ) { if (!ent->aiCharacter) { qboolean right = gVR->right_handed; + // Allocates storage + char *fire_command = (char*)malloc(8 * sizeof(char)); + sprintf(fire_command, "fire_%i", ent->s.weapon); if (ent->s.weapon == WP_AKIMBO) { right = BG_AkimboFireSequence(ent->s.weapon, ent->client->ps.ammoclip[WP_AKIMBO], ent->client->ps.ammoclip[WP_COLT] ); - trap_Vibrate(100, right ? 1 : 0, 1.0, printf("fire_%i", ent->s.weapon), 0.0, 0.0); + trap_Vibrate(100, right ? 1 : 0, 1.0, fire_command, 0.0, 0.0); } else{ - trap_Vibrate(100, right ? 1 : 0, 1.0, printf("fire_%i", ent->s.weapon), 0.0, 0.0); + trap_Vibrate(100, right ? 1 : 0, 1.0, fire_command, 0.0, 0.0); if (gVR->weapon_stabilised) { - trap_Vibrate(100, right ? 0 : 1, 0.7, printf("fire_%i", ent->s.weapon), 0.0, 0.0); + trap_Vibrate(100, right ? 0 : 1, 0.7, fire_command, 0.0, 0.0); } } } diff --git a/Projects/Android/jni/rtcw/src/server/sv_game.c b/Projects/Android/jni/rtcw/src/server/sv_game.c index 8eb0a6d..c038b4a 100644 --- a/Projects/Android/jni/rtcw/src/server/sv_game.c +++ b/Projects/Android/jni/rtcw/src/server/sv_game.c @@ -304,6 +304,7 @@ static int FloatAsInt( float f ) { } void RTCWVR_Vibrate(int duration, int channel, float intensity ); +void RTCWVR_Haptic(int duration, int channel, float intensity, char *description, float yaw, float height); void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ); void RTCWVR_HapticUpdateEvent(const char* event, int intensity, float angle ); void RTCWVR_HapticEndFrame(); @@ -479,6 +480,8 @@ int SV_GameSystemCalls( int *args ) { return SV_GetTag( args[1], VMA( 2 ), VMA( 3 ) ); case G_HAPTIC: RTCWVR_Vibrate( args[1], args[2], VMF( 3 ) ); + + RTCWVR_Haptic( args[1], args[2], VMF( 3 ), VMA(4), VMF(5), VMF(6) ); return 0; //==================================== From 31ec8e1f54dc64aee89a2ec4272c049854da3cc6 Mon Sep 17 00:00:00 2001 From: Grant Bagwell Date: Wed, 9 Jun 2021 19:02:20 +0200 Subject: [PATCH 5/8] Finished Weapons + Damage --- .../Android/jni/RTCWVR/RTCWVR_SurfaceView.c | 13 +++-- .../Android/jni/rtcw/src/cgame/cg_weapons.c | 7 ++- Projects/Android/jni/rtcw/src/game/g_active.c | 54 ++++++++++++------- 3 files changed, 49 insertions(+), 25 deletions(-) diff --git a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c index caa4322..7f63ec9 100644 --- a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c +++ b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c @@ -910,12 +910,18 @@ void RTCWVR_Vibrate( int duration, int channel, float intensity ) void RTCWVR_Haptic( int duration, int channel, float intensity, char *description, float yaw, float height ) { - if(strstr(description,"camera_shake") == NULL) - Com_Printf("Vibrate Description: %s", description); + /*if(strstr(description,"camera_shake") == NULL && strstr(description,"ignore") == NULL && strstr(description,"dead_") == NULL) + Com_Printf("GBRTCW: Vibrate Description: %s (Yaw: %f Pitch: %f)", description, yaw, height);*/ if(strstr(description,"damage_") != NULL) { RTCWVR_HapticEvent(description, 0, 0, 100.0f * intensity, yaw, height); } + else if(strstr(description,"stop_firing_") != NULL) { + /* + if(strcmp(description,"stop_firing_9") == 0 || strcmp(description,"stop_firing_flames") == 0) { + RTCWVR_HapticStopEvent("fire_flamethrower"); + }*/ + } else if(strstr(description,"fire_") != NULL) { if(strcmp(description,"fire_11") == 0 || strcmp(description,"fire_2") == 0) { RTCWVR_HapticEvent("fire_pistol", channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); @@ -995,7 +1001,7 @@ void jni_haptic_disable(); void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ) { - Com_Printf( "Vibrate Event Fired: %s", event ); + //Com_Printf( "Vibrate Event Fired: %s", event ); jni_haptic_event(event, position, flags, intensity, angle, yHeight); } @@ -1020,6 +1026,7 @@ void RTCWVR_HapticEnable() if (firstTime) { jni_haptic_enable(); firstTime = false; + jni_haptic_event("fire_pistol", 0, 0, 100, 0, 0); } } diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c b/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c index bbd3ff4..fe61317 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c @@ -3378,8 +3378,11 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent if (wasfiring) { //Stop haptics - trap_Vibrate(0, 0, 0.0, "stop_firing", 0.0, 0.0); - trap_Vibrate(0, 1, 0.0, "stop_firing", 0.0, 0.0); + char *fire_command = (char*)malloc(8 * sizeof(char)); + sprintf(fire_command, "stop_firing_%i", weaponNum); + + trap_Vibrate(0, 0, 0.0, fire_command, 0.0, 0.0); + trap_Vibrate(0, 1, 0.0, "ignore", 0.0, 0.0); wasfiring = qfalse; } } diff --git a/Projects/Android/jni/rtcw/src/game/g_active.c b/Projects/Android/jni/rtcw/src/game/g_active.c index dcc2131..6151251 100644 --- a/Projects/Android/jni/rtcw/src/game/g_active.c +++ b/Projects/Android/jni/rtcw/src/game/g_active.c @@ -60,7 +60,7 @@ void P_DamageFeedback( gentity_t *player ) { // total points of damage shot at the player this frame count = client->damage_blood + client->damage_armor; - if ( count == 0 ) { + if ( count == 0 ) { return; // didn't take any damage } @@ -95,17 +95,31 @@ void P_DamageFeedback( gentity_t *player ) { client->ps.damageCount = count; if (!client->ps.aiChar) { + vec3_t viewangle; + float_t pitch, yaw; + pitch = (abs(angles[PITCH]) / 360.0) - 0.5; + VectorCopy( client->ps.viewangles, viewangle ); + //If this doesn't work try (+ 360) instead of abs + //Com_Printf( "GBRTCW: viewangle yaw = %f", viewangle[YAW] ); + //Com_Printf( "GBRTCW: damage location yaw = %f", angles[YAW] ); + yaw = angles[YAW] - (viewangle[YAW] + 180.0f); + if(yaw < 0.0f) + yaw += 360.0f; + if(yaw > 360.0f) + yaw -= 360.0f; + + //AnglesToAxis( viewangle, wolfkick.axis ); switch(client->lasthurt_mod) { case MOD_SHOTGUN: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_shotgun", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_shotgun", yaw, pitch); break; case MOD_GRENADE: case MOD_GRENADE_SPLASH: case MOD_GRENADE_LAUNCHER: case MOD_GRENADE_PINEAPPLE: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_frag", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_frag", yaw, pitch); break; case MOD_ROCKET: case MOD_ROCKET_SPLASH: @@ -115,12 +129,12 @@ void P_DamageFeedback( gentity_t *player ) { case MOD_BFG_SPLASH: case MOD_MORTAR: case MOD_MORTAR_SPLASH: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_rocket", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_rocket", yaw, pitch); break; case MOD_KNIFE: case MOD_KNIFE2: case MOD_KNIFE_STEALTH: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_knife", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_knife", yaw, pitch); break; case MOD_LUGER: case MOD_COLT: @@ -128,7 +142,7 @@ void P_DamageFeedback( gentity_t *player ) { case MOD_AKIMBO: case MOD_SPEARGUN_CO2: case MOD_TARGET_LASER: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_low_bullet", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_low_bullet", yaw, pitch); break; case MOD_THOMPSON: case MOD_STEN: @@ -137,7 +151,7 @@ void P_DamageFeedback( gentity_t *player ) { case MOD_GARAND: case MOD_SPEARGUN: case MOD_CROSS: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_mid_bullet", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_mid_bullet", yaw, pitch); break; case MOD_RAILGUN: case MOD_SNIPERRIFLE: @@ -148,59 +162,59 @@ void P_DamageFeedback( gentity_t *player ) { case MOD_MACHINEGUN: case MOD_VENOM: case MOD_VENOM_FULL: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_high_bullet", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_high_bullet", yaw, pitch); break; case MOD_FLAMETHROWER: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_flamethrower", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_flamethrower", yaw, pitch); break; case MOD_LIGHTNING: case MOD_TESLA: case MOD_TELEFRAG: case MOD_LOPER_HIT: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_electric", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_electric", yaw, pitch); break; case MOD_EXPLOSIVE: case MOD_DYNAMITE: case MOD_DYNAMITE_SPLASH: case MOD_AIRSTRIKE: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_explosion", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_explosion", yaw, pitch); break; case MOD_GRAPPLE: case MOD_KICKED: case MOD_GRABBER: case MOD_LOPER_LEAP: case MOD_LOPER_GROUND: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_melee", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_melee", yaw, pitch); break; case MOD_FALLING: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_fall", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_fall", yaw, pitch); break; case MOD_SUICIDE: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_death", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_death", yaw, pitch); break; case MOD_POISONGAS: case MOD_WATER: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_gas", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_gas", yaw, pitch); break; case MOD_ZOMBIESPIRIT: case MOD_ZOMBIESPIRIT_SPLASH: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_zombiespirit", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_zombiespirit", yaw, pitch); break; case MOD_SLIME: case MOD_ZOMBIESPIT: case MOD_ZOMBIESPIT_SPLASH: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_slime", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_slime", yaw, pitch); break; case MOD_LAVA: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_fire", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_fire", yaw, pitch); break; case MOD_CRUSH: case MOD_TRIGGER_HURT: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_crush", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_crush", yaw, pitch); break; default: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage", client->ps.damageYaw, client->ps.damagePitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage", yaw, pitch); break; } trap_Vibrate(1000, 0, (count / 255.0) + 0.5f, "ignore", 0.0, 0.0); From b0d53d4bcd5369db134364e4431c09c37e074e09 Mon Sep 17 00:00:00 2001 From: Grant Bagwell Date: Sun, 27 Jun 2021 23:39:21 +0200 Subject: [PATCH 6/8] Haptics (Release Candidate) --- .../Android/jni/RTCWVR/RTCWVR_SurfaceView.c | 29 ++++++++++++++++--- .../Android/jni/rtcw/src/cgame/cg_effects.c | 2 ++ .../Android/jni/rtcw/src/cgame/cg_event.c | 2 ++ .../Android/jni/rtcw/src/cgame/cg_local.h | 1 + .../Android/jni/rtcw/src/cgame/cg_public.h | 3 +- .../Android/jni/rtcw/src/cgame/cg_syscalls.c | 4 +++ .../Android/jni/rtcw/src/cgame/cg_weapons.c | 8 ++--- .../Android/jni/rtcw/src/client/cl_cgame.c | 7 +++++ Projects/Android/jni/rtcw/src/game/g_active.c | 9 +++++- Projects/Android/jni/rtcw/src/game/g_alarm.c | 4 +++ Projects/Android/jni/rtcw/src/game/g_combat.c | 2 ++ Projects/Android/jni/rtcw/src/game/g_items.c | 12 ++++++++ Projects/Android/jni/rtcw/src/game/g_local.h | 1 + Projects/Android/jni/rtcw/src/game/g_mover.c | 12 ++++++-- Projects/Android/jni/rtcw/src/game/g_public.h | 1 + .../Android/jni/rtcw/src/game/g_syscalls.c | 4 +++ .../Android/jni/rtcw/src/server/sv_game.c | 4 ++- 17 files changed, 92 insertions(+), 13 deletions(-) diff --git a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c index 7f63ec9..e20057b 100644 --- a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c +++ b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c @@ -910,10 +910,31 @@ void RTCWVR_Vibrate( int duration, int channel, float intensity ) void RTCWVR_Haptic( int duration, int channel, float intensity, char *description, float yaw, float height ) { - /*if(strstr(description,"camera_shake") == NULL && strstr(description,"ignore") == NULL && strstr(description,"dead_") == NULL) - Com_Printf("GBRTCW: Vibrate Description: %s (Yaw: %f Pitch: %f)", description, yaw, height);*/ + if(strstr(description,"camera_shake") == NULL && strstr(description,"ignore") == NULL && strstr(description,"dead_") == NULL) + Com_Printf("GBRTCW: Vibrate Description: %s (Yaw: %f Pitch: %f)", description, yaw, height); - if(strstr(description,"damage_") != NULL) { + if(strcmp(description,"player_dead") == 0) { + RTCWVR_HapticEvent("player_dead", 0, 0, 100.0f * intensity, yaw, height); + } + else if(strcmp(description,"door_kick") == 0) { + RTCWVR_HapticEvent("kick_door", 0, 0, 100.0f * intensity, yaw, height); + } + else if(strcmp(description,"door_open") == 0) { + RTCWVR_HapticEvent("open_door", 0, 0, 100.0f * intensity, yaw, height); + } + else if(strcmp(description,"alarm_on") == 0) { + RTCWVR_HapticEvent("heartbeat", 0, 0, 100.0f * intensity, yaw, height); + } + else if(strcmp(description,"end_alarm") == 0) { + RTCWVR_HapticStopEvent("heartbeat"); + } + else if(strcmp(description,"give_armor") == 0) { + RTCWVR_HapticEvent(description, 0, 0, 100.0f * intensity, yaw, height); + } + else if(strcmp(description,"give_health") == 0) { + RTCWVR_HapticEvent(description, 0, 0, 100.0f * intensity, yaw, height); + } + else if(strstr(description,"damage_") != NULL) { RTCWVR_HapticEvent(description, 0, 0, 100.0f * intensity, yaw, height); } else if(strstr(description,"stop_firing_") != NULL) { @@ -1001,7 +1022,7 @@ void jni_haptic_disable(); void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ) { - //Com_Printf( "Vibrate Event Fired: %s", event ); + //Com_Printf( "GBRTCW: Vibrate Event Fired: %s", event ); jni_haptic_event(event, position, flags, intensity, angle, yHeight); } diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_effects.c b/Projects/Android/jni/rtcw/src/cgame/cg_effects.c index 6d8a5b4..46a0405 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_effects.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_effects.c @@ -1706,6 +1706,8 @@ void CG_RumbleEfx( float pitch, float yaw ) { VectorCopy( recoil, cg.kickAVel ); // set the recoil cg.recoilPitch -= pitchRecoilAdd; + + } diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_event.c b/Projects/Android/jni/rtcw/src/cgame/cg_event.c index a26961e..2284f5f 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_event.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_event.c @@ -2239,12 +2239,14 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { ByteToDir( es->eventParm, dir ); CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD, qtrue, es->otherEntityNum2 ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.fkickwall ); + trap_Vibrate(1, 1, 0.8, "door_kick", 0.0, -0.5); break; case EV_WOLFKICK_HIT_FLESH: DEBUGNAME( "EV_WOLFKICK_HIT_FLESH" ); CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm, qtrue, es->otherEntityNum2 ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.fkickflesh ); + trap_Vibrate(1, 1, 0.7, "door_kick", 0.0, -0.5); break; case EV_WOLFKICK_MISS: diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_local.h b/Projects/Android/jni/rtcw/src/cgame/cg_local.h index 1205d82..9e6ded2 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_local.h +++ b/Projects/Android/jni/rtcw/src/cgame/cg_local.h @@ -2458,6 +2458,7 @@ int trap_Key_GetCatcher( void ); void trap_Key_SetCatcher( int catcher ); int trap_Key_GetKey( const char *binding ); +int trap_Haptic( int duration, int channel, float intensity, char *description, float yaw, float height); int trap_Vibrate( int duration, int channel, float intensity, char *description, float yaw, float height); int trap_EnableHaptics(); diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_public.h b/Projects/Android/jni/rtcw/src/cgame/cg_public.h index 20e75fa..83a1f64 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_public.h +++ b/Projects/Android/jni/rtcw/src/cgame/cg_public.h @@ -220,7 +220,8 @@ typedef enum { CG_GETMODELINFO, CG_HAPTIC, - CG_HAPTICENABLE + CG_HAPTICENABLE, + CG_HAPTICTRIGGER } cgameImport_t; diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c b/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c index 16c8bd5..6f744cf 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_syscalls.c @@ -553,6 +553,10 @@ int trap_Vibrate( int duration, int channel, float intensity, char *description, return syscall( CG_HAPTIC, duration, channel, PASSFLOAT(intensity), description, PASSFLOAT(yaw), PASSFLOAT(height)); } +int trap_Haptic( int duration, int channel, float intensity, char *description, float yaw, float height) { + return syscall( CG_HAPTICTRIGGER, duration, channel, PASSFLOAT(intensity), description, PASSFLOAT(yaw), PASSFLOAT(height)); +} + int trap_EnableHaptics() { return syscall( CG_HAPTICENABLE ); } diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c b/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c index fe61317..799b5c8 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c @@ -3379,10 +3379,10 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent if (wasfiring) { //Stop haptics char *fire_command = (char*)malloc(8 * sizeof(char)); - sprintf(fire_command, "stop_firing_%i", weaponNum); - - trap_Vibrate(0, 0, 0.0, fire_command, 0.0, 0.0); - trap_Vibrate(0, 1, 0.0, "ignore", 0.0, 0.0); + //sprintf(fire_command, "stop_firing_%i", weaponNum); + //trap_Vibrate(0, 0, 0.0, fire_command, 0.0, 0.0); + trap_Vibrate(0, 0, 0.0, "ignore", 0.0, 0.0); + trap_Vibrate(0, 1, 0.0, "ignore", 0.0, 0.0); wasfiring = qfalse; } } diff --git a/Projects/Android/jni/rtcw/src/client/cl_cgame.c b/Projects/Android/jni/rtcw/src/client/cl_cgame.c index 84eca9d..7d374bf 100644 --- a/Projects/Android/jni/rtcw/src/client/cl_cgame.c +++ b/Projects/Android/jni/rtcw/src/client/cl_cgame.c @@ -873,7 +873,14 @@ int CL_CgameSystemCalls( int *args ) { RTCWVR_Haptic( args[1], args[2], VMF( 3 ), VMA(4), VMF(5), VMF(6) ); return 0; + case CG_HAPTICTRIGGER: + //VMF(1) = Intensity + //VMA(2) = Description + //VMF(3) = Yaw + //VMF(4) = Height + RTCWVR_Haptic( args[1], args[2], VMF( 3 ), VMA(4), VMF(5), VMF(6) ); + return 0; case CG_HAPTICENABLE: RTCWVR_HapticEnable(); return 0; diff --git a/Projects/Android/jni/rtcw/src/game/g_active.c b/Projects/Android/jni/rtcw/src/game/g_active.c index 6151251..8bba2df 100644 --- a/Projects/Android/jni/rtcw/src/game/g_active.c +++ b/Projects/Android/jni/rtcw/src/game/g_active.c @@ -97,12 +97,19 @@ void P_DamageFeedback( gentity_t *player ) { if (!client->ps.aiChar) { vec3_t viewangle; float_t pitch, yaw; - pitch = (abs(angles[PITCH]) / 360.0) - 0.5; + //pitch = (abs(angles[PITCH]) / 360.0) - 0.5; + pitch = angles[PITCH]; + Com_Printf( "GBRTCW: damage location pitch = %f", pitch ); + if(pitch > -90) + pitch = pitch / 180.0; + else if(pitch < -270 && pitch > -360) + pitch = (pitch + 360) / 180; VectorCopy( client->ps.viewangles, viewangle ); //If this doesn't work try (+ 360) instead of abs //Com_Printf( "GBRTCW: viewangle yaw = %f", viewangle[YAW] ); //Com_Printf( "GBRTCW: damage location yaw = %f", angles[YAW] ); + yaw = angles[YAW] - (viewangle[YAW] + 180.0f); if(yaw < 0.0f) yaw += 360.0f; diff --git a/Projects/Android/jni/rtcw/src/game/g_alarm.c b/Projects/Android/jni/rtcw/src/game/g_alarm.c index 2a9df3d..a47f40f 100644 --- a/Projects/Android/jni/rtcw/src/game/g_alarm.c +++ b/Projects/Android/jni/rtcw/src/game/g_alarm.c @@ -163,6 +163,8 @@ void alarmbox_use( gentity_t *ent, gentity_t *other, gentity_t *foo ) { alarmbox_updateparts( ent, qtrue ); if ( other->client ) { G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos3 ); + //TODO GB Add heartbeat + trap_Haptic(1, 0, 1.0f, "alarm_on", 0.0f, 0.0f); } // G_Printf("touched alarmbox\n"); @@ -183,6 +185,8 @@ void alarmbox_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, in ent->takedamage = qfalse; alarmbox_updateparts( ent, qtrue ); + trap_Haptic(1, 0, 1.0f, "end_alarm", 0.0f, 0.0f); + // fire 'death' targets if ( ent->targetdeath ) { t = NULL; diff --git a/Projects/Android/jni/rtcw/src/game/g_combat.c b/Projects/Android/jni/rtcw/src/game/g_combat.c index 380b906..cfc89b9 100644 --- a/Projects/Android/jni/rtcw/src/game/g_combat.c +++ b/Projects/Android/jni/rtcw/src/game/g_combat.c @@ -359,8 +359,10 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int // if (self->client && self->client->hook) // Weapon_HookFree(self->client->hook); + trap_Haptic(1,0,1.0f,"player_dead",0,0); self->client->ps.pm_type = PM_DEAD; + if ( attacker ) { killer = attacker->s.number; if ( attacker->client ) { diff --git a/Projects/Android/jni/rtcw/src/game/g_items.c b/Projects/Android/jni/rtcw/src/game/g_items.c index 2b5143a..109992b 100644 --- a/Projects/Android/jni/rtcw/src/game/g_items.c +++ b/Projects/Android/jni/rtcw/src/game/g_items.c @@ -498,6 +498,12 @@ int Pickup_Weapon( gentity_t *ent, gentity_t *other ) { int Pickup_Health( gentity_t *ent, gentity_t *other ) { int max; int quantity = 0; + float intensity = 1.0f; + //Trigger a haptic for armor pickup + intensity = ent->count / 100; + if(intensity < 0.4) + intensity = 0.4f; + trap_Haptic(1,0,intensity,"give_health",0.0f, 0.0f); // small and mega healths will go over the max if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) { @@ -548,6 +554,12 @@ int Pickup_Health( gentity_t *ent, gentity_t *other ) { //====================================================================== int Pickup_Armor( gentity_t *ent, gentity_t *other ) { + + //Trigger a haptic for armor pickup + if ( other->client->ps.stats[STAT_ARMOR] < 100 ) { + trap_Haptic(1,0,1.0f,"give_armor",0.0f, 0.0f); + } + other->client->ps.stats[STAT_ARMOR] += ent->item->quantity; // if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * 2 ) { // other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * 2; diff --git a/Projects/Android/jni/rtcw/src/game/g_local.h b/Projects/Android/jni/rtcw/src/game/g_local.h index 83e791d..008761d 100644 --- a/Projects/Android/jni/rtcw/src/game/g_local.h +++ b/Projects/Android/jni/rtcw/src/game/g_local.h @@ -1215,6 +1215,7 @@ qboolean trap_GetEntityToken( char *buffer, int bufferSize ); qboolean trap_GetTag( int clientNum, char *tagName, orientation_t * or ); //int trap_Vibrate(int duration, int channel, float intensity ); +int trap_Haptic( int duration, int channel, float intensity, char *description, float yaw, float height); int trap_Vibrate( int duration, int channel, float intensity, char *description, float yaw, float height); int trap_EnableHaptics(); diff --git a/Projects/Android/jni/rtcw/src/game/g_mover.c b/Projects/Android/jni/rtcw/src/game/g_mover.c index d4be299..55b765a 100644 --- a/Projects/Android/jni/rtcw/src/game/g_mover.c +++ b/Projects/Android/jni/rtcw/src/game/g_mover.c @@ -35,6 +35,9 @@ If you have questions concerning this license or the applicable additional terms */ #include "g_local.h" +#include "../../../RTCWVR/VrClientInfo.h" + +extern vr_client_info_t* gVR; char *hintStrings[] = { "", // HINT_NONE @@ -769,13 +772,16 @@ void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) { if ( kicked ) { f = 2000.0 / ent->gDuration; // double speed when kicked open ent->s.apos.trDuration = ent->gDuration / 2.0; + } else if ( soft ) { f = 500.0 / ent->gDuration; // 1/2 speed when soft opened ent->s.apos.trDuration = ent->gDuration * 2; + //trap_Vibrate(1, gVR->right_handed ? 0 : 1, 0.5f, "door_open", 0, 0); //I've reversed the hands as I presume you will open it with the hand your gun isn't in. } else { f = 1000.0 / ent->gDuration; // ent->s.apos.trDuration = ent->gDurationBack; // (SA) durationback? ent->s.apos.trDuration = ent->gDuration; + //trap_Vibrate(1, gVR->right_handed ? 0 : 1, 0.75f, "door_open", 0, 0); //not sure what this is } VectorScale( ent->rotate, f * ent->angle, ent->s.apos.trDelta ); ent->s.apos.trType = TR_LINEAR_STOP; @@ -2200,17 +2206,19 @@ void G_TryDoor( gentity_t *ent, gentity_t *other, gentity_t *activator ) { Use_BinaryMover( ent->teammaster, activator, activator ); G_UseTargets( ent->teammaster, activator ); - } else + } + else { ent->active = qtrue; if ( walking ) { ent->flags |= FL_SOFTACTIVATE; // no noise + trap_Vibrate(1, gVR->right_handed ? 0 : 1, 1.0f, "door_open", 0, 0); //I've reversed the hands as I presume you will open it with the hand your gun isn't in. } else { + trap_Vibrate(1, gVR->right_handed ? 0 : 1, 0.6f, "door_open", 0, 0); //I've reversed the hands as I presume you will open it with the hand your gun isn't in. if ( activator ) { soundrange = HEAR_RANGE_DOOR_OPEN; } } - Use_BinaryMover( ent, activator, activator ); G_UseTargets( ent, activator ); } diff --git a/Projects/Android/jni/rtcw/src/game/g_public.h b/Projects/Android/jni/rtcw/src/game/g_public.h index 003a46e..f0be592 100644 --- a/Projects/Android/jni/rtcw/src/game/g_public.h +++ b/Projects/Android/jni/rtcw/src/game/g_public.h @@ -250,6 +250,7 @@ typedef enum { G_GETTAG, G_HAPTIC, + G_HAPTICTRIGGER, G_FULL_HAPTIC, BOTLIB_SETUP = 200, // ( void ); diff --git a/Projects/Android/jni/rtcw/src/game/g_syscalls.c b/Projects/Android/jni/rtcw/src/game/g_syscalls.c index bb1a293..ba351b3 100644 --- a/Projects/Android/jni/rtcw/src/game/g_syscalls.c +++ b/Projects/Android/jni/rtcw/src/game/g_syscalls.c @@ -259,6 +259,10 @@ int trap_Vibrate( int duration, int channel, float intensity, char *description, return syscall( G_HAPTIC, duration, channel, PASSFLOAT(intensity), description, PASSFLOAT(yaw), PASSFLOAT(height)); } +int trap_Haptic( int duration, int channel, float intensity, char *description, float yaw, float height) { + return syscall( G_HAPTICTRIGGER, duration, channel, PASSFLOAT(intensity), description, PASSFLOAT(yaw), PASSFLOAT(height)); +} + // BotLib traps start here int trap_BotLibSetup( void ) { return syscall( BOTLIB_SETUP ); diff --git a/Projects/Android/jni/rtcw/src/server/sv_game.c b/Projects/Android/jni/rtcw/src/server/sv_game.c index c038b4a..bb3acce 100644 --- a/Projects/Android/jni/rtcw/src/server/sv_game.c +++ b/Projects/Android/jni/rtcw/src/server/sv_game.c @@ -483,7 +483,9 @@ int SV_GameSystemCalls( int *args ) { RTCWVR_Haptic( args[1], args[2], VMF( 3 ), VMA(4), VMF(5), VMF(6) ); return 0; - + case G_HAPTICTRIGGER: + RTCWVR_Haptic( args[1], args[2], VMF( 3 ), VMA(4), VMF(5), VMF(6) ); + return 0; //==================================== case BOTLIB_SETUP: From 600475e91c2985de41cd3656175605d52e005851 Mon Sep 17 00:00:00 2001 From: Grant Bagwell Date: Fri, 16 Jul 2021 00:54:51 +0200 Subject: [PATCH 7/8] Haptics hopefully finished + Cybershoes Also changed doors to always soft open --- .../Android/jni/RTCWVR/RTCWVR_SurfaceView.c | 22 ++++++++------ Projects/Android/jni/RTCWVR/VrInput.h | 6 +++- Projects/Android/jni/RTCWVR/VrInputCommon.c | 17 ++++++++++- Projects/Android/jni/RTCWVR/VrInputDefault.c | 10 +++++-- .../Android/jni/rtcw/src/cgame/cg_event.c | 25 ++++++++-------- .../Android/jni/rtcw/src/cgame/cg_weapons.c | 2 ++ Projects/Android/jni/rtcw/src/game/g_active.c | 7 +++-- Projects/Android/jni/rtcw/src/game/g_cmds.c | 5 ++-- Projects/Android/jni/rtcw/src/game/g_items.c | 30 +++++++++++++++++-- Projects/Android/jni/rtcw/src/game/g_mover.c | 10 ++++--- 10 files changed, 96 insertions(+), 38 deletions(-) diff --git a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c index e20057b..217960d 100644 --- a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c +++ b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c @@ -928,12 +928,13 @@ void RTCWVR_Haptic( int duration, int channel, float intensity, char *descriptio else if(strcmp(description,"end_alarm") == 0) { RTCWVR_HapticStopEvent("heartbeat"); } - else if(strcmp(description,"give_armor") == 0) { - RTCWVR_HapticEvent(description, 0, 0, 100.0f * intensity, yaw, height); - } - else if(strcmp(description,"give_health") == 0) { - RTCWVR_HapticEvent(description, 0, 0, 100.0f * intensity, yaw, height); + else if(strcmp(description,"switch_weapon") == 0 || strcmp(description,"pickup_item") == 0) { + RTCWVR_HapticEvent(description, channel == 1 ? 2 : 1, 0, 100.0f * intensity, 0, 0); } + else if(strcmp(description,"give_armor") == 0 || strcmp(description,"give_food") == 0 || + strcmp(description,"give_drink") == 0 || strcmp(description,"give_health") == 0 || strcmp(description,"pickup_treasure") == 0) { + RTCWVR_HapticEvent(description, 0, 0, 100.0f * intensity, yaw, height); + } else if(strstr(description,"damage_") != NULL) { RTCWVR_HapticEvent(description, 0, 0, 100.0f * intensity, yaw, height); } @@ -1651,7 +1652,7 @@ void * AppThreadFunction(void * parm ) { // This app will handle android gamepad events itself. vrapi_SetPropertyInt(&gAppState.Java, VRAPI_EAT_NATIVE_GAMEPAD_EVENTS, 0); - //Set device defaults + //Set device defaults if (vrapi_GetSystemPropertyInt(&java, VRAPI_SYS_PROP_DEVICE_TYPE) == VRAPI_DEVICE_TYPE_OCULUSQUEST) { if (SS_MULTIPLIER == 0.0f) @@ -1664,6 +1665,7 @@ void * AppThreadFunction(void * parm ) { if (SS_MULTIPLIER == 0.0f) { //Lower to allow 90hz to work nicely + //GB Override as refresh is now 72 by default SS_MULTIPLIER = 1.0f; } else if (SS_MULTIPLIER > 1.2F) @@ -1733,7 +1735,7 @@ void * AppThreadFunction(void * parm ) { if (REFRESH == 0 || REFRESH > maximumSupportRefresh) { - REFRESH = maximumSupportRefresh; + REFRESH = 72.0; } //----------------------------------------------------------------------------------------------------------- @@ -1864,12 +1866,14 @@ void RTCWVR_getTrackedRemotesOrientation() {//Get info for tracked remotes switch (vr_control_scheme->integer) { case RIGHT_HANDED_DEFAULT: - HandleInput_Default(&rightTrackedRemoteState_new, &rightTrackedRemoteState_old, &rightRemoteTracking_new, + HandleInput_Default(&footTrackedRemoteState_new, &footTrackedRemoteState_old, + &rightTrackedRemoteState_new, &rightTrackedRemoteState_old, &rightRemoteTracking_new, &leftTrackedRemoteState_new, &leftTrackedRemoteState_old, &leftRemoteTracking_new, ovrButton_A, ovrButton_B, ovrButton_X, ovrButton_Y); break; case LEFT_HANDED_DEFAULT: - HandleInput_Default(&leftTrackedRemoteState_new, &leftTrackedRemoteState_old, &leftRemoteTracking_new, + HandleInput_Default(&footTrackedRemoteState_new, &footTrackedRemoteState_old, + &leftTrackedRemoteState_new, &leftTrackedRemoteState_old, &leftRemoteTracking_new, &rightTrackedRemoteState_new, &rightTrackedRemoteState_old, &rightRemoteTracking_new, ovrButton_X, ovrButton_Y, ovrButton_A, ovrButton_B); break; diff --git a/Projects/Android/jni/RTCWVR/VrInput.h b/Projects/Android/jni/RTCWVR/VrInput.h index 54634d0..781ce62 100644 --- a/Projects/Android/jni/RTCWVR/VrInput.h +++ b/Projects/Android/jni/RTCWVR/VrInput.h @@ -21,6 +21,9 @@ ovrInputStateTrackedRemote rightTrackedRemoteState_old; ovrInputStateTrackedRemote rightTrackedRemoteState_new; ovrTracking rightRemoteTracking_new; +ovrInputStateGamepad footTrackedRemoteState_old; +ovrInputStateGamepad footTrackedRemoteState_new; + ovrDeviceID controllerIDs[2]; float remote_movementSideways; @@ -35,7 +38,8 @@ void sendButtonActionSimple(const char* action); void acquireTrackedRemotesData(const ovrMobile *Ovr, double displayTime); -void HandleInput_Default( ovrInputStateTrackedRemote *pDominantTrackedRemoteNew, ovrInputStateTrackedRemote *pDominantTrackedRemoteOld, ovrTracking* pDominantTracking, +void HandleInput_Default( ovrInputStateGamepad *pFootTrackingNew, ovrInputStateGamepad *pFootTrackingOld, + ovrInputStateTrackedRemote *pDominantTrackedRemoteNew, ovrInputStateTrackedRemote *pDominantTrackedRemoteOld, ovrTracking* pDominantTracking, ovrInputStateTrackedRemote *pOffTrackedRemoteNew, ovrInputStateTrackedRemote *pOffTrackedRemoteOld, ovrTracking* pOffTracking, int domButton1, int domButton2, int offButton1, int offButton2 ); diff --git a/Projects/Android/jni/RTCWVR/VrInputCommon.c b/Projects/Android/jni/RTCWVR/VrInputCommon.c index 1396ba9..7bded56 100644 --- a/Projects/Android/jni/RTCWVR/VrInputCommon.c +++ b/Projects/Android/jni/RTCWVR/VrInputCommon.c @@ -94,7 +94,22 @@ void acquireTrackedRemotesData(const ovrMobile *Ovr, double displayTime) {//The break; } - if (cap.Type == ovrControllerType_TrackedRemote) { + if (cap.Type == ovrControllerType_Gamepad) { + + ovrInputGamepadCapabilities remoteCaps; + remoteCaps.Header = cap; + if (vrapi_GetInputDeviceCapabilities(Ovr, &remoteCaps.Header) >= 0) { + // remote is connected + ovrInputStateGamepad remoteState; + remoteState.Header.ControllerType = ovrControllerType_Gamepad; + if ( vrapi_GetCurrentInputState( Ovr, cap.DeviceID, &remoteState.Header ) >= 0 ) + { + // act on device state returned in remoteState + footTrackedRemoteState_new = remoteState; + } + } + } + else if (cap.Type == ovrControllerType_TrackedRemote) { ovrTracking remoteTracking; ovrInputStateTrackedRemote trackedRemoteState; trackedRemoteState.Header.ControllerType = ovrControllerType_TrackedRemote; diff --git a/Projects/Android/jni/RTCWVR/VrInputDefault.c b/Projects/Android/jni/RTCWVR/VrInputDefault.c index 2889044..9eabd48 100644 --- a/Projects/Android/jni/RTCWVR/VrInputDefault.c +++ b/Projects/Android/jni/RTCWVR/VrInputDefault.c @@ -27,7 +27,8 @@ void SV_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const ve void RTCWVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight ); -void HandleInput_Default( ovrInputStateTrackedRemote *pDominantTrackedRemoteNew, ovrInputStateTrackedRemote *pDominantTrackedRemoteOld, ovrTracking* pDominantTracking, +void HandleInput_Default( ovrInputStateGamepad *pFootTrackingNew, ovrInputStateGamepad *pFootTrackingOld, + ovrInputStateTrackedRemote *pDominantTrackedRemoteNew, ovrInputStateTrackedRemote *pDominantTrackedRemoteOld, ovrTracking* pDominantTracking, ovrInputStateTrackedRemote *pOffTrackedRemoteNew, ovrInputStateTrackedRemote *pOffTrackedRemoteOld, ovrTracking* pOffTracking, int domButton1, int domButton2, int offButton1, int offButton2 ) @@ -444,6 +445,8 @@ void HandleInput_Default( ovrInputStateTrackedRemote *pDominantTrackedRemoteNew, } + + //We need to record if we have started firing primary so that releasing trigger will stop firing, if user has pushed grip //in meantime, then it wouldn't stop the gun firing and it would get stuck static qboolean firing = false; @@ -549,8 +552,8 @@ void HandleInput_Default( ovrInputStateTrackedRemote *pDominantTrackedRemoteNew, //Apply a filter and quadratic scaler so small movements are easier to make float dist = length(pSecondaryJoystick->x, pSecondaryJoystick->y); float nlf = nonLinearFilter(dist); - float x = nlf * pSecondaryJoystick->x; - float y = nlf * pSecondaryJoystick->y; + float x = (nlf * pSecondaryJoystick->x) + pFootTrackingNew->LeftJoystick.x; + float y = (nlf * pSecondaryJoystick->y) - pFootTrackingNew->LeftJoystick.y; vr.player_moving = (fabs(x) + fabs(y)) > 0.05f; @@ -616,6 +619,7 @@ void HandleInput_Default( ovrInputStateTrackedRemote *pDominantTrackedRemoteNew, handleTrackedControllerButton(pOffTrackedRemoteNew, pOffTrackedRemoteOld, ovrButton_Trigger, K_SHIFT); + } else { if (pOffTrackedRemoteNew->Buttons & ovrButton_Trigger) { diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_event.c b/Projects/Android/jni/rtcw/src/cgame/cg_event.c index 2284f5f..c037381 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_event.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_event.c @@ -32,9 +32,10 @@ If you have questions concerning this license or the applicable additional terms #include "cg_local.h" #include "../ui/ui_shared.h" // for Menus_CloseAll() +#include "../../../RTCWVR/VrClientInfo.h" extern int hWeaponSnd; - +extern vr_client_info_t *cgVR; extern void CG_Tracer( vec3_t source, vec3_t dest, int sparks ); //========================================================================== @@ -1979,17 +1980,17 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) { // client will get this message if reloading while using an alternate weapon // client should voluntarily switch back to primary at that point switch ( es->weapon ) { - case WP_SNOOPERSCOPE: - newweap = WP_GARAND; - break; - case WP_SNIPERRIFLE: - newweap = WP_MAUSER; - break; - case WP_FG42SCOPE: - newweap = WP_FG42; - break; - default: - break; + case WP_SNOOPERSCOPE: + newweap = WP_GARAND; + break; + case WP_SNIPERRIFLE: + newweap = WP_MAUSER; + break; + case WP_FG42SCOPE: + newweap = WP_FG42; + break; + default: + break; } // TTimo diff --git a/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c b/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c index 799b5c8..2d3f1d9 100644 --- a/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c +++ b/Projects/Android/jni/rtcw/src/cgame/cg_weapons.c @@ -4359,6 +4359,7 @@ void CG_SetSniperZoom( int lastweap, int newweap ) { default: Com_Printf("**WEAPON EVENT** cgVR->scopeengaged = qfalse"); cgVR->scopeengaged = qfalse; + trap_Haptic(1, cgVR->right_handed ? 1 : 0, 0.7f, "switch_weapon", 0.0f, 0.0f); break; } @@ -4372,6 +4373,7 @@ void CG_SetSniperZoom( int lastweap, int newweap ) { switch ( newweap ) { default: + return; // no sniper zoom, get out. case WP_SNIPERRIFLE: diff --git a/Projects/Android/jni/rtcw/src/game/g_active.c b/Projects/Android/jni/rtcw/src/game/g_active.c index 8bba2df..a6cdbb6 100644 --- a/Projects/Android/jni/rtcw/src/game/g_active.c +++ b/Projects/Android/jni/rtcw/src/game/g_active.c @@ -102,7 +102,7 @@ void P_DamageFeedback( gentity_t *player ) { Com_Printf( "GBRTCW: damage location pitch = %f", pitch ); if(pitch > -90) pitch = pitch / 180.0; - else if(pitch < -270 && pitch > -360) + else if(pitch <= -270 && pitch > -360) pitch = (pitch + 360) / 180; VectorCopy( client->ps.viewangles, viewangle ); //If this doesn't work try (+ 360) instead of abs @@ -119,7 +119,7 @@ void P_DamageFeedback( gentity_t *player ) { //AnglesToAxis( viewangle, wolfkick.axis ); switch(client->lasthurt_mod) { - case MOD_SHOTGUN: + case MOD_SHOTGUN: trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_shotgun", yaw, pitch); break; case MOD_GRENADE: @@ -186,6 +186,7 @@ void P_DamageFeedback( gentity_t *player ) { case MOD_AIRSTRIKE: trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_explosion", yaw, pitch); break; + case MOD_GAUNTLET: case MOD_GRAPPLE: case MOD_KICKED: case MOD_GRABBER: @@ -221,7 +222,7 @@ void P_DamageFeedback( gentity_t *player ) { break; default: - trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage", yaw, pitch); + trap_Vibrate(1000, 1, (count / 255.0) + 0.5f, "damage_mid_bullet", yaw, pitch); break; } trap_Vibrate(1000, 0, (count / 255.0) + 0.5f, "ignore", 0.0, 0.0); diff --git a/Projects/Android/jni/rtcw/src/game/g_cmds.c b/Projects/Android/jni/rtcw/src/game/g_cmds.c index 5dfe7ad..1afaae6 100644 --- a/Projects/Android/jni/rtcw/src/game/g_cmds.c +++ b/Projects/Android/jni/rtcw/src/game/g_cmds.c @@ -1407,7 +1407,8 @@ void Cmd_Activate_f( gentity_t *ent ) { if ( ( ( Q_stricmp( traceEnt->classname, "func_door" ) == 0 ) || ( Q_stricmp( traceEnt->classname, "func_door_rotating" ) == 0 ) ) ) { //----(SA) modified - if ( walking ) { + //GB Force this on as they don't hve a key which seems unfair in VR + if ( walking || 1 == 1) { traceEnt->flags |= FL_SOFTACTIVATE; // no noise } G_TryDoor( traceEnt, ent, ent ); // (door,other,activator) @@ -1419,7 +1420,7 @@ void Cmd_Activate_f( gentity_t *ent ) { // Use_BinaryMover (traceEnt, ent, ent); // traceEnt->active = qtrue; } else if ( !Q_stricmp( traceEnt->classname, "func_invisible_user" ) ) { - if ( walking ) { + if ( walking || 1 == 1) { traceEnt->flags |= FL_SOFTACTIVATE; // no noise } traceEnt->use( traceEnt, ent, ent ); diff --git a/Projects/Android/jni/rtcw/src/game/g_items.c b/Projects/Android/jni/rtcw/src/game/g_items.c index 109992b..f2a91e2 100644 --- a/Projects/Android/jni/rtcw/src/game/g_items.c +++ b/Projects/Android/jni/rtcw/src/game/g_items.c @@ -39,7 +39,7 @@ If you have questions concerning this license or the applicable additional terms */ #include "g_local.h" - +#include "../../../RTCWVR/VrClientInfo.h" #define RESPAWN_SP -1 @@ -54,7 +54,7 @@ If you have questions concerning this license or the applicable additional terms #define RESPAWN_PARTIAL 998 // for multi-stage ammo/health #define RESPAWN_PARTIAL_DONE 999 // for multi-stage ammo/health - +extern vr_client_info_t* gVR; //====================================================================== int Pickup_Powerup( gentity_t *ent, gentity_t *other ) { @@ -87,6 +87,7 @@ int Pickup_Powerup( gentity_t *ent, gentity_t *other ) { // brandy also gives a little health (10) if ( ent->item->giTag == PW_NOFATIGUE ) { if ( Q_stricmp( ent->item->classname, "item_stamina_brandy" ) == 0 ) { + trap_Haptic(1, 0, 0.6, "give_drink", 0.0f, 0.0f); other->health += 10; if ( other->health > other->client->ps.stats[STAT_MAX_HEALTH] ) { other->health = other->client->ps.stats[STAT_MAX_HEALTH]; @@ -169,6 +170,7 @@ int Pickup_Powerup( gentity_t *ent, gentity_t *other ) { //====================================================================== int Pickup_Key( gentity_t *ent, gentity_t *other ) { other->client->ps.stats[STAT_KEYS] |= ( 1 << ent->item->giTag ); + trap_Haptic(1, gVR->right_handed ? 1 : 0, 1.0f, "pickup_item", 0.0f, 0.0f); if ( g_gametype.integer == GT_SINGLE_PLAYER ) { if ( !( ent->spawnflags & 8 ) ) { return RESPAWN_SP; @@ -187,6 +189,7 @@ Pickup_Clipboard */ int Pickup_Clipboard( gentity_t *ent, gentity_t *other ) { + trap_Haptic(1, gVR->right_handed ? 1 : 0, 1.0f, "pickup_item", 0.0f, 0.0f); if ( ent->spawnflags & 4 ) { return 0; // leave in world @@ -203,6 +206,7 @@ Pickup_Treasure int Pickup_Treasure( gentity_t *ent, gentity_t *other ) { gentity_t *player = AICast_FindEntityForName( "player" ); player->numTreasureFound++; + trap_Haptic(1, 0, 1.0, "pickup_treasure", 0.0f, 0.0f); G_SendMissionStats(); return RESPAWN_SP; // no respawn } @@ -218,6 +222,7 @@ void UseHoldableItem( gentity_t *ent, int item ) { switch ( item ) { case HI_WINE: // 1921 Chateu Lafite - gives 25 pts health up to max health ent->health += 25; + trap_Haptic(1, 0, 1.0, "give_drink", 0.0f, 0.0f); if ( ent->health > ent->client->ps.stats[STAT_MAX_HEALTH] ) { ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; } @@ -256,6 +261,7 @@ int Pickup_Holdable( gentity_t *ent, gentity_t *other ) { other->client->ps.holding = item->giTag; other->client->ps.stats[STAT_HOLDABLE_ITEM] |= ( 1 << ent->item->giTag ); //----(SA) added + trap_Haptic(1, gVR->right_handed ? 1 : 0, 1.0f, "pickup_item", 0.0f, 0.0f); if ( g_gametype.integer == GT_SINGLE_PLAYER ) { if ( !( ent->spawnflags & 8 ) ) { @@ -386,6 +392,7 @@ int Pickup_Ammo( gentity_t *ent, gentity_t *other ) { } Add_Ammo( other, ent->item->giTag, quantity, qfalse ); //----(SA) modified + trap_Haptic(1, gVR->right_handed ? 1 : 0, 1.0f, "pickup_item", 0.0f, 0.0f); // single player has no respawns (SA) if ( g_gametype.integer == GT_SINGLE_PLAYER ) { @@ -468,6 +475,7 @@ int Pickup_Weapon( gentity_t *ent, gentity_t *other ) { //----(SA) end Add_Ammo( other, weapon, quantity, !alreadyHave ); + trap_Haptic(1, gVR->right_handed ? 1 : 0, 1.0f, "pickup_item", 0.0f, 0.0f); //----(SA) no hook // if (weapon == WP_GRAPPLING_HOOK) @@ -503,7 +511,23 @@ int Pickup_Health( gentity_t *ent, gentity_t *other ) { intensity = ent->count / 100; if(intensity < 0.4) intensity = 0.4f; - trap_Haptic(1,0,intensity,"give_health",0.0f, 0.0f); + + //Need to find food and see if different (cheese and other stuff) + //item_health / icons/iconh_med (25) + //item_health_wall / icons/iconh_wall (25) + //item_health_turkey / icons/iconh_turkey (15) + //item_health_breadandmeat / "icons/iconh_breadandmeat (10) + if(strstr(ent->item->classname,"turkey") || + strstr(ent->item->classname,"bread") || + strstr(ent->item->classname,"meat") || + strstr(ent->item->classname,"wine") || + strstr(ent->item->classname,"latour")) { + //quantity (25) + trap_Haptic(1, 0, intensity, "give_food", 0.0f, 0.0f); + } else { + trap_Haptic(1, 0, intensity, "give_health", 0.0f, 0.0f); + } + // small and mega healths will go over the max if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) { diff --git a/Projects/Android/jni/rtcw/src/game/g_mover.c b/Projects/Android/jni/rtcw/src/game/g_mover.c index 55b765a..a089f3c 100644 --- a/Projects/Android/jni/rtcw/src/game/g_mover.c +++ b/Projects/Android/jni/rtcw/src/game/g_mover.c @@ -1393,7 +1393,7 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) { if ( kicked ) { ent->teammaster->flags |= FL_KICKACTIVATE; } - if ( soft ) { + else if ( soft || 1 == 1) { ent->teammaster->flags |= FL_SOFTACTIVATE; } @@ -2210,11 +2210,13 @@ void G_TryDoor( gentity_t *ent, gentity_t *other, gentity_t *activator ) { else { ent->active = qtrue; - if ( walking ) { + if ( walking || 1 == 1) { ent->flags |= FL_SOFTACTIVATE; // no noise - trap_Vibrate(1, gVR->right_handed ? 0 : 1, 1.0f, "door_open", 0, 0); //I've reversed the hands as I presume you will open it with the hand your gun isn't in. + if(gVR) + trap_Vibrate(1, gVR->right_handed ? 0 : 1, 0.3f, "door_open", 0, 0); //I've reversed the hands as I presume you will open it with the hand your gun isn't in. } else { - trap_Vibrate(1, gVR->right_handed ? 0 : 1, 0.6f, "door_open", 0, 0); //I've reversed the hands as I presume you will open it with the hand your gun isn't in. + if(gVR) + trap_Vibrate(1, gVR->right_handed ? 0 : 1, 0.5f, "door_open", 0, 0); //I've reversed the hands as I presume you will open it with the hand your gun isn't in. if ( activator ) { soundrange = HEAR_RANGE_DOOR_OPEN; } From 3fc79e52ab4c5d5dd7f37461e490c3b068f54ab5 Mon Sep 17 00:00:00 2001 From: Grant Bagwell Date: Mon, 19 Jul 2021 20:20:31 +0200 Subject: [PATCH 8/8] Adjusting default SS from Bummsers test values --- Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c | 9 ++++----- Projects/Android/jni/RTCWVR/VrCompositor.c | 8 +++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c index 217960d..b21bb97 100644 --- a/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c +++ b/Projects/Android/jni/RTCWVR/RTCWVR_SurfaceView.c @@ -1664,13 +1664,12 @@ void * AppThreadFunction(void * parm ) { { if (SS_MULTIPLIER == 0.0f) { - //Lower to allow 90hz to work nicely - //GB Override as refresh is now 72 by default - SS_MULTIPLIER = 1.0f; + //GB Override as refresh is now 72 by default as we decided a higher res is better as 90hz has stutters + SS_MULTIPLIER = 1.35f; } - else if (SS_MULTIPLIER > 1.2F) + else if (SS_MULTIPLIER > 1.5f) { - SS_MULTIPLIER = 1.2f; + SS_MULTIPLIER = 1.5f; } } else { //Don't know what headset this is!? abort diff --git a/Projects/Android/jni/RTCWVR/VrCompositor.c b/Projects/Android/jni/RTCWVR/VrCompositor.c index 28125db..060dabf 100644 --- a/Projects/Android/jni/RTCWVR/VrCompositor.c +++ b/Projects/Android/jni/RTCWVR/VrCompositor.c @@ -154,7 +154,13 @@ ovrLayerCylinder2 BuildCylinderLayer( ovrRenderer * cylinderRenderer, const float density = 4500.0f; const float rotateYaw = 0.0f; const float radius = 4.0f; - const float distance = vr_screen_dist ? -vr_screen_dist->value : -3.5f; + //GB Hacky Override + float screen_offset = 0; + if(textureWidth > 1900) + { + screen_offset = -2.625f; + } + const float distance = vr_screen_dist ? -vr_screen_dist->value + screen_offset : -3.5f + screen_offset; const ovrVector3f translation = { 0.0f, playerHeight/2, distance };