Last active
December 15, 2021 19:22
-
-
Save JLChnToZ/1bd960c814b054c6253bfe87b23498ec to your computer and use it in GitHub Desktop.
Cheat detector for platform-like games in VRChat.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UdonSharp; | |
using UnityEngine; | |
using VRC.SDKBase; | |
using VRC.Udon; | |
namespace JLChnToZ.VRC { | |
[UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)] | |
public class CheatDetector : UdonSharpBehaviour { | |
[Tooltip("Detects open menu stucking cheat.")] | |
[SerializeField] bool menuCheatDetect = true; | |
[Tooltip("Interval of actively challenges player opening main menu, 0 to disable.")] | |
[SerializeField] float menuCheatCheckInterval = 5; | |
[Space, Tooltip("Detects flying avatar. Requires a non-kinematic rigidbody and a slightly downward offsetted small collider attached to this game object.")] | |
[SerializeField] bool flyingAvatarDetect = true; | |
[Space, Tooltip("Game objects with udons that wants to know if player is flying, will send OnFlyingDetected custom event to them when detected. If you are using U#, you can use RegisterCallback(this) instead.")] | |
[SerializeField] GameObject[] callbackGameObjects; | |
Component[] callbacks; | |
new Rigidbody rigidbody; | |
VRCPlayerApi localPlayer; | |
Vector3 lastPosition; | |
int menuHackForgiveness; | |
float menuHackStrictForgiveness; | |
void Start() { | |
if (callbackGameObjects != null) | |
foreach (var callbackGO in callbackGameObjects) { | |
var udonBehaviours = callbackGO.GetComponents(typeof(UdonBehaviour)); | |
if (udonBehaviours.Length == 0) continue; | |
if (callbacks == null || callbacks.Length == 0) { | |
callbacks = udonBehaviours; | |
continue; | |
} | |
var temp = new Component[callbacks.Length + udonBehaviours.Length]; | |
Array.Copy(callbacks, temp, callbacks.Length); | |
Array.Copy(udonBehaviours, 0, temp, callbacks.Length, udonBehaviours.Length); | |
callbacks = temp; | |
} | |
} | |
void OnEnable() { | |
Debug.Log("[Cheat Detector] Start initialize cheat detector."); | |
if (flyingAvatarDetect) { | |
if (rigidbody == null) rigidbody = GetComponent<Rigidbody>(); | |
rigidbody.isKinematic = false; | |
rigidbody.freezeRotation = true; | |
rigidbody.mass = 0.00001F; // Extra low mass to minimize affects on other on scene physic objects. | |
} | |
menuHackForgiveness = 0; | |
menuHackStrictForgiveness = 0; | |
lastPosition = new Vector3(float.NaN, float.NaN, float.NaN); | |
Debug.Log("[Cheat Detector] Initialize completed, start detecting cheats."); | |
} | |
void OnDisable() { | |
Debug.Log("[Cheat Detector] Cheat detector disabled."); | |
if (flyingAvatarDetect) { | |
rigidbody.isKinematic = true; | |
} | |
} | |
void FixedUpdate() { | |
if (!Utilities.IsValid(localPlayer)) return; | |
var position = localPlayer.GetPosition(); | |
var velocity = localPlayer.GetVelocity(); | |
var deltaTime = Time.fixedUnscaledDeltaTime; | |
// Menu hack detection | |
if (menuCheatDetect) { | |
if (position == lastPosition) { | |
// If player stays at the exact same position but it is not grounded for certain amount of time, ban! | |
if (!localPlayer.IsPlayerGrounded()) { | |
if(++menuHackForgiveness > 5) | |
FlyingDetected(); | |
} else { | |
// Actively challenges player to set velocity, if grounded state is still setted afterwards, ban! | |
if (menuCheatCheckInterval > 0 && (menuHackStrictForgiveness += deltaTime) >= menuCheatCheckInterval) { | |
localPlayer.SetVelocity(velocity); | |
if (localPlayer.IsPlayerGrounded()) | |
FlyingDetected(); | |
menuHackStrictForgiveness = 0; | |
} | |
menuHackForgiveness = 0; | |
} | |
} else { | |
menuHackForgiveness = 0; | |
menuHackStrictForgiveness = 0; | |
} | |
lastPosition = position; | |
} | |
// Flying avatar detector update position | |
if (flyingAvatarDetect) { | |
if (velocity.y < 0) position += new Vector3(0, velocity.y * deltaTime, 0); | |
transform.position = position; | |
rigidbody.position = position; | |
rigidbody.velocity = Vector3.zero; | |
} | |
} | |
public void RegisterCallback(UdonSharpBehaviour ub) { | |
if (callbacks == null || callbacks.Length == 0) { | |
callbacks = new [] { ub }; | |
return; | |
} | |
var temp = new Component[callbacks.Length + 1]; | |
Array.Copy(callbacks, temp, callbacks.Length); | |
temp[callbacks.Length] = ub; | |
callbacks = temp; | |
} | |
public override void OnPlayerJoined(VRCPlayerApi player) { | |
if (player.isLocal) localPlayer = player; | |
} | |
// Flying avatar detection: If any extra non-trigger colliders under the player's capsule, ban! | |
public override void OnPlayerCollisionEnter(VRCPlayerApi player) { | |
if (flyingAvatarDetect && player == localPlayer) FlyingDetected(); | |
} | |
void FlyingDetected() { | |
Debug.Log("[Cheat Detector] <color=#FF0000>Flying detected!</color>"); | |
if (callbacks != null) | |
foreach (var ub in callbacks) | |
((UdonBehaviour)ub).SendCustomEvent("OnFlyingDetected"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment