using System;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using UnityEngine;
using VRC.Udon.Common.Interfaces;
using System.Diagnostics;
using UnityEngine.Serialization;
using VRC.Dynamics;
#if VRC_ENABLE_PLAYER_PERSISTENCE || VRC_ENABLE_INSTANCE_PERSISTENCE
using VRC.SDK3.Persistence;
#endif
using VRC.SDKBase;
using VRC.SDK3.Data;
using VRC.Udon.Serialization.OdinSerializer;
namespace UdonSharp
{
public abstract class UdonSharpBehaviour : MonoBehaviour, ISerializationCallbackReceiver, ISupportsPrefabSerialization
{
// Stubs for the UdonBehaviour functions that emulate Udon behavior
///
/// Gets a field from the target UdonSharpBehaviour
///
[PublicAPI]
public object GetProgramVariable(string name)
{
FieldInfo variableField = GetType().GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (variableField == null)
return null;
return variableField.GetValue(this);
}
///
/// Sets a field on the target UdonSharpBehaviour.
/// Make sure you are setting a value on the behaviour with a compatible type to the given , or you may run into unexpected crashes.
///
[PublicAPI]
public void SetProgramVariable(string name, object value)
{
FieldInfo variableField = GetType().GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (variableField == null)
return;
FieldChangeCallbackAttribute fieldChangeCallback = variableField.GetCustomAttribute();
if (fieldChangeCallback != null)
{
PropertyInfo targetProperty = variableField.DeclaringType.GetProperty(fieldChangeCallback.CallbackPropertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (targetProperty == null)
return;
MethodInfo setMethod = targetProperty.GetSetMethod(true);
if (setMethod == null)
return;
setMethod.Invoke(this, new object[] { value });
}
else
{
variableField.SetValue(this, value);
}
}
///
/// Calls the method with on the target UdonSharpBehaviour. The target method must be public and have no parameters.
/// The method is allowed to return a value, but the return value will not be accessible via this method.
///
/// Name of the method to call
[PublicAPI]
public void SendCustomEvent(string eventName)
{
#if UNITY_EDITOR
if (_udonSharpBackingUdonBehaviour != null) // If this is a proxy, we need to check if this is a valid call to SendCustomEvent, since animation events can call it when they shouldn't
{
StackFrame frame = new StackFrame(1); // Get the frame of the calling method
// If the calling method is null, this has been called from native code which indicates it was called by Unity, which we don't want on proxies
if (frame.GetMethod() == null)
return;
}
#endif
MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 0);
if (eventMethod != null)
{
eventMethod.Invoke(this, new object[] { });
}
}
///
/// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have no parameters.
/// The method is allowed to return a value, but the return value will not be accessible via this method.
/// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute.
///
/// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance
/// Name of the method to call
[PublicAPI]
public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName)
{
SendCustomEvent(eventName);
}
///
/// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have one parameter.
/// The method is allowed to return a value, but the return value will not be accessible via this method.
/// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute..
///
/// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance
/// Name of the method to call
[PublicAPI]
public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0)
{
MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 1);
eventMethod?.Invoke(this, new object[] { parameter0 });
}
///
/// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have two parameters.
/// The method is allowed to return a value, but the return value will not be accessible via this method.
/// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute..
///
/// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance
/// Name of the method to call
[PublicAPI]
public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1)
{
MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 2);
eventMethod?.Invoke(this, new object[] { parameter0, parameter1 });
}
///
/// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have three parameters.
/// The method is allowed to return a value, but the return value will not be accessible via this method.
/// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute..
///
/// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance
/// Name of the method to call
[PublicAPI]
public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2)
{
MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 3);
eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2 });
}
///
/// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have four parameters.
/// The method is allowed to return a value, but the return value will not be accessible via this method.
/// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute..
///
/// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance
/// Name of the method to call
[PublicAPI]
public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2, object parameter3)
{
MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 4);
eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2, parameter3 });
}
///
/// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have five parameters.
/// The method is allowed to return a value, but the return value will not be accessible via this method.
/// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute..
///
/// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance
/// Name of the method to call
[PublicAPI]
public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2, object parameter3, object parameter4)
{
MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 5);
eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2, parameter3, parameter4 });
}
///
/// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have five parameters.
/// The method is allowed to return a value, but the return value will not be accessible via this method.
/// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute..
///
/// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance
/// Name of the method to call
[PublicAPI]
public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2, object parameter3, object parameter4, object parameter5)
{
MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 6);
eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2, parameter3, parameter4, parameter5 });
}
///
/// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have five parameters.
/// The method is allowed to return a value, but the return value will not be accessible via this method.
/// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute..
///
/// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance
/// Name of the method to call
[PublicAPI]
public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2, object parameter3, object parameter4, object parameter5, object parameter6)
{
MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 7);
eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 });
}
///
/// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have five parameters.
/// The method is allowed to return a value, but the return value will not be accessible via this method.
/// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute..
///
/// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance
/// Name of the method to call
[PublicAPI]
public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2, object parameter3, object parameter4, object parameter5, object parameter6, object parameter7)
{
MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 8);
eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 });
}
///
/// Executes target event after delaySeconds. If 0.0 delaySeconds is specified, will execute the following frame
///
///
///
///
[PublicAPI]
public void SendCustomEventDelayedSeconds(string eventName, float delaySeconds, VRC.Udon.Common.Enums.EventTiming eventTiming = VRC.Udon.Common.Enums.EventTiming.Update) { }
///
/// Executes target event after delayFrames have passed. If 0 frames is specified, will execute the following frame. In effect 0 frame delay and 1 fame delay are the same on this method.
///
///
///
///
[PublicAPI]
public void SendCustomEventDelayedFrames(string eventName, int delayFrames, VRC.Udon.Common.Enums.EventTiming eventTiming = VRC.Udon.Common.Enums.EventTiming.Update) { }
///
/// Disables Interact events on this UdonBehaviour and disables the interact outline on the object this is attached to
///
[PublicAPI]
public bool DisableInteractive { get; set; }
///
/// Access the text that shows up on interactable tooltips
///
[PublicAPI]
public string InteractionText { get; set; }
[Obsolete("This method is obsolete, use Object.Instantiate(gameObject) instead")]
protected static GameObject VRCInstantiate(GameObject original)
{
return Instantiate(original);
}
///
/// Requests a network serialization of the target UdonSharpBehaviour.
/// This will only function if the UdonSharpBehaviour is set to Manual sync mode and the person calling RequestSerialization() is the owner of the object.
///
[PublicAPI]
public void RequestSerialization() { }
// Stubs for builtin UdonSharp methods to get type info
private static long GetUdonTypeID(System.Type type)
{
return Internal.UdonSharpInternalUtility.GetTypeID(type);
}
///
/// Returns the unique ID of the UdonBehavior user type. Will return 0 if the UdonBehavior has no ID, which usually means that it's a graph program.
///
///
[PublicAPI]
public long GetUdonTypeID()
{
return GetUdonTypeID(GetType());
}
///
/// Gets the type ID for the given T, usually used for checking UdonSharpBehaviour type equality
///
[PublicAPI]
public static long GetUdonTypeID() where T : UdonSharpBehaviour
{
return GetUdonTypeID(typeof(T));
}
private static string GetUdonTypeName(System.Type type)
{
return Internal.UdonSharpInternalUtility.GetTypeName(type);
}
[PublicAPI]
public string GetUdonTypeName()
{
return GetUdonTypeName(GetType());
}
[PublicAPI]
public static string GetUdonTypeName() where T : UdonSharpBehaviour
{
return GetUdonTypeName(typeof(T));
}
// Method stubs for auto completion
[PublicAPI] public virtual void PostLateUpdate() { }
[PublicAPI] public virtual void Interact() { }
[PublicAPI] public virtual void OnAvatarChanged(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnAvatarEyeHeightChanged(VRC.SDKBase.VRCPlayerApi player, float prevEyeHeightAsMeters) { }
[PublicAPI] public virtual void OnDrop() { }
[PublicAPI] public virtual void OnOwnershipTransferred(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnMasterTransferred(VRC.SDKBase.VRCPlayerApi newMaster) { }
[PublicAPI] public virtual void OnPickup() { }
[PublicAPI] public virtual void OnPickupUseDown() { }
[PublicAPI] public virtual void OnPickupUseUp() { }
[PublicAPI] public virtual void OnPlayerJoined(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnPlayerLeft(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnSpawn() { }
[PublicAPI] public virtual void OnStationEntered(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnStationExited(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnVideoEnd() { }
[PublicAPI] public virtual void OnVideoError(VRC.SDK3.Components.Video.VideoError videoError) { }
[PublicAPI] public virtual void OnVideoLoop() { }
[PublicAPI] public virtual void OnVideoPause() { }
[PublicAPI] public virtual void OnVideoPlay() { }
[PublicAPI] public virtual void OnVideoReady() { }
[PublicAPI] public virtual void OnVideoStart() { }
[PublicAPI] public virtual void OnPreSerialization() { }
[PublicAPI] public virtual void OnDeserialization() { }
[PublicAPI] public virtual void OnDeserialization(VRC.Udon.Common.DeserializationResult result) { }
#if VRC_ENABLE_PLAYER_PERSISTENCE
[PublicAPI] public virtual void OnPlayerDataUpdated(VRC.SDKBase.VRCPlayerApi player, VRC.SDK3.Persistence.PlayerData.Info[] infos) { }
#endif
[PublicAPI] public virtual void OnPlayerRestored(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnPlayerTriggerEnter(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnPlayerTriggerExit(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnPlayerTriggerStay(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnPlayerCollisionEnter(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnPlayerCollisionExit(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnPlayerCollisionStay(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnPlayerParticleCollision(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnControllerColliderHitPlayer(VRC.SDK3.ControllerColliderPlayerHit hit) { }
[PublicAPI] public virtual void OnPlayerRespawn(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnImageLoadSuccess(VRC.SDK3.Image.IVRCImageDownload result) { }
[PublicAPI] public virtual void OnImageLoadError(VRC.SDK3.Image.IVRCImageDownload result) { }
[PublicAPI] public virtual void OnStringLoadSuccess(VRC.SDK3.StringLoading.IVRCStringDownload result) { }
[PublicAPI] public virtual void OnStringLoadError(VRC.SDK3.StringLoading.IVRCStringDownload result) { }
[PublicAPI] public virtual void OnPlayerSuspendChanged(VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnDroneTriggerEnter(VRC.SDKBase.VRCDroneApi drone) { }
[PublicAPI] public virtual void OnDroneTriggerExit(VRC.SDKBase.VRCDroneApi drone) { }
[PublicAPI] public virtual void OnDroneTriggerStay(VRC.SDKBase.VRCDroneApi drone) { }
[PublicAPI] public virtual void OnPostSerialization(VRC.Udon.Common.SerializationResult result) { }
[PublicAPI] public virtual bool OnOwnershipRequest(VRC.SDKBase.VRCPlayerApi requestingPlayer, VRC.SDKBase.VRCPlayerApi requestedOwner) => true;
#region Creator Economy
[PublicAPI] public virtual void OnPurchaseConfirmed(VRC.Economy.IProduct product, VRC.SDKBase.VRCPlayerApi player, bool purchasedNow) { }
[PublicAPI] public virtual void OnPurchaseConfirmedMultiple(VRC.Economy.IProduct product, VRC.SDKBase.VRCPlayerApi player, bool purchasedNow, int quantity) { }
[PublicAPI] public virtual void OnPurchaseExpired(VRC.Economy.IProduct product, VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnPurchasesLoaded(VRC.Economy.IProduct[] products, VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnProductEvent(VRC.Economy.IProduct product, VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnListPurchases(VRC.Economy.IProduct[] products, VRC.SDKBase.VRCPlayerApi player) { }
[PublicAPI] public virtual void OnListAvailableProducts(VRC.Economy.IProduct[] products) { }
[PublicAPI] public virtual void OnListProductOwners(VRC.Economy.IProduct product, string[] owners) { }
#endregion
[PublicAPI] public virtual void MidiNoteOn(int channel, int number, int velocity) { }
[PublicAPI] public virtual void MidiNoteOff(int channel, int number, int velocity) { }
[PublicAPI] public virtual void MidiControlChange(int channel, int number, int value) { }
[PublicAPI] public virtual void InputJump(bool value, VRC.Udon.Common.UdonInputEventArgs args) { }
[PublicAPI] public virtual void InputUse(bool value, VRC.Udon.Common.UdonInputEventArgs args) { }
[PublicAPI] public virtual void InputGrab(bool value, VRC.Udon.Common.UdonInputEventArgs args) { }
[PublicAPI] public virtual void InputDrop(bool value, VRC.Udon.Common.UdonInputEventArgs args) { }
[PublicAPI] public virtual void InputMoveHorizontal(float value, VRC.Udon.Common.UdonInputEventArgs args) { }
[PublicAPI] public virtual void InputMoveVertical(float value, VRC.Udon.Common.UdonInputEventArgs args) { }
[PublicAPI] public virtual void InputLookHorizontal(float value, VRC.Udon.Common.UdonInputEventArgs args) { }
[PublicAPI] public virtual void InputLookVertical(float value, VRC.Udon.Common.UdonInputEventArgs args) { }
[PublicAPI] public virtual void OnInputMethodChanged(VRC.SDKBase.VRCInputMethod inputMethod) { }
[PublicAPI] public virtual void OnLanguageChanged(string language) { }
[PublicAPI] public virtual void OnAsyncGpuReadbackComplete(VRC.SDK3.Rendering.VRCAsyncGPUReadbackRequest request) { }
[PublicAPI] public virtual void OnVRCCameraSettingsChanged(VRC.SDK3.Rendering.VRCCameraSettings cameraSettings) { }
[PublicAPI] public virtual void OnVRCQualitySettingsChanged() { }
[PublicAPI] public virtual void OnScreenUpdate(VRC.SDK3.Platform.ScreenUpdateData data) {}
[PublicAPI] public virtual void OnVRCPlusMassGift(VRC.SDKBase.VRCPlayerApi gifter, int numGifts) { }
[PublicAPI] public virtual void OnPhysBoneGrabbed(PhysBoneGrabbedInfo physBoneInfo) { }
[PublicAPI] public virtual void OnPhysBoneReleased(PhysBoneReleasedInfo physBoneInfo) { }
[PublicAPI] public virtual void OnPhysBonePosed(PhysBonePosedInfo physBoneInfo) { }
[PublicAPI] public virtual void OnPhysBoneUnPosed(PhysBoneUnPosedInfo physBoneInfo) { }
[PublicAPI] public virtual void OnContactEnter(ContactEnterInfo contactInfo) { }
[PublicAPI] public virtual void OnContactExit(ContactExitInfo contactInfo) { }
#if VRC_ENABLE_PLAYER_PERSISTENCE || VRC_ENABLE_INSTANCE_PERSISTENCE
[PublicAPI] public virtual void OnPersistenceUsageUpdated() {}
#endif
#if VRC_ENABLE_PLAYER_PERSISTENCE
[PublicAPI] public virtual void OnPlayerDataStorageExceeded(VRCPlayerApi player) {}
[PublicAPI] public virtual void OnPlayerDataStorageWarning(VRCPlayerApi player) {}
[PublicAPI] public virtual void OnPlayerObjectStorageExceeded(VRCPlayerApi player) {}
[PublicAPI] public virtual void OnPlayerObjectStorageWarning(VRCPlayerApi player) {}
#endif
[Obsolete("The OnStationEntered() event is deprecated use the OnStationEntered(VRCPlayerApi player) event instead", true)]
public virtual void OnStationEntered() { }
[Obsolete("The OnStationExited() event is deprecated use the OnStationExited(VRCPlayerApi player) event instead", true)]
public virtual void OnStationExited() { }
[Obsolete("The OnOwnershipTransferred() event is deprecated use the OnOwnershipTransferred(VRCPlayerApi player) event instead", true)]
public virtual void OnOwnershipTransferred() { }
// Used for tracking serialization data in editor
// Also allows serializing user data on U# behaviours that is otherwise not supported by Unity, so stuff like jagged arrays and more complex collection types.
[SerializeField, HideInInspector]
private SerializationData serializationData;
[SerializeField, HideInInspector, UsedImplicitly]
private VRC.Udon.UdonBehaviour _udonSharpBackingUdonBehaviour;
// Used to preserve backing behaviour on Reset, paste component value calls, and preset applications
// http://answers.unity.com/answers/1754330/view.html
private VRC.Udon.UdonBehaviour _backingUdonBehaviourDump;
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
UnitySerializationUtility.SerializeUnityObject(this, ref serializationData);
_backingUdonBehaviourDump = _udonSharpBackingUdonBehaviour;
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
UnitySerializationUtility.DeserializeUnityObject(this, ref serializationData);
if (_backingUdonBehaviourDump)
{
_udonSharpBackingUdonBehaviour = _backingUdonBehaviourDump;
}
}
SerializationData ISupportsPrefabSerialization.SerializationData
{
get => serializationData;
set => serializationData = value;
}
#pragma warning disable CS0414 // Referenced via reflection
[UsedImplicitly]
private static bool _skipEvents;
#pragma warning restore CS0414
[UsedImplicitly]
private static bool ShouldSkipEvents() => _skipEvents;
}
}