Skip to content

Instantly share code, notes, and snippets.

@JLChnToZ
Last active December 15, 2021 19:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JLChnToZ/1bd960c814b054c6253bfe87b23498ec to your computer and use it in GitHub Desktop.
Save JLChnToZ/1bd960c814b054c6253bfe87b23498ec to your computer and use it in GitHub Desktop.
Cheat detector for platform-like games in VRChat.
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