1
Fork 0
mirror of https://github.com/Steffo99/better-tee.git synced 2024-11-24 00:04:19 +00:00

"some" new stuff

This commit is contained in:
Steffo 2019-09-16 00:28:36 +02:00
parent e601d3f4b6
commit 6f6d436551
515 changed files with 43532 additions and 98 deletions

View file

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
@ -18,16 +16,6 @@ public abstract class ActController : MonoBehaviour
public Canvas canvas = null;
public EventSystem eventSystem = null;
[Serializable]
public class ActSettings {
public string type;
}
[Serializable]
public class ActResults {
};
[Serializable]
public class InvalidPhaseException : Exception {
public readonly ActPhase currentPhase;
@ -40,15 +28,6 @@ public abstract class ActController : MonoBehaviour
[Serializable]
public class MissingSettingsException : Exception {};
[Serializable]
public enum ActPhase {
NONE,
INIT,
START,
END,
CLEANUP
}
public ActPhase Phase { get; }
/// <summary>

View file

@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

11
Assets/Code/ActPhase.cs Normal file
View file

@ -0,0 +1,11 @@
using System;
[Serializable]
public enum ActPhase {
NONE,
INIT,
START,
END,
CLEANUP
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7e1d130c18e44bc4fa2a106ff85bd45d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,5 @@
using System;
[Serializable]
public class ActResults {};

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ff258aa9f24aa1b4d9c0c402166784d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,7 @@
using System;
[Serializable]
public class ActSettings {
public string type;
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cb49ab9cfe218ab47bececca8851077c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,6 +1,5 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine;
public class DrawableFrame : MonoBehaviour
{

View file

@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: -50
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -22,35 +22,6 @@ public class DrawingController : ActController
protected Text actName;
protected Text actDescription;
protected Timer actTimer;
[Serializable]
public class DrawingSettings : ActSettings {
public Color startingColor = Color.white;
public List<Color> palette = new List<Color>();
public float timeLimit = 99f;
public string actName = "Untitled";
public string actDescription = "This Act is missing a description.";
public string destinationPool = "default";
public DrawingSettings(Color startingColor, List<Color> palette, float timeLimit, string actName, string actDescription, string destinationPool) {
this.type = "Drawing";
this.startingColor = startingColor;
this.palette = palette;
this.timeLimit = timeLimit;
this.actName = actName;
this.actDescription = actDescription;
this.destinationPool = destinationPool;
}
}
[Serializable]
public class DrawingResults : ActResults {
public readonly byte[] pngBytes;
public DrawingResults(byte[] pngBytes) {
this.pngBytes = pngBytes;
}
}
public override void ActInit() {
base.ActInit();

View file

@ -19,7 +19,7 @@ MonoImporter:
- actTimerPrefab: {fileID: 8049216009855113645, guid: 2ecb10bfea7885e40ad4340fa7eaa265,
type: 3}
executionOrder: 0
icon: {fileID: -5397416234189338067, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
using System;
[Serializable]
public class DrawingResults : ActResults {
public readonly byte[] pngBytes;
public DrawingResults(byte[] pngBytes) {
this.pngBytes = pngBytes;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8c417ef78cbd61438bff61a5dda66de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class DrawingSettings : ActSettings {
public Color startingColor = Color.white;
public List<Color> palette = new List<Color>();
public float timeLimit = 99f;
public string actName = "Untitled";
public string actDescription = "This Act is missing a description.";
public string destinationPool = "default";
public DrawingSettings(Color startingColor, List<Color> palette, float timeLimit, string actName, string actDescription, string destinationPool) {
this.type = "Drawing";
this.startingColor = startingColor;
this.palette = palette;
this.timeLimit = timeLimit;
this.actName = actName;
this.actDescription = actDescription;
this.destinationPool = destinationPool;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b882786176f59d04b897dfa2cfbe3f95
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,48 @@
using System;
using Mirror;
namespace NetMessages {
public class ErrorMessage : MessageBase
{
public string errorName;
public string errorDescription = "";
}
public class PlayerConnectionMessage : MessageBase
{
public string playerName;
public string gamePassword;
}
public class ViewerConnectionMessage : MessageBase
{
public string gamePassword;
}
public class ConnectionSuccessfulResponse : MessageBase
{
public string[] playersConnected;
}
public class NewPlayerConnectedNotification : MessageBase
{
public string playerName;
}
public class GameStartMessage : MessageBase
{
}
public class ActSettingsMessage : MessageBase
{
public ActSettings settings;
}
public class ActResultsMessage : MessageBase
{
public ActResults results;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 26964efd4b147744fb4bb183d75abaa6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,6 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
@ -31,15 +29,15 @@ public class PlayerMainController : MonoBehaviour
};
public void LoadAct(string jsonData) {
ActController.ActSettings unknownSettings = JsonUtility.FromJson<ActController.ActSettings>(jsonData);
ActSettings unknownSettings = JsonUtility.FromJson<ActSettings>(jsonData);
if(unknownSettings.type == "Drawing") {
currentAct = Instantiate(drawingControllerPrefab, transform).GetComponent<DrawingController>();
currentAct.settings = JsonUtility.FromJson<DrawingController.DrawingSettings>(jsonData);
currentAct.settings = JsonUtility.FromJson<DrawingSettings>(jsonData);
}
else if (unknownSettings.type == "Typing") {
currentAct = Instantiate(typingControllerPrefab, transform).GetComponent<TypingController>();
currentAct.settings = JsonUtility.FromJson<TypingController.TypingSettings>(jsonData);
currentAct.settings = JsonUtility.FromJson<TypingSettings>(jsonData);
}
else {
throw new InvalidJsonDataException(jsonData);

View file

@ -10,7 +10,7 @@ MonoImporter:
- typingControllerPrefab: {fileID: 2942172269146964315, guid: d91131f9599079b4d96bfefa29d77a3a,
type: 3}
executionOrder: 0
icon: {fileID: -5397416234189338067, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,15 @@
using UnityEngine;
using System.Collections.Generic;
using Mirror;
public class ServerMainController : MonoBehaviour
{
public const int MAX_CONNECTIONS = 32;
public bool isListening = false;
public void ServerStart() {
NetworkServer.Listen(MAX_CONNECTIONS);
isListening = true;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 89fe2c8d614a60e42b42812f5bdcfb7f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -25,31 +25,6 @@ public class TypingController : ActController
protected Submit submit;
protected Text submittedCount;
[Serializable]
public class TypingSettings : ActSettings {
public float timeLimit = 99f;
public string actName = "Untitled";
public string actDescription = "This Act is missing a description.";
public string destinationPool = "default";
public TypingSettings(float timeLimit, string actName, string actDescription, string destinationPool) {
this.type = "Typing";
this.timeLimit = timeLimit;
this.actName = actName;
this.actDescription = actDescription;
this.destinationPool = destinationPool;
}
}
[Serializable]
public class TypingResults : ActResults {
public List<string> submissions;
public TypingResults(List<string> submissions) {
this.submissions = submissions;
}
}
protected override void Start() {
base.Start();
submissions = new List<string>();

View file

@ -19,7 +19,7 @@ MonoImporter:
- submittedCountPrefab: {fileID: 3217623250842909506, guid: ea05f9737b8776c4faf05bf53c37b4b9,
type: 3}
executionOrder: 0
icon: {fileID: -5397416234189338067, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
[Serializable]
public class TypingResults : ActResults {
public List<string> submissions;
public TypingResults(List<string> submissions) {
this.submissions = submissions;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 400c8f3c2f7b6cf468dc2eb5fcccea62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,18 @@
using System;
[Serializable]
public class TypingSettings : ActSettings {
public float timeLimit = 99f;
public string actName = "Untitled";
public string actDescription = "This Act is missing a description.";
public string destinationPool = "default";
public TypingSettings(float timeLimit, string actName, string actDescription, string destinationPool) {
this.type = "Typing";
this.timeLimit = timeLimit;
this.actName = actName;
this.actDescription = actDescription;
this.destinationPool = destinationPool;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f1b1ace24b994a418057ba3c7e97810
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 7866945982896999795, guid: 0000000000000000d000000000000000, type: 0}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Assets/Packages.meta Normal file
View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d2723bee01450514382d1e32aa01f968
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5cf8eb36be0834b3da408c694a41cb88
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9bee879fbc8ef4b1a9a9f7088bfbf726
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,14 @@
{
"name": "Mirror.Components",
"references": [
"Mirror"
],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 72872094b21c16e48b631b2224833d49
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,427 @@
using UnityEngine;
using UnityEngine.Serialization;
namespace Mirror
{
/// <summary>
/// A component to synchronize Mecanim animation states for networked objects.
/// </summary>
/// <remarks>
/// <para>The animation of game objects can be networked by this component. There are two models of authority for networked movement:</para>
/// <para>If the object has authority on the client, then it should animated locally on the owning client. The animation state information will be sent from the owning client to the server, then broadcast to all of the other clients. This is common for player objects.</para>
/// <para>If the object has authority on the server, then it should be animated on the server and state information will be sent to all clients. This is common for objects not related to a specific client, such as an enemy unit.</para>
/// <para>The NetworkAnimator synchronizes the animation parameters that are checked in the inspector view. It does not automatically sychronize triggers. The function SetTrigger can by used by an object with authority to fire an animation trigger on other clients.</para>
/// </remarks>
[DisallowMultipleComponent]
[AddComponentMenu("Network/NetworkAnimator")]
[RequireComponent(typeof(NetworkIdentity))]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkAnimator.html")]
public class NetworkAnimator : NetworkBehaviour
{
/// <summary>
/// The animator component to synchronize.
/// </summary>
[FormerlySerializedAs("m_Animator")]
public Animator animator;
// Note: not an object[] array because otherwise initialization is real annoying
int[] lastIntParameters;
float[] lastFloatParameters;
bool[] lastBoolParameters;
AnimatorControllerParameter[] parameters;
int[] animationHash; // multiple layers
int[] transitionHash;
float sendTimer;
bool sendMessagesAllowed
{
get
{
if (isServer)
{
if (!localPlayerAuthority)
return true;
// This is a special case where we have localPlayerAuthority set
// on a NetworkIdentity but we have not assigned the client who has
// authority over it, no animator data will be sent over the network by the server.
//
// So we check here for a clientAuthorityOwner and if it is null we will
// let the server send animation data until we receive an owner.
if (netIdentity != null && netIdentity.clientAuthorityOwner == null)
return true;
}
return hasAuthority;
}
}
void Awake()
{
// store the animator parameters in a variable - the "Animator.parameters" getter allocates
// a new parameter array every time it is accessed so we should avoid doing it in a loop
parameters = animator.parameters;
lastIntParameters = new int[parameters.Length];
lastFloatParameters = new float[parameters.Length];
lastBoolParameters = new bool[parameters.Length];
animationHash = new int[animator.layerCount];
transitionHash = new int[animator.layerCount];
}
void FixedUpdate()
{
if (!sendMessagesAllowed)
return;
CheckSendRate();
for(int i = 0; i < animator.layerCount; i++)
{
int stateHash;
float normalizedTime;
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, i))
{
continue;
}
NetworkWriter writer = new NetworkWriter();
WriteParameters(writer);
SendAnimationMessage(stateHash, normalizedTime, i, writer.ToArray());
}
}
bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layerId)
{
stateHash = 0;
normalizedTime = 0;
if (animator.IsInTransition(layerId))
{
AnimatorTransitionInfo tt = animator.GetAnimatorTransitionInfo(layerId);
if (tt.fullPathHash != transitionHash[layerId])
{
// first time in this transition
transitionHash[layerId] = tt.fullPathHash;
animationHash[layerId] = 0;
return true;
}
return false;
}
AnimatorStateInfo st = animator.GetCurrentAnimatorStateInfo(layerId);
if (st.fullPathHash != animationHash[layerId])
{
// first time in this animation state
if (animationHash[layerId] != 0)
{
// came from another animation directly - from Play()
stateHash = st.fullPathHash;
normalizedTime = st.normalizedTime;
}
transitionHash[layerId] = 0;
animationHash[layerId] = st.fullPathHash;
return true;
}
return false;
}
void CheckSendRate()
{
if (sendMessagesAllowed && syncInterval != 0 && sendTimer < Time.time)
{
sendTimer = Time.time + syncInterval;
NetworkWriter writer = new NetworkWriter();
if (WriteParameters(writer))
{
SendAnimationParametersMessage(writer.ToArray());
}
}
}
void SendAnimationMessage(int stateHash, float normalizedTime, int layerId, byte[] parameters)
{
if (isServer)
{
RpcOnAnimationClientMessage(stateHash, normalizedTime, layerId, parameters);
}
else if (ClientScene.readyConnection != null)
{
CmdOnAnimationServerMessage(stateHash, normalizedTime, layerId, parameters);
}
}
void SendAnimationParametersMessage(byte[] parameters)
{
if (isServer)
{
RpcOnAnimationParametersClientMessage(parameters);
}
else if (ClientScene.readyConnection != null)
{
CmdOnAnimationParametersServerMessage(parameters);
}
}
void HandleAnimMsg(int stateHash, float normalizedTime, int layerId, NetworkReader reader)
{
if (hasAuthority)
return;
// usually transitions will be triggered by parameters, if not, play anims directly.
// NOTE: this plays "animations", not transitions, so any transitions will be skipped.
// NOTE: there is no API to play a transition(?)
if (stateHash != 0)
{
animator.Play(stateHash, layerId, normalizedTime);
}
ReadParameters(reader);
}
void HandleAnimParamsMsg(NetworkReader reader)
{
if (hasAuthority)
return;
ReadParameters(reader);
}
void HandleAnimTriggerMsg(int hash)
{
animator.SetTrigger(hash);
}
ulong NextDirtyBits()
{
ulong dirtyBits = 0;
for (int i = 0; i < parameters.Length; i++)
{
AnimatorControllerParameter par = parameters[i];
bool changed = false;
if (par.type == AnimatorControllerParameterType.Int)
{
int newIntValue = animator.GetInteger(par.nameHash);
changed = newIntValue != lastIntParameters[i];
if (changed)
{
lastIntParameters[i] = newIntValue;
}
}
else if (par.type == AnimatorControllerParameterType.Float)
{
float newFloatValue = animator.GetFloat(par.nameHash);
changed = Mathf.Abs(newFloatValue - lastFloatParameters[i]) > 0.001f;
if (changed)
{
lastFloatParameters[i] = newFloatValue;
}
}
else if (par.type == AnimatorControllerParameterType.Bool)
{
bool newBoolValue = animator.GetBool(par.nameHash);
changed = newBoolValue != lastBoolParameters[i];
if (changed)
{
lastBoolParameters[i] = newBoolValue;
}
}
if (changed)
{
dirtyBits |= 1ul << i;
}
}
return dirtyBits;
}
bool WriteParameters(NetworkWriter writer)
{
ulong dirtyBits = NextDirtyBits();
writer.WritePackedUInt64(dirtyBits);
for (int i = 0; i < parameters.Length; i++)
{
if ((dirtyBits & (1ul << i)) == 0)
continue;
AnimatorControllerParameter par = parameters[i];
if (par.type == AnimatorControllerParameterType.Int)
{
int newIntValue = animator.GetInteger(par.nameHash);
writer.WritePackedInt32(newIntValue);
}
else if (par.type == AnimatorControllerParameterType.Float)
{
float newFloatValue = animator.GetFloat(par.nameHash);
writer.WriteSingle(newFloatValue);
}
else if (par.type == AnimatorControllerParameterType.Bool)
{
bool newBoolValue = animator.GetBool(par.nameHash);
writer.WriteBoolean(newBoolValue);
}
}
return dirtyBits != 0;
}
void ReadParameters(NetworkReader reader)
{
ulong dirtyBits = reader.ReadPackedUInt64();
for (int i = 0; i < parameters.Length; i++)
{
if ((dirtyBits & (1ul << i)) == 0)
continue;
AnimatorControllerParameter par = parameters[i];
if (par.type == AnimatorControllerParameterType.Int)
{
int newIntValue = reader.ReadPackedInt32();
animator.SetInteger(par.nameHash, newIntValue);
}
else if (par.type == AnimatorControllerParameterType.Float)
{
float newFloatValue = reader.ReadSingle();
animator.SetFloat(par.nameHash, newFloatValue);
}
else if (par.type == AnimatorControllerParameterType.Bool)
{
bool newBoolValue = reader.ReadBoolean();
animator.SetBool(par.nameHash, newBoolValue);
}
}
}
/// <summary>
/// Custom Serialization
/// </summary>
/// <param name="writer"></param>
/// <param name="forceAll"></param>
/// <returns></returns>
public override bool OnSerialize(NetworkWriter writer, bool forceAll)
{
if (forceAll)
{
for(int i = 0; i < animator.layerCount; i++)
{
if (animator.IsInTransition(i))
{
AnimatorStateInfo st = animator.GetNextAnimatorStateInfo(i);
writer.WriteInt32(st.fullPathHash);
writer.WriteSingle(st.normalizedTime);
}
else
{
AnimatorStateInfo st = animator.GetCurrentAnimatorStateInfo(i);
writer.WriteInt32(st.fullPathHash);
writer.WriteSingle(st.normalizedTime);
}
}
WriteParameters(writer);
return true;
}
return false;
}
/// <summary>
/// Custom Deserialization
/// </summary>
/// <param name="reader"></param>
/// <param name="initialState"></param>
public override void OnDeserialize(NetworkReader reader, bool initialState)
{
if (initialState)
{
for(int i = 0; i < animator.layerCount; i++)
{
int stateHash = reader.ReadInt32();
float normalizedTime = reader.ReadSingle();
animator.Play(stateHash, i, normalizedTime);
}
ReadParameters(reader);
}
}
/// <summary>
/// Causes an animation trigger to be invoked for a networked object.
/// <para>If local authority is set, and this is called from the client, then the trigger will be invoked on the server and all clients. If not, then this is called on the server, and the trigger will be called on all clients.</para>
/// </summary>
/// <param name="triggerName">Name of trigger.</param>
public void SetTrigger(string triggerName)
{
SetTrigger(Animator.StringToHash(triggerName));
}
/// <summary>
/// Causes an animation trigger to be invoked for a networked object.
/// </summary>
/// <param name="hash">Hash id of trigger (from the Animator).</param>
public void SetTrigger(int hash)
{
if (hasAuthority && localPlayerAuthority)
{
if (ClientScene.readyConnection != null)
{
CmdOnAnimationTriggerServerMessage(hash);
}
return;
}
if (isServer && !localPlayerAuthority)
{
RpcOnAnimationTriggerClientMessage(hash);
}
}
#region server message handlers
[Command]
void CmdOnAnimationServerMessage(int stateHash, float normalizedTime, int layerId, byte[] parameters)
{
if (LogFilter.Debug) Debug.Log("OnAnimationMessage for netId=" + netId);
// handle and broadcast
HandleAnimMsg(stateHash, normalizedTime, layerId, new NetworkReader(parameters));
RpcOnAnimationClientMessage(stateHash, normalizedTime, layerId, parameters);
}
[Command]
void CmdOnAnimationParametersServerMessage(byte[] parameters)
{
// handle and broadcast
HandleAnimParamsMsg(new NetworkReader(parameters));
RpcOnAnimationParametersClientMessage(parameters);
}
[Command]
void CmdOnAnimationTriggerServerMessage(int hash)
{
// handle and broadcast
HandleAnimTriggerMsg(hash);
RpcOnAnimationTriggerClientMessage(hash);
}
#endregion
#region client message handlers
[ClientRpc]
void RpcOnAnimationClientMessage(int stateHash, float normalizedTime, int layerId, byte[] parameters)
{
HandleAnimMsg(stateHash, normalizedTime, layerId, new NetworkReader(parameters));
}
[ClientRpc]
void RpcOnAnimationParametersClientMessage(byte[] parameters)
{
HandleAnimParamsMsg(new NetworkReader(parameters));
}
// server sends this to one client
[ClientRpc]
void RpcOnAnimationTriggerClientMessage(int hash)
{
HandleAnimTriggerMsg(hash);
}
#endregion
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f6f3bf89aa97405989c802ba270f815
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,659 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Serialization;
namespace Mirror
{
/// <summary>
/// This is a specialized NetworkManager that includes a networked lobby.
/// </summary>
/// <remarks>
/// <para>The lobby has slots that track the joined players, and a maximum player count that is enforced. It requires that the NetworkLobbyPlayer component be on the lobby player objects.</para>
/// <para>NetworkLobbyManager is derived from NetworkManager, and so it implements many of the virtual functions provided by the NetworkManager class. To avoid accidentally replacing functionality of the NetworkLobbyManager, there are new virtual functions on the NetworkLobbyManager that begin with "OnLobby". These should be used on classes derived from NetworkLobbyManager instead of the virtual functions on NetworkManager.</para>
/// <para>The OnLobby*() functions have empty implementations on the NetworkLobbyManager base class, so the base class functions do not have to be called.</para>
/// </remarks>
[AddComponentMenu("Network/NetworkLobbyManager")]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkLobbyManager.html")]
public class NetworkLobbyManager : NetworkManager
{
public struct PendingPlayer
{
public NetworkConnection conn;
public GameObject lobbyPlayer;
}
[Header("Lobby Settings")]
[FormerlySerializedAs("m_ShowLobbyGUI")]
[SerializeField]
internal bool showLobbyGUI = true;
[FormerlySerializedAs("m_MinPlayers")]
[SerializeField]
int minPlayers = 1;
[FormerlySerializedAs("m_LobbyPlayerPrefab")]
[SerializeField]
NetworkLobbyPlayer lobbyPlayerPrefab;
/// <summary>
/// The scene to use for the lobby. This is similar to the offlineScene of the NetworkManager.
/// </summary>
[Scene]
public string LobbyScene;
/// <summary>
/// The scene to use for the playing the game from the lobby. This is similar to the onlineScene of the NetworkManager.
/// </summary>
[Scene]
public string GameplayScene;
/// <summary>
/// List of players that are in the Lobby
/// </summary>
[FormerlySerializedAs("m_PendingPlayers")]
public List<PendingPlayer> pendingPlayers = new List<PendingPlayer>();
/// <summary>
/// These slots track players that enter the lobby.
/// <para>The slotId on players is global to the game - across all players.</para>
/// </summary>
public List<NetworkLobbyPlayer> lobbySlots = new List<NetworkLobbyPlayer>();
/// <summary>
/// True when all players have submitted a Ready message
/// </summary>
public bool allPlayersReady;
public override void OnValidate()
{
// always >= 0
maxConnections = Mathf.Max(maxConnections, 0);
// always <= maxConnections
minPlayers = Mathf.Min(minPlayers, maxConnections);
// always >= 0
minPlayers = Mathf.Max(minPlayers, 0);
if (lobbyPlayerPrefab != null)
{
NetworkIdentity identity = lobbyPlayerPrefab.GetComponent<NetworkIdentity>();
if (identity == null)
{
lobbyPlayerPrefab = null;
Debug.LogError("LobbyPlayer prefab must have a NetworkIdentity component.");
}
}
base.OnValidate();
}
internal void ReadyStatusChanged()
{
int CurrentPlayers = 0;
int ReadyPlayers = 0;
foreach (NetworkLobbyPlayer item in lobbySlots)
{
if (item != null)
{
CurrentPlayers++;
if (item.readyToBegin)
ReadyPlayers++;
}
}
if (CurrentPlayers == ReadyPlayers)
CheckReadyToBegin();
else
allPlayersReady = false;
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnServerReady(NetworkConnection conn)
{
if (LogFilter.Debug) Debug.Log("NetworkLobbyManager OnServerReady");
base.OnServerReady(conn);
if (conn != null && conn.playerController != null)
{
GameObject lobbyPlayer = conn.playerController.gameObject;
// if null or not a lobby player, dont replace it
if (lobbyPlayer != null && lobbyPlayer.GetComponent<NetworkLobbyPlayer>() != null)
SceneLoadedForPlayer(conn, lobbyPlayer);
}
}
void SceneLoadedForPlayer(NetworkConnection conn, GameObject lobbyPlayer)
{
if (LogFilter.Debug) Debug.LogFormat("NetworkLobby SceneLoadedForPlayer scene: {0} {1}", SceneManager.GetActiveScene().name, conn);
if (SceneManager.GetActiveScene().name == LobbyScene)
{
// cant be ready in lobby, add to ready list
PendingPlayer pending;
pending.conn = conn;
pending.lobbyPlayer = lobbyPlayer;
pendingPlayers.Add(pending);
return;
}
GameObject gamePlayer = OnLobbyServerCreateGamePlayer(conn);
if (gamePlayer == null)
{
// get start position from base class
Transform startPos = GetStartPosition();
gamePlayer = startPos != null
? Instantiate(playerPrefab, startPos.position, startPos.rotation)
: Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
gamePlayer.name = playerPrefab.name;
}
if (!OnLobbyServerSceneLoadedForPlayer(lobbyPlayer, gamePlayer))
return;
// replace lobby player with game player
NetworkServer.ReplacePlayerForConnection(conn, gamePlayer);
}
/// <summary>
/// CheckReadyToBegin checks all of the players in the lobby to see if their readyToBegin flag is set.
/// <para>If all of the players are ready, then the server switches from the LobbyScene to the PlayScene - essentially starting the game. This is called automatically in response to NetworkLobbyPlayer.SendReadyToBeginMessage().</para>
/// </summary>
public void CheckReadyToBegin()
{
if (SceneManager.GetActiveScene().name != LobbyScene) return;
if (minPlayers > 0 && NetworkServer.connections.Count(conn => conn.Value != null && conn.Value.playerController.gameObject.GetComponent<NetworkLobbyPlayer>().readyToBegin) < minPlayers)
{
allPlayersReady = false;
return;
}
pendingPlayers.Clear();
allPlayersReady = true;
OnLobbyServerPlayersReady();
}
void CallOnClientEnterLobby()
{
OnLobbyClientEnter();
foreach (NetworkLobbyPlayer player in lobbySlots)
if (player != null)
{
player.OnClientEnterLobby();
}
}
void CallOnClientExitLobby()
{
OnLobbyClientExit();
foreach (NetworkLobbyPlayer player in lobbySlots)
if (player != null)
{
player.OnClientExitLobby();
}
}
#region server handlers
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnServerConnect(NetworkConnection conn)
{
if (numPlayers >= maxConnections)
{
conn.Disconnect();
return;
}
// cannot join game in progress
if (SceneManager.GetActiveScene().name != LobbyScene)
{
conn.Disconnect();
return;
}
base.OnServerConnect(conn);
OnLobbyServerConnect(conn);
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnServerDisconnect(NetworkConnection conn)
{
if (conn.playerController != null)
{
NetworkLobbyPlayer player = conn.playerController.GetComponent<NetworkLobbyPlayer>();
if (player != null)
lobbySlots.Remove(player);
}
allPlayersReady = false;
foreach (NetworkLobbyPlayer player in lobbySlots)
{
if (player != null)
player.GetComponent<NetworkLobbyPlayer>().readyToBegin = false;
}
if (SceneManager.GetActiveScene().name == LobbyScene)
RecalculateLobbyPlayerIndices();
base.OnServerDisconnect(conn);
OnLobbyServerDisconnect(conn);
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
/// <param name="extraMessage"></param>
public override void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage)
{
if (SceneManager.GetActiveScene().name != LobbyScene) return;
if (lobbySlots.Count == maxConnections) return;
allPlayersReady = false;
if (LogFilter.Debug) Debug.LogFormat("NetworkLobbyManager.OnServerAddPlayer playerPrefab:{0}", lobbyPlayerPrefab.name);
GameObject newLobbyGameObject = OnLobbyServerCreateLobbyPlayer(conn);
if (newLobbyGameObject == null)
newLobbyGameObject = (GameObject)Instantiate(lobbyPlayerPrefab.gameObject, Vector3.zero, Quaternion.identity);
NetworkLobbyPlayer newLobbyPlayer = newLobbyGameObject.GetComponent<NetworkLobbyPlayer>();
lobbySlots.Add(newLobbyPlayer);
RecalculateLobbyPlayerIndices();
NetworkServer.AddPlayerForConnection(conn, newLobbyGameObject);
}
void RecalculateLobbyPlayerIndices()
{
if (lobbySlots.Count > 0)
{
for (int i = 0; i < lobbySlots.Count; i++)
{
lobbySlots[i].index = i;
}
}
}
/// <summary>
///
/// </summary>
/// <param name="sceneName"></param>
public override void ServerChangeScene(string sceneName)
{
if (sceneName == LobbyScene)
{
foreach (NetworkLobbyPlayer lobbyPlayer in lobbySlots)
{
if (lobbyPlayer == null) continue;
// find the game-player object for this connection, and destroy it
NetworkIdentity identity = lobbyPlayer.GetComponent<NetworkIdentity>();
NetworkIdentity playerController = identity.connectionToClient.playerController;
NetworkServer.Destroy(playerController.gameObject);
if (NetworkServer.active)
{
// re-add the lobby object
lobbyPlayer.GetComponent<NetworkLobbyPlayer>().readyToBegin = false;
NetworkServer.ReplacePlayerForConnection(identity.connectionToClient, lobbyPlayer.gameObject);
}
}
}
else
{
if (dontDestroyOnLoad)
{
foreach (NetworkLobbyPlayer lobbyPlayer in lobbySlots)
{
if (lobbyPlayer != null)
{
lobbyPlayer.transform.SetParent(null);
DontDestroyOnLoad(lobbyPlayer);
}
}
}
}
base.ServerChangeScene(sceneName);
}
/// <summary>
///
/// </summary>
/// <param name="sceneName"></param>
public override void OnServerSceneChanged(string sceneName)
{
if (sceneName != LobbyScene)
{
// call SceneLoadedForPlayer on any players that become ready while we were loading the scene.
foreach (PendingPlayer pending in pendingPlayers)
SceneLoadedForPlayer(pending.conn, pending.lobbyPlayer);
pendingPlayers.Clear();
}
OnLobbyServerSceneChanged(sceneName);
}
/// <summary>
///
/// </summary>
public override void OnStartServer()
{
if (string.IsNullOrEmpty(LobbyScene))
{
Debug.LogError("NetworkLobbyManager LobbyScene is empty. Set the LobbyScene in the inspector for the NetworkLobbyMangaer");
return;
}
if (string.IsNullOrEmpty(GameplayScene))
{
Debug.LogError("NetworkLobbyManager PlayScene is empty. Set the PlayScene in the inspector for the NetworkLobbyMangaer");
return;
}
OnLobbyStartServer();
}
/// <summary>
///
/// </summary>
public override void OnStartHost()
{
OnLobbyStartHost();
}
/// <summary>
///
/// </summary>
public override void OnStopServer()
{
lobbySlots.Clear();
base.OnStopServer();
}
/// <summary>
///
/// </summary>
public override void OnStopHost()
{
OnLobbyStopHost();
}
#endregion
#region client handlers
/// <summary>
///
/// </summary>
public override void OnStartClient()
{
if (lobbyPlayerPrefab == null || lobbyPlayerPrefab.gameObject == null)
Debug.LogError("NetworkLobbyManager no LobbyPlayer prefab is registered. Please add a LobbyPlayer prefab.");
else
ClientScene.RegisterPrefab(lobbyPlayerPrefab.gameObject);
if (playerPrefab == null)
Debug.LogError("NetworkLobbyManager no GamePlayer prefab is registered. Please add a GamePlayer prefab.");
else
ClientScene.RegisterPrefab(playerPrefab);
OnLobbyStartClient();
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnClientConnect(NetworkConnection conn)
{
OnLobbyClientConnect(conn);
CallOnClientEnterLobby();
base.OnClientConnect(conn);
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnClientDisconnect(NetworkConnection conn)
{
OnLobbyClientDisconnect(conn);
base.OnClientDisconnect(conn);
}
/// <summary>
///
/// </summary>
public override void OnStopClient()
{
OnLobbyStopClient();
CallOnClientExitLobby();
if (!string.IsNullOrEmpty(offlineScene))
{
// Move the LobbyManager from the virtual DontDestroyOnLoad scene to the Game scene.
// This let's it be destroyed when client changes to the Offline scene.
SceneManager.MoveGameObjectToScene(gameObject, SceneManager.GetActiveScene());
}
}
/// <summary>
///
/// </summary>
/// <param name="newSceneName"></param>
public override void OnClientChangeScene(string newSceneName)
{
if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene from {0} to {1}", SceneManager.GetActiveScene().name, newSceneName);
if (SceneManager.GetActiveScene().name == LobbyScene && newSceneName == GameplayScene && dontDestroyOnLoad && NetworkClient.isConnected)
{
if (NetworkClient.connection != null && NetworkClient.connection.playerController != null)
{
GameObject lobbyPlayer = NetworkClient.connection.playerController.gameObject;
if (lobbyPlayer != null)
{
lobbyPlayer.transform.SetParent(null);
DontDestroyOnLoad(lobbyPlayer);
}
else
Debug.LogWarningFormat("OnClientChangeScene: lobbyPlayer is null");
}
}
else
if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene {0} {1}", dontDestroyOnLoad, NetworkClient.isConnected);
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnClientSceneChanged(NetworkConnection conn)
{
if (SceneManager.GetActiveScene().name == LobbyScene)
{
if (NetworkClient.isConnected)
CallOnClientEnterLobby();
}
else
CallOnClientExitLobby();
base.OnClientSceneChanged(conn);
OnLobbyClientSceneChanged(conn);
}
#endregion
#region lobby server virtuals
/// <summary>
/// This is called on the host when a host is started.
/// </summary>
public virtual void OnLobbyStartHost() { }
/// <summary>
/// This is called on the host when the host is stopped.
/// </summary>
public virtual void OnLobbyStopHost() { }
/// <summary>
/// This is called on the server when the server is started - including when a host is started.
/// </summary>
public virtual void OnLobbyStartServer() { }
/// <summary>
/// This is called on the server when a new client connects to the server.
/// </summary>
/// <param name="conn">The new connection.</param>
public virtual void OnLobbyServerConnect(NetworkConnection conn) { }
/// <summary>
/// This is called on the server when a client disconnects.
/// </summary>
/// <param name="conn">The connection that disconnected.</param>
public virtual void OnLobbyServerDisconnect(NetworkConnection conn) { }
/// <summary>
/// This is called on the server when a networked scene finishes loading.
/// </summary>
/// <param name="sceneName">Name of the new scene.</param>
public virtual void OnLobbyServerSceneChanged(string sceneName) { }
/// <summary>
/// This allows customization of the creation of the lobby-player object on the server.
/// <para>By default the lobbyPlayerPrefab is used to create the lobby-player, but this function allows that behaviour to be customized.</para>
/// </summary>
/// <param name="conn">The connection the player object is for.</param>
/// <returns>The new lobby-player object.</returns>
public virtual GameObject OnLobbyServerCreateLobbyPlayer(NetworkConnection conn)
{
return null;
}
/// <summary>
/// This allows customization of the creation of the GamePlayer object on the server.
/// <para>By default the gamePlayerPrefab is used to create the game-player, but this function allows that behaviour to be customized. The object returned from the function will be used to replace the lobby-player on the connection.</para>
/// </summary>
/// <param name="conn">The connection the player object is for.</param>
/// <returns>A new GamePlayer object.</returns>
public virtual GameObject OnLobbyServerCreateGamePlayer(NetworkConnection conn)
{
return null;
}
// for users to apply settings from their lobby player object to their in-game player object
/// <summary>
/// This is called on the server when it is told that a client has finished switching from the lobby scene to a game player scene.
/// <para>When switching from the lobby, the lobby-player is replaced with a game-player object. This callback function gives an opportunity to apply state from the lobby-player to the game-player object.</para>
/// </summary>
/// <param name="lobbyPlayer">The lobby player object.</param>
/// <param name="gamePlayer">The game player object.</param>
/// <returns>False to not allow this player to replace the lobby player.</returns>
public virtual bool OnLobbyServerSceneLoadedForPlayer(GameObject lobbyPlayer, GameObject gamePlayer)
{
return true;
}
/// <summary>
/// This is called on the server when all the players in the lobby are ready.
/// <para>The default implementation of this function uses ServerChangeScene() to switch to the game player scene. By implementing this callback you can customize what happens when all the players in the lobby are ready, such as adding a countdown or a confirmation for a group leader.</para>
/// </summary>
public virtual void OnLobbyServerPlayersReady()
{
// all players are readyToBegin, start the game
ServerChangeScene(GameplayScene);
}
#endregion
#region lobby client virtuals
/// <summary>
/// This is a hook to allow custom behaviour when the game client enters the lobby.
/// </summary>
public virtual void OnLobbyClientEnter() { }
/// <summary>
/// This is a hook to allow custom behaviour when the game client exits the lobby.
/// </summary>
public virtual void OnLobbyClientExit() { }
/// <summary>
/// This is called on the client when it connects to server.
/// </summary>
/// <param name="conn">The connection that connected.</param>
public virtual void OnLobbyClientConnect(NetworkConnection conn) { }
/// <summary>
/// This is called on the client when disconnected from a server.
/// </summary>
/// <param name="conn">The connection that disconnected.</param>
public virtual void OnLobbyClientDisconnect(NetworkConnection conn) { }
/// <summary>
/// This is called on the client when a client is started.
/// </summary>
/// <param name="lobbyClient">The connection for the lobby.</param>
public virtual void OnLobbyStartClient() { }
/// <summary>
/// This is called on the client when the client stops.
/// </summary>
public virtual void OnLobbyStopClient() { }
/// <summary>
/// This is called on the client when the client is finished loading a new networked scene.
/// </summary>
/// <param name="conn">The connection that finished loading a new networked scene.</param>
public virtual void OnLobbyClientSceneChanged(NetworkConnection conn) { }
/// <summary>
/// Called on the client when adding a player to the lobby fails.
/// <para>This could be because the lobby is full, or the connection is not allowed to have more players.</para>
/// </summary>
public virtual void OnLobbyClientAddPlayerFailed() { }
#endregion
#region optional UI
/// <summary>
/// virtual so inheriting classes can roll their own
/// </summary>
public virtual void OnGUI()
{
if (!showLobbyGUI)
return;
if (SceneManager.GetActiveScene().name != LobbyScene)
return;
GUI.Box(new Rect(10f, 180f, 520f, 150f), "PLAYERS");
}
#endregion
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 615e6c6589cf9e54cad646b5a11e0529
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,155 @@
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Mirror
{
/// <summary>
/// This component works in conjunction with the NetworkLobbyManager to make up the multiplayer lobby system.
/// <para>The LobbyPrefab object of the NetworkLobbyManager must have this component on it. This component holds basic lobby player data required for the lobby to function. Game specific data for lobby players can be put in other components on the LobbyPrefab or in scripts derived from NetworkLobbyPlayer.</para>
/// </summary>
[DisallowMultipleComponent]
[AddComponentMenu("Network/NetworkLobbyPlayer")]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkLobbyPlayer.html")]
public class NetworkLobbyPlayer : NetworkBehaviour
{
/// <summary>
/// This flag controls whether the default UI is shown for the lobby player.
/// <para>As this UI is rendered using the old GUI system, it is only recommended for testing purposes.</para>
/// </summary>
public bool showLobbyGUI = true;
/// <summary>
/// This is a flag that control whether this player is ready for the game to begin.
/// <para>When all players are ready to begin, the game will start. This should not be set directly, the SendReadyToBeginMessage function should be called on the client to set it on the server.</para>
/// </summary>
[SyncVar(hook = nameof(ReadyStateChanged))]
public bool readyToBegin;
/// <summary>
/// Current index of the player, e.g. Player1, Player2, etc.
/// </summary>
[SyncVar]
public int index;
#region Unity Callbacks
/// <summary>
/// Do not use Start - Override OnStartrHost / OnStartClient instead!
/// </summary>
public void Start()
{
if (NetworkManager.singleton as NetworkLobbyManager)
OnClientEnterLobby();
else
Debug.LogError("LobbyPlayer could not find a NetworkLobbyManager. The LobbyPlayer requires a NetworkLobbyManager object to function. Make sure that there is one in the scene.");
}
#endregion
#region Commands
[Command]
public void CmdChangeReadyState(bool readyState)
{
readyToBegin = readyState;
NetworkLobbyManager lobby = NetworkManager.singleton as NetworkLobbyManager;
if (lobby != null)
{
lobby.ReadyStatusChanged();
}
}
#endregion
#region SyncVar Hooks
void ReadyStateChanged(bool newReadyState)
{
OnClientReady(readyToBegin);
}
#endregion
#region Lobby Client Virtuals
/// <summary>
/// This is a hook that is invoked on all player objects when entering the lobby.
/// <para>Note: isLocalPlayer is not guaranteed to be set until OnStartLocalPlayer is called.</para>
/// </summary>
public virtual void OnClientEnterLobby() { }
/// <summary>
/// This is a hook that is invoked on all player objects when exiting the lobby.
/// </summary>
public virtual void OnClientExitLobby() { }
/// <summary>
/// This is a hook that is invoked on clients when a LobbyPlayer switches between ready or not ready.
/// <para>This function is called when the a client player calls SendReadyToBeginMessage() or SendNotReadyToBeginMessage().</para>
/// </summary>
/// <param name="readyState">Whether the player is ready or not.</param>
public virtual void OnClientReady(bool readyState) { }
#endregion
#region Optional UI
/// <summary>
/// Render a UI for the lobby. Override to provide your on UI
/// </summary>
public virtual void OnGUI()
{
if (!showLobbyGUI)
return;
NetworkLobbyManager lobby = NetworkManager.singleton as NetworkLobbyManager;
if (lobby)
{
if (!lobby.showLobbyGUI)
return;
if (SceneManager.GetActiveScene().name != lobby.LobbyScene)
return;
GUILayout.BeginArea(new Rect(20f + (index * 100), 200f, 90f, 130f));
GUILayout.Label($"Player [{index + 1}]");
if (readyToBegin)
GUILayout.Label("Ready");
else
GUILayout.Label("Not Ready");
if (((isServer && index > 0) || isServerOnly) && GUILayout.Button("REMOVE"))
{
// This button only shows on the Host for all players other than the Host
// Host and Players can't remove themselves (stop the client instead)
// Host can kick a Player this way.
GetComponent<NetworkIdentity>().connectionToClient.Disconnect();
}
GUILayout.EndArea();
if (NetworkClient.active && isLocalPlayer)
{
GUILayout.BeginArea(new Rect(20f, 300f, 120f, 20f));
if (readyToBegin)
{
if (GUILayout.Button("Cancel"))
CmdChangeReadyState(false);
}
else
{
if (GUILayout.Button("Ready"))
CmdChangeReadyState(true);
}
GUILayout.EndArea();
}
}
}
#endregion
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 79874ac94d5b1314788ecf0e86bd23fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,171 @@
using System.Collections.Generic;
using UnityEngine;
namespace Mirror
{
/// <summary>
/// Component that controls visibility of networked objects for players.
/// <para>Any object with this component on it will not be visible to players more than a (configurable) distance away.</para>
/// </summary>
[AddComponentMenu("Network/NetworkProximityChecker")]
[RequireComponent(typeof(NetworkIdentity))]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkProximityChecker.html")]
public class NetworkProximityChecker : NetworkBehaviour
{
/// <summary>
/// Enumeration of methods to use to check proximity.
/// </summary>
public enum CheckMethod
{
Physics3D,
Physics2D
}
/// <summary>
/// The maximim range that objects will be visible at.
/// </summary>
[Tooltip("The maximum range that objects will be visible at.")]
public int visRange = 10;
/// <summary>
/// How often (in seconds) that this object should update the list of observers that can see it.
/// </summary>
[Tooltip("How often (in seconds) that this object should update the list of observers that can see it.")]
public float visUpdateInterval = 1;
/// <summary>
/// Which method to use for checking proximity of players.
/// <para>Physics3D uses 3D physics to determine proximity.</para>
/// <para>Physics2D uses 2D physics to determine proximity.</para>
/// </summary>
[Tooltip("Which method to use for checking proximity of players.\n\nPhysics3D uses 3D physics to determine proximity.\nPhysics2D uses 2D physics to determine proximity.")]
public CheckMethod checkMethod = CheckMethod.Physics3D;
/// <summary>
/// Flag to force this object to be hidden for players.
/// <para>If this object is a player object, it will not be hidden for that player.</para>
/// </summary>
[Tooltip("Enable to force this object to be hidden from players.")]
public bool forceHidden;
// Layers are used anyway, might as well expose them to the user.
/// <summary>
/// Select only the Player's layer to avoid unnecessary SphereCasts against the Terrain, etc.
/// <para>~0 means 'Everything'.</para>
/// </summary>
[Tooltip("Select only the Player's layer to avoid unnecessary SphereCasts against the Terrain, etc.")]
public LayerMask castLayers = ~0;
float lastUpdateTime;
// OverlapSphereNonAlloc array to avoid allocations.
// -> static so we don't create one per component
// -> this is worth it because proximity checking happens for just about
// every entity on the server!
// -> should be big enough to work in just about all cases
static Collider[] hitsBuffer3D = new Collider[10000];
static Collider2D[] hitsBuffer2D = new Collider2D[10000];
void Update()
{
if (!NetworkServer.active)
return;
if (Time.time - lastUpdateTime > visUpdateInterval)
{
netIdentity.RebuildObservers(false);
lastUpdateTime = Time.time;
}
}
/// <summary>
/// Called when a new player enters
/// </summary>
/// <param name="newObserver"></param>
/// <returns></returns>
public override bool OnCheckObserver(NetworkConnection newObserver)
{
if (forceHidden)
return false;
return Vector3.Distance(newObserver.playerController.transform.position, transform.position) < visRange;
}
/// <summary>
/// Called when a new player enters, and when scene changes occur
/// </summary>
/// <param name="observers">List of players to be updated. Modify this set with all the players that can see this object</param>
/// <param name="initial">True if this is the first time the method is called for this object</param>
/// <returns>True if this component calculated the list of observers</returns>
public override bool OnRebuildObservers(HashSet<NetworkConnection> observers, bool initial)
{
// if force hidden then return without adding any observers.
if (forceHidden)
// always return true when overwriting OnRebuildObservers so that
// Mirror knows not to use the built in rebuild method.
return true;
// find players within range
switch (checkMethod)
{
case CheckMethod.Physics3D:
{
// cast without allocating GC for maximum performance
int hitCount = Physics.OverlapSphereNonAlloc(transform.position, visRange, hitsBuffer3D, castLayers);
if (hitCount == hitsBuffer3D.Length) Debug.LogWarning("NetworkProximityChecker's OverlapSphere test for " + name + " has filled the whole buffer(" + hitsBuffer3D.Length + "). Some results might have been omitted. Consider increasing buffer size.");
for (int i = 0; i < hitCount; i++)
{
Collider hit = hitsBuffer3D[i];
// collider might be on pelvis, often the NetworkIdentity is in a parent
// (looks in the object itself and then parents)
NetworkIdentity identity = hit.GetComponentInParent<NetworkIdentity>();
// (if an object has a connectionToClient, it is a player)
if (identity != null && identity.connectionToClient != null)
{
observers.Add(identity.connectionToClient);
}
}
break;
}
case CheckMethod.Physics2D:
{
// cast without allocating GC for maximum performance
int hitCount = Physics2D.OverlapCircleNonAlloc(transform.position, visRange, hitsBuffer2D, castLayers);
if (hitCount == hitsBuffer2D.Length) Debug.LogWarning("NetworkProximityChecker's OverlapCircle test for " + name + " has filled the whole buffer(" + hitsBuffer2D.Length + "). Some results might have been omitted. Consider increasing buffer size.");
for (int i = 0; i < hitCount; i++)
{
Collider2D hit = hitsBuffer2D[i];
// collider might be on pelvis, often the NetworkIdentity is in a parent
// (looks in the object itself and then parents)
NetworkIdentity identity = hit.GetComponentInParent<NetworkIdentity>();
// (if an object has a connectionToClient, it is a player)
if (identity != null && identity.connectionToClient != null)
{
observers.Add(identity.connectionToClient);
}
}
break;
}
}
// always return true when overwriting OnRebuildObservers so that
// Mirror knows not to use the built in rebuild method.
return true;
}
/// <summary>
/// Called when hiding and showing objects on the host
/// </summary>
/// <param name="visible"></param>
public override void OnSetLocalVisibility(bool visible)
{
foreach (Renderer rend in GetComponentsInChildren<Renderer>())
{
rend.enabled = visible;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1731d8de2d0c84333b08ebe1e79f4118
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,12 @@
using UnityEngine;
namespace Mirror
{
[DisallowMultipleComponent]
[AddComponentMenu("Network/NetworkTransform")]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkTransform.html")]
public class NetworkTransform : NetworkTransformBase
{
protected override Transform targetComponent => transform;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2f74aedd71d9a4f55b3ce499326d45fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,447 @@
// vis2k:
// base class for NetworkTransform and NetworkTransformChild.
// New method is simple and stupid. No more 1500 lines of code.
//
// Server sends current data.
// Client saves it and interpolates last and latest data points.
// Update handles transform movement / rotation
// FixedUpdate handles rigidbody movement / rotation
//
// Notes:
// * Built-in Teleport detection in case of lags / teleport / obstacles
// * Quaternion > EulerAngles because gimbal lock and Quaternion.Slerp
// * Syncs XYZ. Works 3D and 2D. Saving 4 bytes isn't worth 1000 lines of code.
// * Initial delay might happen if server sends packet immediately after moving
// just 1cm, hence we move 1cm and then wait 100ms for next packet
// * Only way for smooth movement is to use a fixed movement speed during
// interpolation. interpolation over time is never that good.
//
using UnityEngine;
namespace Mirror
{
public abstract class NetworkTransformBase : NetworkBehaviour
{
// rotation compression. not public so that other scripts can't modify
// it at runtime. alternatively we could send 1 extra byte for the mode
// each time so clients know how to decompress, but the whole point was
// to save bandwidth in the first place.
// -> can still be modified in the Inspector while the game is running,
// but would cause errors immediately and be pretty obvious.
[Tooltip("Compresses 16 Byte Quaternion into None=12, Much=3, Lots=2 Byte")]
[SerializeField] Compression compressRotation = Compression.Much;
public enum Compression { None, Much, Lots, NoRotation }; // easily understandable and funny
// server
Vector3 lastPosition;
Quaternion lastRotation;
private Vector3 lastScale;
// client
public class DataPoint
{
public float timeStamp;
// use local position/rotation for VR support
public Vector3 localPosition;
public Quaternion localRotation;
public Vector3 localScale;
public float movementSpeed;
}
// interpolation start and goal
DataPoint start;
DataPoint goal;
// local authority send time
float lastClientSendTime;
// target transform to sync. can be on a child.
protected abstract Transform targetComponent { get; }
// serialization is needed by OnSerialize and by manual sending from authority
static void SerializeIntoWriter(NetworkWriter writer, Vector3 position, Quaternion rotation, Compression compressRotation, Vector3 scale)
{
// serialize position
writer.WriteVector3(position);
// serialize rotation
// writing quaternion = 16 byte
// writing euler angles = 12 byte
// -> quaternion->euler->quaternion always works.
// -> gimbal lock only occurs when adding.
Vector3 euler = rotation.eulerAngles;
if (compressRotation == Compression.None)
{
// write 3 floats = 12 byte
writer.WriteSingle(euler.x);
writer.WriteSingle(euler.y);
writer.WriteSingle(euler.z);
}
else if (compressRotation == Compression.Much)
{
// write 3 byte. scaling [0,360] to [0,255]
writer.WriteByte(FloatBytePacker.ScaleFloatToByte(euler.x, 0, 360, byte.MinValue, byte.MaxValue));
writer.WriteByte(FloatBytePacker.ScaleFloatToByte(euler.y, 0, 360, byte.MinValue, byte.MaxValue));
writer.WriteByte(FloatBytePacker.ScaleFloatToByte(euler.z, 0, 360, byte.MinValue, byte.MaxValue));
}
else if (compressRotation == Compression.Lots)
{
// write 2 byte, 5 bits for each float
writer.WriteUInt16(FloatBytePacker.PackThreeFloatsIntoUShort(euler.x, euler.y, euler.z, 0, 360));
}
// serialize scale
writer.WriteVector3(scale);
}
public override bool OnSerialize(NetworkWriter writer, bool initialState)
{
// use local position/rotation/scale for VR support
SerializeIntoWriter(writer, targetComponent.transform.localPosition, targetComponent.transform.localRotation, compressRotation, targetComponent.transform.localScale);
return true;
}
// try to estimate movement speed for a data point based on how far it
// moved since the previous one
// => if this is the first time ever then we use our best guess:
// -> delta based on transform.localPosition
// -> elapsed based on send interval hoping that it roughly matches
static float EstimateMovementSpeed(DataPoint from, DataPoint to, Transform transform, float sendInterval)
{
Vector3 delta = to.localPosition - (from != null ? from.localPosition : transform.localPosition);
float elapsed = from != null ? to.timeStamp - from.timeStamp : sendInterval;
return elapsed > 0 ? delta.magnitude / elapsed : 0; // avoid NaN
}
// serialization is needed by OnSerialize and by manual sending from authority
void DeserializeFromReader(NetworkReader reader)
{
// put it into a data point immediately
DataPoint temp = new DataPoint
{
// deserialize position
localPosition = reader.ReadVector3()
};
// deserialize rotation
if (compressRotation == Compression.None)
{
// read 3 floats = 16 byte
float x = reader.ReadSingle();
float y = reader.ReadSingle();
float z = reader.ReadSingle();
temp.localRotation = Quaternion.Euler(x, y, z);
}
else if (compressRotation == Compression.Much)
{
// read 3 byte. scaling [0,255] to [0,360]
float x = FloatBytePacker.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360);
float y = FloatBytePacker.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360);
float z = FloatBytePacker.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360);
temp.localRotation = Quaternion.Euler(x, y, z);
}
else if (compressRotation == Compression.Lots)
{
// read 2 byte, 5 bits per float
Vector3 xyz = FloatBytePacker.UnpackUShortIntoThreeFloats(reader.ReadUInt16(), 0, 360);
temp.localRotation = Quaternion.Euler(xyz.x, xyz.y, xyz.z);
}
temp.localScale = reader.ReadVector3();
temp.timeStamp = Time.time;
// movement speed: based on how far it moved since last time
// has to be calculated before 'start' is overwritten
temp.movementSpeed = EstimateMovementSpeed(goal, temp, targetComponent.transform, syncInterval);
// reassign start wisely
// -> first ever data point? then make something up for previous one
// so that we can start interpolation without waiting for next.
if (start == null)
{
start = new DataPoint
{
timeStamp = Time.time - syncInterval,
// local position/rotation for VR support
localPosition = targetComponent.transform.localPosition,
localRotation = targetComponent.transform.localRotation,
localScale = targetComponent.transform.localScale,
movementSpeed = temp.movementSpeed
};
}
// -> second or nth data point? then update previous, but:
// we start at where ever we are right now, so that it's
// perfectly smooth and we don't jump anywhere
//
// example if we are at 'x':
//
// A--x->B
//
// and then receive a new point C:
//
// A--x--B
// |
// |
// C
//
// then we don't want to just jump to B and start interpolation:
//
// x
// |
// |
// C
//
// we stay at 'x' and interpolate from there to C:
//
// x..B
// \ .
// \.
// C
//
else
{
float oldDistance = Vector3.Distance(start.localPosition, goal.localPosition);
float newDistance = Vector3.Distance(goal.localPosition, temp.localPosition);
start = goal;
// teleport / lag / obstacle detection: only continue at current
// position if we aren't too far away
//
// // local position/rotation for VR support
if (Vector3.Distance(targetComponent.transform.localPosition, start.localPosition) < oldDistance + newDistance)
{
start.localPosition = targetComponent.transform.localPosition;
start.localRotation = targetComponent.transform.localRotation;
start.localScale = targetComponent.transform.localScale;
}
}
// set new destination in any case. new data is best data.
goal = temp;
}
public override void OnDeserialize(NetworkReader reader, bool initialState)
{
// deserialize
DeserializeFromReader(reader);
}
// local authority client sends sync message to server for broadcasting
[Command]
void CmdClientToServerSync(byte[] payload)
{
// deserialize payload
NetworkReader reader = new NetworkReader(payload);
DeserializeFromReader(reader);
// server-only mode does no interpolation to save computations,
// but let's set the position directly
if (isServer && !isClient)
ApplyPositionRotationScale(goal.localPosition, goal.localRotation, goal.localScale);
// set dirty so that OnSerialize broadcasts it
SetDirtyBit(1UL);
}
// where are we in the timeline between start and goal? [0,1]
static float CurrentInterpolationFactor(DataPoint start, DataPoint goal)
{
if (start != null)
{
float difference = goal.timeStamp - start.timeStamp;
// the moment we get 'goal', 'start' is supposed to
// start, so elapsed time is based on:
float elapsed = Time.time - goal.timeStamp;
return difference > 0 ? elapsed / difference : 0; // avoid NaN
}
return 0;
}
static Vector3 InterpolatePosition(DataPoint start, DataPoint goal, Vector3 currentPosition)
{
if (start != null)
{
// Option 1: simply interpolate based on time. but stutter
// will happen, it's not that smooth. especially noticeable if
// the camera automatically follows the player
// float t = CurrentInterpolationFactor();
// return Vector3.Lerp(start.position, goal.position, t);
// Option 2: always += speed
// -> speed is 0 if we just started after idle, so always use max
// for best results
float speed = Mathf.Max(start.movementSpeed, goal.movementSpeed);
return Vector3.MoveTowards(currentPosition, goal.localPosition, speed * Time.deltaTime);
}
return currentPosition;
}
static Quaternion InterpolateRotation(DataPoint start, DataPoint goal, Quaternion defaultRotation)
{
if (start != null)
{
float t = CurrentInterpolationFactor(start, goal);
return Quaternion.Slerp(start.localRotation, goal.localRotation, t);
}
return defaultRotation;
}
static Vector3 InterpolateScale(DataPoint start, DataPoint goal, Vector3 currentScale)
{
if (start != null)
{
float t = CurrentInterpolationFactor(start, goal);
return Vector3.Lerp(start.localScale, goal.localScale, t);
}
return currentScale;
}
// teleport / lag / stuck detection
// -> checking distance is not enough since there could be just a tiny
// fence between us and the goal
// -> checking time always works, this way we just teleport if we still
// didn't reach the goal after too much time has elapsed
bool NeedsTeleport()
{
// calculate time between the two data points
float startTime = start != null ? start.timeStamp : Time.time - syncInterval;
float goalTime = goal != null ? goal.timeStamp : Time.time;
float difference = goalTime - startTime;
float timeSinceGoalReceived = Time.time - goalTime;
return timeSinceGoalReceived > difference * 5;
}
// moved since last time we checked it?
bool HasEitherMovedRotatedScaled()
{
// moved or rotated or scaled?
// local position/rotation/scale for VR support
bool moved = lastPosition != targetComponent.transform.localPosition;
bool rotated = lastRotation != targetComponent.transform.localRotation;
bool scaled = lastScale != targetComponent.transform.localScale;
// save last for next frame to compare
// (only if change was detected. otherwise slow moving objects might
// never sync because of C#'s float comparison tolerance. see also:
// https://github.com/vis2k/Mirror/pull/428)
bool change = moved || rotated || scaled;
if (change)
{
// local position/rotation for VR support
lastPosition = targetComponent.transform.localPosition;
lastRotation = targetComponent.transform.localRotation;
lastScale = targetComponent.transform.localScale;
}
return change;
}
// set position carefully depending on the target component
void ApplyPositionRotationScale(Vector3 position, Quaternion rotation, Vector3 scale)
{
// local position/rotation for VR support
targetComponent.transform.localPosition = position;
if (Compression.NoRotation != compressRotation)
{
targetComponent.transform.localRotation = rotation;
}
targetComponent.transform.localScale = scale;
}
void Update()
{
// if server then always sync to others.
if (isServer)
{
// just use OnSerialize via SetDirtyBit only sync when position
// changed. set dirty bits 0 or 1
SetDirtyBit(HasEitherMovedRotatedScaled() ? 1UL : 0UL);
}
// no 'else if' since host mode would be both
if (isClient)
{
// send to server if we have local authority (and aren't the server)
// -> only if connectionToServer has been initialized yet too
if (!isServer && hasAuthority)
{
// check only each 'syncInterval'
if (Time.time - lastClientSendTime >= syncInterval)
{
if (HasEitherMovedRotatedScaled())
{
// serialize
// local position/rotation for VR support
NetworkWriter writer = new NetworkWriter();
SerializeIntoWriter(writer, targetComponent.transform.localPosition, targetComponent.transform.localRotation, compressRotation, targetComponent.transform.localScale);
// send to server
CmdClientToServerSync(writer.ToArray());
}
lastClientSendTime = Time.time;
}
}
// apply interpolation on client for all players
// unless this client has authority over the object. could be
// himself or another object that he was assigned authority over
if (!hasAuthority)
{
// received one yet? (initialized?)
if (goal != null)
{
// teleport or interpolate
if (NeedsTeleport())
{
// local position/rotation for VR support
ApplyPositionRotationScale(goal.localPosition, goal.localRotation, goal.localScale);
}
else
{
// local position/rotation for VR support
ApplyPositionRotationScale(InterpolatePosition(start, goal, targetComponent.transform.localPosition),
InterpolateRotation(start, goal, targetComponent.transform.localRotation),
InterpolateScale(start, goal, targetComponent.transform.localScale));
}
}
}
}
}
static void DrawDataPointGizmo(DataPoint data, Color color)
{
// use a little offset because transform.localPosition might be in
// the ground in many cases
Vector3 offset = Vector3.up * 0.01f;
// draw position
Gizmos.color = color;
Gizmos.DrawSphere(data.localPosition + offset, 0.5f);
// draw forward and up
Gizmos.color = Color.blue; // like unity move tool
Gizmos.DrawRay(data.localPosition + offset, data.localRotation * Vector3.forward);
Gizmos.color = Color.green; // like unity move tool
Gizmos.DrawRay(data.localPosition + offset, data.localRotation * Vector3.up);
}
static void DrawLineBetweenDataPoints(DataPoint data1, DataPoint data2, Color color)
{
Gizmos.color = color;
Gizmos.DrawLine(data1.localPosition, data2.localPosition);
}
// draw the data points for easier debugging
void OnDrawGizmos()
{
// draw start and goal points
if (start != null) DrawDataPointGizmo(start, Color.gray);
if (goal != null) DrawDataPointGizmo(goal, Color.white);
// draw line between them
if (start != null && goal != null) DrawLineBetweenDataPoints(start, goal, Color.cyan);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2e77294d8ccbc4e7cb8ca2bd0d3e99ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,16 @@
using UnityEngine;
namespace Mirror
{
/// <summary>
/// A component to synchronize the position of child transforms of networked objects.
/// <para>There must be a NetworkTransform on the root object of the hierarchy. There can be multiple NetworkTransformChild components on an object. This does not use physics for synchronization, it simply synchronizes the localPosition and localRotation of the child transform and lerps towards the recieved values.</para>
/// </summary>
[AddComponentMenu("Network/NetworkTransformChild")]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkTransformChild.html")]
public class NetworkTransformChild : NetworkTransformBase
{
public Transform target;
protected override Transform targetComponent => target;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 734b48bea0b204338958ee3d885e11f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2539267b6934a4026a505690a1e1eda2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,16 @@
{
"name": "Mirror.Editor",
"references": [
"Mirror"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1c7c33eb5480dd24c9e29a8250c1a775
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,5 @@
// This file was removed in Mirror 3.4.9
// The purpose of this file is to get the old file overwritten
// when users update from the asset store to prevent a flood of errors
// from having the old file still in the project as a straggler.
// This file will be dropped from the Asset Store package in May 2019

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9589e903d4e98490fb1157762a307fd7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,197 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Mirror
{
[CustomEditor(typeof(NetworkBehaviour), true)]
[CanEditMultipleObjects]
public class NetworkBehaviourInspector : Editor
{
bool initialized;
protected List<string> syncVarNames = new List<string>();
bool syncsAnything;
bool[] showSyncLists;
readonly GUIContent syncVarIndicatorContent = new GUIContent("SyncVar", "This variable has been marked with the [SyncVar] attribute.");
internal virtual bool HideScriptField => false;
// does this type sync anything? otherwise we don't need to show syncInterval
bool SyncsAnything(Type scriptClass)
{
// has OnSerialize that is not in NetworkBehaviour?
// then it either has a syncvar or custom OnSerialize. either way
// this means we have something to sync.
MethodInfo method = scriptClass.GetMethod("OnSerialize");
if (method != null && method.DeclaringType != typeof(NetworkBehaviour))
{
return true;
}
// SyncObjects are serialized in NetworkBehaviour.OnSerialize, which
// is always there even if we don't use SyncObjects. so we need to
// search for SyncObjects manually.
// (look for 'Mirror.Sync'. not '.SyncObject' because we'd have to
// check base type for that again)
foreach (FieldInfo field in scriptClass.GetFields())
{
if (field.FieldType.BaseType != null &&
field.FieldType.BaseType.FullName != null &&
field.FieldType.BaseType.FullName.Contains("Mirror.Sync"))
{
return true;
}
}
return false;
}
void Init(MonoScript script)
{
initialized = true;
Type scriptClass = script.GetClass();
// find public SyncVars to show (user doesn't want protected ones to be shown in inspector)
foreach (FieldInfo field in scriptClass.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
Attribute[] fieldMarkers = (Attribute[])field.GetCustomAttributes(typeof(SyncVarAttribute), true);
if (fieldMarkers.Length > 0)
{
syncVarNames.Add(field.Name);
}
}
int numSyncLists = scriptClass.GetFields().Count(
field => field.FieldType.BaseType != null &&
field.FieldType.BaseType.Name.Contains("SyncList"));
if (numSyncLists > 0)
{
showSyncLists = new bool[numSyncLists];
}
syncsAnything = SyncsAnything(scriptClass);
}
public override void OnInspectorGUI()
{
if (!initialized)
{
serializedObject.Update();
SerializedProperty scriptProperty = serializedObject.FindProperty("m_Script");
if (scriptProperty == null)
return;
MonoScript targetScript = scriptProperty.objectReferenceValue as MonoScript;
Init(targetScript);
}
EditorGUI.BeginChangeCheck();
serializedObject.Update();
// Loop through properties and create one field (including children) for each top level property.
SerializedProperty property = serializedObject.GetIterator();
bool expanded = true;
while (property.NextVisible(expanded))
{
bool isSyncVar = syncVarNames.Contains(property.name);
if (property.propertyType == SerializedPropertyType.ObjectReference)
{
if (property.name == "m_Script")
{
if (HideScriptField)
{
continue;
}
EditorGUI.BeginDisabledGroup(true);
}
EditorGUILayout.PropertyField(property, true);
if (isSyncVar)
{
GUILayout.Label(syncVarIndicatorContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(syncVarIndicatorContent).x));
}
if (property.name == "m_Script")
{
EditorGUI.EndDisabledGroup();
}
}
else
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(property, true);
if (isSyncVar)
{
GUILayout.Label(syncVarIndicatorContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(syncVarIndicatorContent).x));
}
EditorGUILayout.EndHorizontal();
}
expanded = false;
}
serializedObject.ApplyModifiedProperties();
EditorGUI.EndChangeCheck();
// find SyncLists.. they are not properties.
int syncListIndex = 0;
foreach (FieldInfo field in serializedObject.targetObject.GetType().GetFields())
{
if (field.FieldType.BaseType != null && field.FieldType.BaseType.Name.Contains("SyncList"))
{
showSyncLists[syncListIndex] = EditorGUILayout.Foldout(showSyncLists[syncListIndex], "SyncList " + field.Name + " [" + field.FieldType.Name + "]");
if (showSyncLists[syncListIndex])
{
EditorGUI.indentLevel += 1;
if (field.GetValue(serializedObject.targetObject) is IEnumerable synclist)
{
int index = 0;
IEnumerator enu = synclist.GetEnumerator();
while (enu.MoveNext())
{
if (enu.Current != null)
{
EditorGUILayout.LabelField("Item:" + index, enu.Current.ToString());
}
index += 1;
}
}
EditorGUI.indentLevel -= 1;
}
syncListIndex += 1;
}
}
// does it sync anything? then show extra properties
// (no need to show it if the class only has Cmds/Rpcs and no sync)
if (syncsAnything)
{
NetworkBehaviour networkBehaviour = target as NetworkBehaviour;
if (networkBehaviour != null)
{
// syncMode
serializedObject.FindProperty("syncMode").enumValueIndex = (int)(SyncMode)
EditorGUILayout.EnumPopup("Network Sync Mode", networkBehaviour.syncMode);
// syncInterval
// [0,2] should be enough. anything >2s is too laggy anyway.
serializedObject.FindProperty("syncInterval").floatValue = EditorGUILayout.Slider(
new GUIContent("Network Sync Interval",
"Time in seconds until next change is synchronized to the client. '0' means send immediately if changed. '0.5' means only send changes every 500ms.\n(This is for state synchronization like SyncVars, SyncLists, OnSerialize. Not for Cmds, Rpcs, etc.)"),
networkBehaviour.syncInterval, 0, 2);
// apply
serializedObject.ApplyModifiedProperties();
}
}
}
}
} //namespace

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f02853db46b6346e4866594a96c3b0e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,106 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Mirror
{
[CustomEditor(typeof(NetworkIdentity), true)]
[CanEditMultipleObjects]
public class NetworkIdentityEditor : Editor
{
SerializedProperty serverOnlyProperty;
SerializedProperty localPlayerAuthorityProperty;
readonly GUIContent serverOnlyLabel = new GUIContent("Server Only", "True if the object should only exist on the server.");
readonly GUIContent localPlayerAuthorityLabel = new GUIContent("Local Player Authority", "True if this object will be controlled by a player on a client.");
readonly GUIContent spawnLabel = new GUIContent("Spawn Object", "This causes an unspawned server object to be spawned on clients");
NetworkIdentity networkIdentity;
bool initialized;
bool showObservers;
void Init()
{
if (initialized)
{
return;
}
initialized = true;
networkIdentity = target as NetworkIdentity;
serverOnlyProperty = serializedObject.FindProperty("serverOnly");
localPlayerAuthorityProperty = serializedObject.FindProperty("localPlayerAuthority");
}
public override void OnInspectorGUI()
{
if (serverOnlyProperty == null)
{
initialized = false;
}
Init();
serializedObject.Update();
if (serverOnlyProperty.boolValue)
{
EditorGUILayout.PropertyField(serverOnlyProperty, serverOnlyLabel);
EditorGUILayout.LabelField("Local Player Authority cannot be set for server-only objects");
}
else if (localPlayerAuthorityProperty.boolValue)
{
EditorGUILayout.LabelField("Server Only cannot be set for Local Player Authority objects");
EditorGUILayout.PropertyField(localPlayerAuthorityProperty, localPlayerAuthorityLabel);
}
else
{
EditorGUILayout.PropertyField(serverOnlyProperty, serverOnlyLabel);
EditorGUILayout.PropertyField(localPlayerAuthorityProperty, localPlayerAuthorityLabel);
}
serializedObject.ApplyModifiedProperties();
if (!Application.isPlaying)
{
return;
}
// Runtime actions below here
EditorGUILayout.Separator();
if (networkIdentity.observers != null && networkIdentity.observers.Count > 0)
{
showObservers = EditorGUILayout.Foldout(showObservers, "Observers");
if (showObservers)
{
EditorGUI.indentLevel += 1;
foreach (KeyValuePair<int, NetworkConnection> kvp in networkIdentity.observers)
{
if (kvp.Value.playerController != null)
EditorGUILayout.ObjectField("Connection " + kvp.Value.connectionId, kvp.Value.playerController.gameObject, typeof(GameObject), false);
else
EditorGUILayout.TextField("Connection " + kvp.Value.connectionId);
}
EditorGUI.indentLevel -= 1;
}
}
if (PrefabUtility.IsPartOfPrefabAsset(networkIdentity.gameObject))
return;
if (networkIdentity.gameObject.activeSelf && networkIdentity.netId == 0 && NetworkServer.active)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(spawnLabel);
if (GUILayout.Toggle(false, "Spawn", EditorStyles.miniButtonLeft))
{
NetworkServer.Spawn(networkIdentity.gameObject);
EditorUtility.SetDirty(target); // preview window STILL doens't update immediately..
}
EditorGUILayout.EndHorizontal();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1b6e3680cc14b4769bff378e5dbc3544
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,280 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace Mirror
{
[CustomPreview(typeof(GameObject))]
class NetworkInformationPreview : ObjectPreview
{
class NetworkIdentityInfo
{
public GUIContent name;
public GUIContent value;
}
class NetworkBehaviourInfo
{
// This is here just so we can check if it's enabled/disabled
public NetworkBehaviour behaviour;
public GUIContent name;
}
class Styles
{
public GUIStyle labelStyle = new GUIStyle(EditorStyles.label);
public GUIStyle componentName = new GUIStyle(EditorStyles.boldLabel);
public GUIStyle disabledName = new GUIStyle(EditorStyles.miniLabel);
public Styles()
{
Color fontColor = new Color(0.7f, 0.7f, 0.7f);
labelStyle.padding.right += 20;
labelStyle.normal.textColor = fontColor;
labelStyle.active.textColor = fontColor;
labelStyle.focused.textColor = fontColor;
labelStyle.hover.textColor = fontColor;
labelStyle.onNormal.textColor = fontColor;
labelStyle.onActive.textColor = fontColor;
labelStyle.onFocused.textColor = fontColor;
labelStyle.onHover.textColor = fontColor;
componentName.normal.textColor = fontColor;
componentName.active.textColor = fontColor;
componentName.focused.textColor = fontColor;
componentName.hover.textColor = fontColor;
componentName.onNormal.textColor = fontColor;
componentName.onActive.textColor = fontColor;
componentName.onFocused.textColor = fontColor;
componentName.onHover.textColor = fontColor;
disabledName.normal.textColor = fontColor;
disabledName.active.textColor = fontColor;
disabledName.focused.textColor = fontColor;
disabledName.hover.textColor = fontColor;
disabledName.onNormal.textColor = fontColor;
disabledName.onActive.textColor = fontColor;
disabledName.onFocused.textColor = fontColor;
disabledName.onHover.textColor = fontColor;
}
}
List<NetworkIdentityInfo> info;
List<NetworkBehaviourInfo> behavioursInfo;
NetworkIdentity identity;
GUIContent title;
Styles styles = new Styles();
public override void Initialize(UnityObject[] targets)
{
base.Initialize(targets);
GetNetworkInformation(target as GameObject);
}
public override GUIContent GetPreviewTitle()
{
if (title == null)
{
title = new GUIContent("Network Information");
}
return title;
}
public override bool HasPreviewGUI()
{
return info != null && info.Count > 0;
}
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
if (Event.current.type != EventType.Repaint)
return;
if (info == null || info.Count == 0)
return;
if (styles == null)
styles = new Styles();
// Get required label size for the names of the information values we're going to show
// There are two columns, one with label for the name of the info and the next for the value
Vector2 maxNameLabelSize = new Vector2(140, 16);
Vector2 maxValueLabelSize = GetMaxNameLabelSize();
//Apply padding
RectOffset previewPadding = new RectOffset(-5, -5, -5, -5);
Rect paddedr = previewPadding.Add(r);
//Centering
float initialX = paddedr.x + 10;
float initialY = paddedr.y + 10;
Rect labelRect = new Rect(initialX, initialY, maxNameLabelSize.x, maxNameLabelSize.y);
Rect idLabelRect = new Rect(maxNameLabelSize.x, initialY, maxValueLabelSize.x, maxValueLabelSize.y);
foreach (NetworkIdentityInfo info in info)
{
GUI.Label(labelRect, info.name, styles.labelStyle);
GUI.Label(idLabelRect, info.value, styles.componentName);
labelRect.y += labelRect.height;
labelRect.x = initialX;
idLabelRect.y += idLabelRect.height;
}
// Show behaviours list in a different way than the name/value pairs above
float lastY = labelRect.y;
if (behavioursInfo != null && behavioursInfo.Count > 0)
{
Vector2 maxBehaviourLabelSize = GetMaxBehaviourLabelSize();
Rect behaviourRect = new Rect(initialX, labelRect.y + 10, maxBehaviourLabelSize.x, maxBehaviourLabelSize.y);
GUI.Label(behaviourRect, new GUIContent("Network Behaviours"), styles.labelStyle);
behaviourRect.x += 20; // indent names
behaviourRect.y += behaviourRect.height;
foreach (NetworkBehaviourInfo info in behavioursInfo)
{
if (info.behaviour == null)
{
// could be the case in the editor after existing play mode.
continue;
}
GUI.Label(behaviourRect, info.name, info.behaviour.enabled ? styles.componentName : styles.disabledName);
behaviourRect.y += behaviourRect.height;
lastY = behaviourRect.y;
}
if (identity.observers != null && identity.observers.Count > 0)
{
Rect observerRect = new Rect(initialX, lastY + 10, 200, 20);
GUI.Label(observerRect, new GUIContent("Network observers"), styles.labelStyle);
observerRect.x += 20; // indent names
observerRect.y += observerRect.height;
foreach (KeyValuePair<int, NetworkConnection> kvp in identity.observers)
{
GUI.Label(observerRect, kvp.Value.address + ":" + kvp.Value.connectionId, styles.componentName);
observerRect.y += observerRect.height;
lastY = observerRect.y;
}
}
if (identity.clientAuthorityOwner != null)
{
Rect ownerRect = new Rect(initialX, lastY + 10, 400, 20);
GUI.Label(ownerRect, new GUIContent("Client Authority: " + identity.clientAuthorityOwner), styles.labelStyle);
}
}
}
// Get the maximum size used by the value of information items
Vector2 GetMaxNameLabelSize()
{
Vector2 maxLabelSize = Vector2.zero;
foreach (NetworkIdentityInfo info in info)
{
Vector2 labelSize = styles.labelStyle.CalcSize(info.value);
if (maxLabelSize.x < labelSize.x)
{
maxLabelSize.x = labelSize.x;
}
if (maxLabelSize.y < labelSize.y)
{
maxLabelSize.y = labelSize.y;
}
}
return maxLabelSize;
}
Vector2 GetMaxBehaviourLabelSize()
{
Vector2 maxLabelSize = Vector2.zero;
foreach (NetworkBehaviourInfo behaviour in behavioursInfo)
{
Vector2 labelSize = styles.labelStyle.CalcSize(behaviour.name);
if (maxLabelSize.x < labelSize.x)
{
maxLabelSize.x = labelSize.x;
}
if (maxLabelSize.y < labelSize.y)
{
maxLabelSize.y = labelSize.y;
}
}
return maxLabelSize;
}
void GetNetworkInformation(GameObject gameObject)
{
identity = gameObject.GetComponent<NetworkIdentity>();
if (identity != null)
{
info = new List<NetworkIdentityInfo>
{
GetAssetId(),
GetString("Scene ID", identity.sceneId.ToString("X"))
};
if (!Application.isPlaying)
{
return;
}
info.Add(GetString("Network ID", identity.netId.ToString()));
info.Add(GetBoolean("Is Client", identity.isClient));
info.Add(GetBoolean("Is Server", identity.isServer));
info.Add(GetBoolean("Has Authority", identity.hasAuthority));
info.Add(GetBoolean("Is Local Player", identity.isLocalPlayer));
NetworkBehaviour[] behaviours = gameObject.GetComponents<NetworkBehaviour>();
if (behaviours.Length > 0)
{
behavioursInfo = new List<NetworkBehaviourInfo>();
foreach (NetworkBehaviour behaviour in behaviours)
{
NetworkBehaviourInfo info = new NetworkBehaviourInfo
{
name = new GUIContent(behaviour.GetType().FullName),
behaviour = behaviour
};
behavioursInfo.Add(info);
}
}
}
}
NetworkIdentityInfo GetAssetId()
{
string assetId = identity.assetId.ToString();
if (string.IsNullOrEmpty(assetId))
{
assetId = "<object has no prefab>";
}
return GetString("Asset ID", assetId);
}
static NetworkIdentityInfo GetString(string name, string value)
{
NetworkIdentityInfo info = new NetworkIdentityInfo
{
name = new GUIContent(name),
value = new GUIContent(value)
};
return info;
}
static NetworkIdentityInfo GetBoolean(string name, bool value)
{
NetworkIdentityInfo info = new NetworkIdentityInfo
{
name = new GUIContent(name),
value = new GUIContent((value ? "Yes" : "No"))
};
return info;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 51a99294efe134232932c34606737356
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,112 @@
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace Mirror
{
[CustomEditor(typeof(NetworkManager), true)]
[CanEditMultipleObjects]
public class NetworkManagerEditor : Editor
{
SerializedProperty spawnListProperty;
ReorderableList spawnList;
protected NetworkManager networkManager;
protected void Init()
{
if (spawnList == null)
{
networkManager = target as NetworkManager;
spawnListProperty = serializedObject.FindProperty("spawnPrefabs");
spawnList = new ReorderableList(serializedObject, spawnListProperty)
{
drawHeaderCallback = DrawHeader,
drawElementCallback = DrawChild,
onReorderCallback = Changed,
onRemoveCallback = RemoveButton,
onChangedCallback = Changed,
onAddCallback = AddButton,
elementHeight = 16 // this uses a 16x16 icon. other sizes make it stretch.
};
}
}
public override void OnInspectorGUI()
{
Init();
DrawDefaultInspector();
EditorGUI.BeginChangeCheck();
spawnList.DoLayoutList();
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
}
static void DrawHeader(Rect headerRect)
{
GUI.Label(headerRect, "Registered Spawnable Prefabs:");
}
internal void DrawChild(Rect r, int index, bool isActive, bool isFocused)
{
SerializedProperty prefab = spawnListProperty.GetArrayElementAtIndex(index);
GameObject go = (GameObject)prefab.objectReferenceValue;
GUIContent label;
if (go == null)
{
label = new GUIContent("Empty", "Drag a prefab with a NetworkIdentity here");
}
else
{
NetworkIdentity identity = go.GetComponent<NetworkIdentity>();
label = new GUIContent(go.name, identity != null ? "AssetId: [" + identity.assetId + "]" : "No Network Identity");
}
GameObject newGameObject = (GameObject)EditorGUI.ObjectField(r, label, go, typeof(GameObject), false);
if (newGameObject != go)
{
if (newGameObject != null && !newGameObject.GetComponent<NetworkIdentity>())
{
Debug.LogError("Prefab " + newGameObject + " cannot be added as spawnable as it doesn't have a NetworkIdentity.");
return;
}
prefab.objectReferenceValue = newGameObject;
}
}
internal void Changed(ReorderableList list)
{
EditorUtility.SetDirty(target);
}
internal void AddButton(ReorderableList list)
{
spawnListProperty.arraySize += 1;
list.index = spawnListProperty.arraySize - 1;
SerializedProperty obj = spawnListProperty.GetArrayElementAtIndex(spawnListProperty.arraySize - 1);
obj.objectReferenceValue = null;
spawnList.index = spawnList.count - 1;
Changed(list);
}
internal void RemoveButton(ReorderableList list)
{
spawnListProperty.DeleteArrayElementAtIndex(spawnList.index);
if (list.index >= spawnListProperty.arraySize)
{
list.index = spawnListProperty.arraySize - 1;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 519712eb07f7a44039df57664811c2c5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,90 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
namespace Mirror
{
public class NetworkScenePostProcess : MonoBehaviour
{
[PostProcessScene]
public static void OnPostProcessScene()
{
// find all NetworkIdentities in all scenes
// => can't limit it to GetActiveScene() because that wouldn't work
// for additive scene loads (the additively loaded scene is never
// the active scene)
// => ignore DontDestroyOnLoad scene! this avoids weird situations
// like in NetworkZones when we destroy the local player and
// load another scene afterwards, yet the local player is still
// in the FindObjectsOfType result with scene=DontDestroyOnLoad
// for some reason
// => OfTypeAll so disabled objects are included too
// => Unity 2019 returns prefabs here too, so filter them out.
IEnumerable<NetworkIdentity> identities = Resources.FindObjectsOfTypeAll<NetworkIdentity>()
.Where(identity => identity.gameObject.hideFlags != HideFlags.NotEditable &&
identity.gameObject.hideFlags != HideFlags.HideAndDontSave &&
identity.gameObject.scene.name != "DontDestroyOnLoad" &&
!PrefabUtility.IsPartOfPrefabAsset(identity.gameObject));
foreach (NetworkIdentity identity in identities)
{
// if we had a [ConflictComponent] attribute that would be better than this check.
// also there is no context about which scene this is in.
if (identity.GetComponent<NetworkManager>() != null)
{
Debug.LogError("NetworkManager has a NetworkIdentity component. This will cause the NetworkManager object to be disabled, so it is not recommended.");
}
// not spawned before?
// OnPostProcessScene is called after additive scene loads too,
// and we don't want to set main scene's objects inactive again
if (!identity.isClient && !identity.isServer)
{
// valid scene object?
// otherwise it might be an unopened scene that still has null
// sceneIds. builds are interrupted if they contain 0 sceneIds,
// but it's still possible that we call LoadScene in Editor
// for a previously unopened scene.
// (and only do SetActive if this was actually a scene object)
if (identity.sceneId != 0)
{
// set scene hash
identity.SetSceneIdSceneHashPartInternal();
// disable it
// note: NetworkIdentity.OnDisable adds itself to the
// spawnableObjects dictionary (only if sceneId != 0)
identity.gameObject.SetActive(false);
// safety check for prefabs with more than one NetworkIdentity
#if UNITY_2018_2_OR_NEWER
GameObject prefabGO = PrefabUtility.GetCorrespondingObjectFromSource(identity.gameObject) as GameObject;
#else
GameObject prefabGO = PrefabUtility.GetPrefabParent(identity.gameObject) as GameObject;
#endif
if (prefabGO)
{
#if UNITY_2018_3_OR_NEWER
GameObject prefabRootGO = prefabGO.transform.root.gameObject;
#else
GameObject prefabRootGO = PrefabUtility.FindPrefabRoot(prefabGO);
#endif
if (prefabRootGO)
{
if (prefabRootGO.GetComponentsInChildren<NetworkIdentity>().Length > 1)
{
Debug.LogWarningFormat("Prefab '{0}' has several NetworkIdentity components attached to itself or its children, this is not supported.", prefabRootGO.name);
}
}
}
}
// throwing an exception would only show it for one object
// because this function would return afterwards.
else Debug.LogError("Scene " + identity.gameObject.scene.path + " needs to be opened and resaved, because the scene object " + identity.name + " has no valid sceneId yet.");
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3ec1c414d821444a9e77f18a2c130ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,24 @@
using System.Collections.Generic;
using UnityEditor;
namespace Mirror
{
static class PreprocessorDefine
{
/// <summary>
/// Add define symbols as soon as Unity gets done compiling.
/// </summary>
[InitializeOnLoadMethod]
static void AddDefineSymbols()
{
HashSet<string> defines = new HashSet<string>(PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup).Split(';'))
{
"MIRROR",
"MIRROR_1726_OR_NEWER",
"MIRROR_3_0_OR_NEWER",
"MIRROR_3_12_OR_NEWER"
};
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, string.Join(";", defines));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f1d66fe74ec6f42dd974cba37d25d453
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,56 @@
using UnityEditor;
using UnityEngine;
namespace Mirror
{
[CustomPropertyDrawer(typeof(SceneAttribute))]
public class SceneDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType == SerializedPropertyType.String)
{
SceneAsset sceneObject = GetSceneObject(property.stringValue);
SceneAsset scene = (SceneAsset)EditorGUI.ObjectField(position, label, sceneObject, typeof(SceneAsset), true);
if (scene == null)
{
property.stringValue = "";
}
else if (scene.name != property.stringValue)
{
SceneAsset sceneObj = GetSceneObject(scene.name);
if (sceneObj == null)
{
Debug.LogWarning("The scene " + scene.name + " cannot be used. To use this scene add it to the build settings for the project");
}
else
{
property.stringValue = scene.name;
}
}
}
else
EditorGUI.LabelField(position, label.text, "Use [Scene] with strings.");
}
protected SceneAsset GetSceneObject(string sceneObjectName)
{
if (string.IsNullOrEmpty(sceneObjectName))
{
return null;
}
foreach (EditorBuildSettingsScene editorScene in EditorBuildSettings.scenes)
{
if (editorScene.path.IndexOf(sceneObjectName) != -1)
{
return AssetDatabase.LoadAssetAtPath(editorScene.path, typeof(SceneAsset)) as SceneAsset;
}
}
Debug.LogWarning("Scene [" + sceneObjectName + "] cannot be used. Add this scene to the 'Scenes in the Build' in build settings.");
return null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b24704a46211b4ea294aba8f58715cea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d9f8e6274119b4ce29e498cfb8aca8a4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
using UnityAssembly = UnityEditor.Compilation.Assembly;
namespace Mirror.Weaver
{
public static class CompilationFinishedHook
{
const string MirrorRuntimeAssemblyName = "Mirror";
const string MirrorWeaverAssemblyName = "Mirror.Weaver";
public static Action<string> OnWeaverMessage; // delegate for subscription to Weaver debug messages
public static Action<string> OnWeaverWarning; // delegate for subscription to Weaver warning messages
public static Action<string> OnWeaverError; // delete for subscription to Weaver error messages
public static bool WeaverEnabled { get; set; } // controls whether we weave any assemblies when CompilationPipeline delegates are invoked
public static bool UnityLogEnabled = true; // controls weather Weaver errors are reported direct to the Unity console (tests enable this)
public static bool WeaveFailed { get; private set; } // holds the result status of our latest Weave operation
// debug message handler that also calls OnMessageMethod delegate
static void HandleMessage(string msg)
{
if (UnityLogEnabled) Debug.Log(msg);
if (OnWeaverMessage != null) OnWeaverMessage.Invoke(msg);
}
// warning message handler that also calls OnWarningMethod delegate
static void HandleWarning(string msg)
{
if (UnityLogEnabled) Debug.LogWarning(msg);
if (OnWeaverWarning != null) OnWeaverWarning.Invoke(msg);
}
// error message handler that also calls OnErrorMethod delegate
static void HandleError(string msg)
{
if (UnityLogEnabled) Debug.LogError(msg);
if (OnWeaverError != null) OnWeaverError.Invoke(msg);
}
[InitializeOnLoadMethod]
static void OnInitializeOnLoad()
{
CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished;
}
static string FindMirrorRuntime()
{
foreach (UnityAssembly assembly in CompilationPipeline.GetAssemblies())
{
if (assembly.name == MirrorRuntimeAssemblyName)
{
return assembly.outputPath;
}
}
return "";
}
static bool CompilerMessagesContainError(CompilerMessage[] messages)
{
return messages.Any(msg => msg.type == CompilerMessageType.Error);
}
static void OnCompilationFinished(string assemblyPath, CompilerMessage[] messages)
{
// Do nothing if there were compile errors on the target
if (CompilerMessagesContainError(messages))
{
Debug.Log("Weaver: stop because compile errors on target");
return;
}
// Should not run on the editor only assemblies
if (assemblyPath.Contains("-Editor") || assemblyPath.Contains(".Editor"))
{
return;
}
// don't weave mirror files
string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
if (assemblyName == MirrorRuntimeAssemblyName || assemblyName == MirrorWeaverAssemblyName)
{
return;
}
// find Mirror.dll
string mirrorRuntimeDll = FindMirrorRuntime();
if (string.IsNullOrEmpty(mirrorRuntimeDll))
{
Debug.LogError("Failed to find Mirror runtime assembly");
return;
}
if (!File.Exists(mirrorRuntimeDll))
{
// this is normal, it happens with any assembly that is built before mirror
// such as unity packages or your own assemblies
// those don't need to be weaved
// if any assembly depends on mirror, then it will be built after
return;
}
// find UnityEngine.CoreModule.dll
string unityEngineCoreModuleDLL = UnityEditorInternal.InternalEditorUtility.GetEngineCoreModuleAssemblyPath();
if (string.IsNullOrEmpty(unityEngineCoreModuleDLL))
{
Debug.LogError("Failed to find UnityEngine assembly");
return;
}
// build directory list for later asm/symbol resolving using CompilationPipeline refs
HashSet<string> dependencyPaths = new HashSet<string>();
dependencyPaths.Add(Path.GetDirectoryName(assemblyPath));
foreach (UnityAssembly unityAsm in CompilationPipeline.GetAssemblies())
{
if (unityAsm.outputPath != assemblyPath) continue;
foreach (string unityAsmRef in unityAsm.compiledAssemblyReferences)
{
dependencyPaths.Add(Path.GetDirectoryName(unityAsmRef));
}
}
// passing null in the outputDirectory param will do an in-place update of the assembly
if (Program.Process(unityEngineCoreModuleDLL, mirrorRuntimeDll, null, new[] { assemblyPath }, dependencyPaths.ToArray(), HandleWarning, HandleError))
{
WeaveFailed = false;
//Debug.Log("Weaving succeeded for: " + assemblyPath);
}
else
{
WeaveFailed = true;
if (UnityLogEnabled) Debug.LogError("Weaving failed for: " + assemblyPath);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de2aeb2e8068f421a9a1febe408f7051
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,151 @@
using System;
using Mono.CecilX;
namespace Mirror.Weaver
{
public static class Extensions
{
public static bool IsDerivedFrom(this TypeDefinition td, TypeReference baseClass)
{
if (!td.IsClass)
return false;
// are ANY parent classes of baseClass?
TypeReference parent = td.BaseType;
while (parent != null)
{
string parentName = parent.FullName;
// strip generic parameters
int index = parentName.IndexOf('<');
if (index != -1)
{
parentName = parentName.Substring(0, index);
}
if (parentName == baseClass.FullName)
{
return true;
}
try
{
parent = parent.Resolve().BaseType;
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
return false;
}
public static TypeReference GetEnumUnderlyingType(this TypeDefinition td)
{
foreach (FieldDefinition field in td.Fields)
{
if (!field.IsStatic)
return field.FieldType;
}
throw new ArgumentException($"Invalid enum {td.FullName}");
}
public static bool ImplementsInterface(this TypeDefinition td, TypeReference baseInterface)
{
TypeDefinition typedef = td;
while (typedef != null)
{
foreach (InterfaceImplementation iface in typedef.Interfaces)
{
if (iface.InterfaceType.FullName == baseInterface.FullName)
return true;
}
try
{
TypeReference parent = typedef.BaseType;
typedef = parent?.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for pluins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
return false;
}
public static bool IsArrayType(this TypeReference tr)
{
if ((tr.IsArray && ((ArrayType)tr).ElementType.IsArray) || // jagged array
(tr.IsArray && ((ArrayType)tr).Rank > 1)) // multidimensional array
return false;
return true;
}
public static bool CanBeResolved(this TypeReference parent)
{
while (parent != null)
{
if (parent.Scope.Name == "Windows")
{
return false;
}
if (parent.Scope.Name == "mscorlib")
{
TypeDefinition resolved = parent.Resolve();
return resolved != null;
}
try
{
parent = parent.Resolve().BaseType;
}
catch
{
return false;
}
}
return true;
}
// Given a method of a generic class such as ArraySegment<T>.get_Count,
// and a generic instance such as ArraySegment<int>
// Creates a reference to the specialized method ArraySegment<int>.get_Count;
// Note that calling ArraySegment<T>.get_Count directly gives an invalid IL error
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, GenericInstanceType instanceType)
{
MethodReference reference = new MethodReference(self.Name, self.ReturnType, instanceType)
{
CallingConvention = self.CallingConvention,
HasThis = self.HasThis,
ExplicitThis = self.ExplicitThis
};
foreach (ParameterDefinition parameter in self.Parameters)
reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
foreach (GenericParameter generic_parameter in self.GenericParameters)
reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
return Weaver.CurrentAssembly.MainModule.ImportReference(reference);
}
public static CustomAttribute GetCustomAttribute(this MethodDefinition method, string attributeName)
{
foreach (CustomAttribute ca in method.CustomAttributes)
{
if (ca.AttributeType.FullName == attributeName)
return ca;
}
return null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 562a5cf0254cc45738e9aa549a7100b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,124 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Mdb;
using Mono.CecilX.Pdb;
namespace Mirror.Weaver
{
class Helpers
{
// This code is taken from SerializationWeaver
class AddSearchDirectoryHelper
{
delegate void AddSearchDirectoryDelegate(string directory);
readonly AddSearchDirectoryDelegate _addSearchDirectory;
public AddSearchDirectoryHelper(IAssemblyResolver assemblyResolver)
{
// reflection is used because IAssemblyResolver doesn't implement AddSearchDirectory but both DefaultAssemblyResolver and NuGetAssemblyResolver do
MethodInfo addSearchDirectory = assemblyResolver.GetType().GetMethod("AddSearchDirectory", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string) }, null);
if (addSearchDirectory == null)
throw new Exception("Assembly resolver doesn't implement AddSearchDirectory method.");
_addSearchDirectory = (AddSearchDirectoryDelegate)Delegate.CreateDelegate(typeof(AddSearchDirectoryDelegate), assemblyResolver, addSearchDirectory);
}
public void AddSearchDirectory(string directory)
{
_addSearchDirectory(directory);
}
}
public static string UnityEngineDLLDirectoryName()
{
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
return directoryName?.Replace(@"file:\", "");
}
public static ISymbolReaderProvider GetSymbolReaderProvider(string inputFile)
{
string nakedFileName = inputFile.Substring(0, inputFile.Length - 4);
if (File.Exists(nakedFileName + ".pdb"))
{
Console.WriteLine("Symbols will be read from " + nakedFileName + ".pdb");
return new PdbReaderProvider();
}
if (File.Exists(nakedFileName + ".dll.mdb"))
{
Console.WriteLine("Symbols will be read from " + nakedFileName + ".dll.mdb");
return new MdbReaderProvider();
}
Console.WriteLine("No symbols for " + inputFile);
return null;
}
public static string DestinationFileFor(string outputDir, string assemblyPath)
{
string fileName = Path.GetFileName(assemblyPath);
Debug.Assert(fileName != null, "fileName != null");
return Path.Combine(outputDir, fileName);
}
public static string PrettyPrintType(TypeReference type)
{
// generic instances, such as List<Int32>
if (type.IsGenericInstance)
{
GenericInstanceType giType = (GenericInstanceType)type;
return giType.Name.Substring(0, giType.Name.Length - 2) + "<" + string.Join(", ", giType.GenericArguments.Select(PrettyPrintType).ToArray()) + ">";
}
// generic types, such as List<T>
if (type.HasGenericParameters)
{
return type.Name.Substring(0, type.Name.Length - 2) + "<" + string.Join(", ", type.GenericParameters.Select(x => x.Name).ToArray()) + ">";
}
// non-generic type such as Int
return type.Name;
}
public static ReaderParameters ReaderParameters(string assemblyPath, IEnumerable<string> extraPaths, IAssemblyResolver assemblyResolver, string unityEngineDLLPath, string mirrorNetDLLPath)
{
ReaderParameters parameters = new ReaderParameters {ReadWrite = true};
if (assemblyResolver == null)
assemblyResolver = new DefaultAssemblyResolver();
AddSearchDirectoryHelper helper = new AddSearchDirectoryHelper(assemblyResolver);
helper.AddSearchDirectory(Path.GetDirectoryName(assemblyPath));
helper.AddSearchDirectory(UnityEngineDLLDirectoryName());
helper.AddSearchDirectory(Path.GetDirectoryName(unityEngineDLLPath));
helper.AddSearchDirectory(Path.GetDirectoryName(mirrorNetDLLPath));
if (extraPaths != null)
{
foreach (string path in extraPaths)
helper.AddSearchDirectory(path);
}
parameters.AssemblyResolver = assemblyResolver;
parameters.SymbolReaderProvider = GetSymbolReaderProvider(assemblyPath);
return parameters;
}
public static WriterParameters GetWriterParameters(ReaderParameters readParams)
{
WriterParameters writeParams = new WriterParameters();
if (readParams.SymbolReaderProvider is PdbReaderProvider)
{
//Log("Will export symbols of pdb format");
writeParams.SymbolWriterProvider = new PdbWriterProvider();
}
else if (readParams.SymbolReaderProvider is MdbReaderProvider)
{
//Log("Will export symbols of mdb format");
writeParams.SymbolWriterProvider = new MdbWriterProvider();
}
return writeParams;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6c4ed76daf48547c5abb7c58f8d20886
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,14 @@
{
"name": "Mirror.Weaver",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1d0b9d21c3ff546a4aa32399dfd33474
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e538d627280d2471b8c72fdea822ca49
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,153 @@
// all the [Command] code from NetworkBehaviourProcessor in one place
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class CommandProcessor
{
const string CmdPrefix = "InvokeCmd";
/*
// generates code like:
public void CallCmdThrust(float thrusting, int spin)
{
if (isServer)
{
// we are ON the server, invoke directly
CmdThrust(thrusting, spin);
return;
}
NetworkWriter networkWriter = new NetworkWriter();
networkWriter.Write(thrusting);
networkWriter.WritePackedUInt32((uint)spin);
base.SendCommandInternal(cmdName, networkWriter, cmdName);
}
*/
public static MethodDefinition ProcessCommandCall(TypeDefinition td, MethodDefinition md, CustomAttribute ca)
{
MethodDefinition cmd = new MethodDefinition("Call" + md.Name,
MethodAttributes.Public | MethodAttributes.HideBySig,
Weaver.voidType);
// add parameters
foreach (ParameterDefinition pd in md.Parameters)
{
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
ILProcessor cmdWorker = cmd.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(cmdWorker);
if (Weaver.GenerateLogErrors)
{
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldstr, "Call Command function " + md.Name));
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.logErrorReference));
}
// local client check
Instruction localClientLabel = cmdWorker.Create(OpCodes.Nop);
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.getBehaviourIsServer));
cmdWorker.Append(cmdWorker.Create(OpCodes.Brfalse, localClientLabel));
// call the cmd function directly.
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
for (int i = 0; i < md.Parameters.Count; i++)
{
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg, i + 1));
}
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, md));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
cmdWorker.Append(localClientLabel);
// NetworkWriter writer = new NetworkWriter();
NetworkBehaviourProcessor.WriteCreateWriter(cmdWorker);
// write all the arguments that the user passed to the Cmd call
if (!NetworkBehaviourProcessor.WriteArguments(cmdWorker, md, false))
return null;
string cmdName = md.Name;
int index = cmdName.IndexOf(CmdPrefix);
if (index > -1)
{
cmdName = cmdName.Substring(CmdPrefix.Length);
}
// invoke internal send and return
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0)); // load 'base.' to call the SendCommand function with
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldtoken, td));
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference)); // invokerClass
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldstr, cmdName));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldloc_0)); // writer
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldc_I4, NetworkBehaviourProcessor.GetChannelId(ca)));
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.sendCommandInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(cmdWorker);
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
return cmd;
}
/*
// generates code like:
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader)
{
if (!NetworkServer.active)
{
return;
}
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
}
*/
public static MethodDefinition ProcessCommandInvoke(TypeDefinition td, MethodDefinition md)
{
MethodDefinition cmd = new MethodDefinition(CmdPrefix + md.Name,
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor cmdWorker = cmd.Body.GetILProcessor();
Instruction label = cmdWorker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteServerActiveCheck(cmdWorker, md.Name, label, "Command");
// setup for reader
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Castclass, td));
if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(md, cmdWorker, false))
return null;
// invoke actual command function
cmdWorker.Append(cmdWorker.Create(OpCodes.Callvirt, md));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
NetworkBehaviourProcessor.AddInvokeParameters(cmd.Parameters);
return cmd;
}
public static bool ProcessMethodsValidateCommand(MethodDefinition md, CustomAttribute ca)
{
if (!md.Name.StartsWith("Cmd"))
{
Weaver.Error($"{md} must start with Cmd. Consider renaming it to Cmd{md.Name}");
return false;
}
if (md.IsStatic)
{
Weaver.Error($"{md} cannot be static");
return false;
}
// validate
return NetworkBehaviourProcessor.ProcessMethodsValidateFunction(md) &&
NetworkBehaviourProcessor.ProcessMethodsValidateParameters(md, ca);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,135 @@
// this class generates OnSerialize/OnDeserialize when inheriting from MessageBase
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
static class MessageClassProcessor
{
public static void Process(TypeDefinition td)
{
Weaver.DLog(td, "MessageClassProcessor Start");
GenerateSerialization(td);
if (Weaver.WeavingFailed)
{
return;
}
GenerateDeSerialization(td);
Weaver.DLog(td, "MessageClassProcessor Done");
}
static void GenerateSerialization(TypeDefinition td)
{
Weaver.DLog(td, " GenerateSerialization");
foreach (MethodDefinition m in td.Methods)
{
if (m.Name == "Serialize")
return;
}
if (td.Fields.Count == 0)
{
return;
}
// check for self-referencing types
foreach (FieldDefinition field in td.Fields)
{
if (field.FieldType.FullName == td.FullName)
{
Weaver.Error($"{td} has field ${field} that references itself");
return;
}
}
MethodDefinition serializeFunc = new MethodDefinition("Serialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.voidType);
serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
foreach (FieldDefinition field in td.Fields)
{
if (field.IsStatic || field.IsPrivate || field.IsSpecialName)
continue;
if (field.FieldType.Resolve().HasGenericParameters && !field.FieldType.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal))
{
Weaver.Error($"{field} cannot have generic type {field.FieldType}. Consider creating a class that derives the generic type");
return;
}
if (field.FieldType.Resolve().IsInterface)
{
Weaver.Error($"{field} has unsupported type. Use a concrete class instead of interface {field.FieldType}");
return;
}
MethodReference writeFunc = Writers.GetWriteFunc(field.FieldType);
if (writeFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldfld, field));
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error($"{field} has unsupported type");
return;
}
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
td.Methods.Add(serializeFunc);
}
static void GenerateDeSerialization(TypeDefinition td)
{
Weaver.DLog(td, " GenerateDeserialization");
foreach (MethodDefinition m in td.Methods)
{
if (m.Name == "Deserialize")
return;
}
if (td.Fields.Count == 0)
{
return;
}
MethodDefinition serializeFunc = new MethodDefinition("Deserialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.voidType);
serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
foreach (FieldDefinition field in td.Fields)
{
if (field.IsStatic || field.IsPrivate || field.IsSpecialName)
continue;
MethodReference readerFunc = Readers.GetReadFunc(field.FieldType);
if (readerFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc));
serWorker.Append(serWorker.Create(OpCodes.Stfld, field));
}
else
{
Weaver.Error($"{field} has unsupported type");
return;
}
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
td.Methods.Add(serializeFunc);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3544c9f00f6e5443ea3c30873c5a06ef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,77 @@
// this class only shows warnings in case we use SyncVars etc. for MonoBehaviour.
using Mono.CecilX;
namespace Mirror.Weaver
{
static class MonoBehaviourProcessor
{
public static void Process(TypeDefinition td)
{
ProcessSyncVars(td);
ProcessMethods(td);
}
static void ProcessSyncVars(TypeDefinition td)
{
// find syncvars
foreach (FieldDefinition fd in td.Fields)
{
foreach (CustomAttribute ca in fd.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName)
{
Weaver.Error($"[SyncVar] {fd} must be inside a NetworkBehaviour. {td} is not a NetworkBehaviour");
}
}
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
{
Weaver.Error($"{fd} is a SyncObject and must be inside a NetworkBehaviour. {td} is not a NetworkBehaviour");
}
}
}
static void ProcessMethods(TypeDefinition td)
{
// find command and RPC functions
foreach (MethodDefinition md in td.Methods)
{
foreach (CustomAttribute ca in md.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.CommandType.FullName)
{
Weaver.Error($"[Command] {md} must be declared inside a NetworkBehaviour");
}
if (ca.AttributeType.FullName == Weaver.ClientRpcType.FullName)
{
Weaver.Error($"[ClienRpc] {md} must be declared inside a NetworkBehaviour");
}
if (ca.AttributeType.FullName == Weaver.TargetRpcType.FullName)
{
Weaver.Error($"[TargetRpc] {md} must be declared inside a NetworkBehaviour");
}
string attributeName = ca.Constructor.DeclaringType.ToString();
switch (attributeName)
{
case "Mirror.ServerAttribute":
Weaver.Error($"[Server] {md} must be declared inside a NetworkBehaviour");
break;
case "Mirror.ServerCallbackAttribute":
Weaver.Error($"[ServerCallback] {md} must be declared inside a NetworkBehaviour");
break;
case "Mirror.ClientAttribute":
Weaver.Error($"[Client] {md} must be declared inside a NetworkBehaviour");
break;
case "Mirror.ClientCallbackAttribute":
Weaver.Error($"[ClientCallback] {md} must be declared inside a NetworkBehaviour");
break;
}
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 35c16722912b64af894e4f6668f2e54c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,858 @@
// this class processes SyncVars, Cmds, Rpcs, etc. of NetworkBehaviours
using System;
using System.Linq;
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
class NetworkBehaviourProcessor
{
readonly List<FieldDefinition> syncVars = new List<FieldDefinition>();
readonly List<FieldDefinition> syncObjects = new List<FieldDefinition>();
readonly Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>(); // <SyncVarField,NetIdField>
readonly List<MethodDefinition> commands = new List<MethodDefinition>();
readonly List<MethodDefinition> clientRpcs = new List<MethodDefinition>();
readonly List<MethodDefinition> targetRpcs = new List<MethodDefinition>();
readonly List<EventDefinition> eventRpcs = new List<EventDefinition>();
readonly List<MethodDefinition> commandInvocationFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> clientRpcInvocationFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> targetRpcInvocationFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> eventRpcInvocationFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> commandCallFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> clientRpcCallFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> targetRpcCallFuncs = new List<MethodDefinition>();
readonly TypeDefinition netBehaviourSubclass;
public NetworkBehaviourProcessor(TypeDefinition td)
{
Weaver.DLog(td, "NetworkBehaviourProcessor");
netBehaviourSubclass = td;
}
public void Process()
{
if (netBehaviourSubclass.HasGenericParameters)
{
Weaver.Error($"{netBehaviourSubclass} cannot have generic parameters");
return;
}
Weaver.DLog(netBehaviourSubclass, "Process Start");
MarkAsProcessed(netBehaviourSubclass);
SyncVarProcessor.ProcessSyncVars(netBehaviourSubclass, syncVars, syncObjects, syncVarNetIds);
ProcessMethods();
SyncEventProcessor.ProcessEvents(netBehaviourSubclass, eventRpcs, eventRpcInvocationFuncs);
if (Weaver.WeavingFailed)
{
return;
}
GenerateConstants();
GenerateSerialization();
if (Weaver.WeavingFailed)
{
return;
}
GenerateDeSerialization();
Weaver.DLog(netBehaviourSubclass, "Process Done");
}
/*
generates code like:
if (!NetworkClient.active)
Debug.LogError((object) "Command function CmdRespawn called on server.");
which is used in InvokeCmd, InvokeRpc, etc.
*/
public static void WriteClientActiveCheck(ILProcessor worker, string mdName, Instruction label, string errString)
{
// client active check
worker.Append(worker.Create(OpCodes.Call, Weaver.NetworkClientGetActive));
worker.Append(worker.Create(OpCodes.Brtrue, label));
worker.Append(worker.Create(OpCodes.Ldstr, errString + " " + mdName + " called on server."));
worker.Append(worker.Create(OpCodes.Call, Weaver.logErrorReference));
worker.Append(worker.Create(OpCodes.Ret));
worker.Append(label);
}
/*
generates code like:
if (!NetworkServer.active)
Debug.LogError((object) "Command CmdMsgWhisper called on client.");
*/
public static void WriteServerActiveCheck(ILProcessor worker, string mdName, Instruction label, string errString)
{
// server active check
worker.Append(worker.Create(OpCodes.Call, Weaver.NetworkServerGetActive));
worker.Append(worker.Create(OpCodes.Brtrue, label));
worker.Append(worker.Create(OpCodes.Ldstr, errString + " " + mdName + " called on client."));
worker.Append(worker.Create(OpCodes.Call, Weaver.logErrorReference));
worker.Append(worker.Create(OpCodes.Ret));
worker.Append(label);
}
public static void WriteSetupLocals(ILProcessor worker)
{
worker.Body.InitLocals = true;
worker.Body.Variables.Add(new VariableDefinition(Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
}
public static void WriteCreateWriter(ILProcessor worker)
{
// create writer
worker.Append(worker.Create(OpCodes.Call, Weaver.GetPooledWriterReference));
worker.Append(worker.Create(OpCodes.Stloc_0));
}
public static void WriteRecycleWriter(ILProcessor worker)
{
// NetworkWriterPool.Recycle(writer);
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Call, Weaver.RecycleWriterReference));
}
public static bool WriteArguments(ILProcessor worker, MethodDefinition md, bool skipFirst)
{
// write each argument
short argNum = 1;
foreach (ParameterDefinition pd in md.Parameters)
{
if (argNum == 1 && skipFirst)
{
argNum += 1;
continue;
}
MethodReference writeFunc = Writers.GetWriteFunc(pd.ParameterType);
if (writeFunc == null)
{
Weaver.Error($"{md} has invalid parameter {pd}" );
return false;
}
// use built-in writer func on writer object
worker.Append(worker.Create(OpCodes.Ldloc_0)); // writer object
worker.Append(worker.Create(OpCodes.Ldarg, argNum)); // argument
worker.Append(worker.Create(OpCodes.Call, writeFunc)); // call writer func on writer object
argNum += 1;
}
return true;
}
#region mark / check type as processed
public const string ProcessedFunctionName = "MirrorProcessed";
// by adding an empty MirrorProcessed() function
public static bool WasProcessed(TypeDefinition td)
{
return td.Methods.Any(method => method.Name == ProcessedFunctionName);
}
public static void MarkAsProcessed(TypeDefinition td)
{
if (!WasProcessed(td))
{
MethodDefinition versionMethod = new MethodDefinition(ProcessedFunctionName, MethodAttributes.Private, Weaver.voidType);
ILProcessor worker = versionMethod.Body.GetILProcessor();
worker.Append(worker.Create(OpCodes.Ret));
td.Methods.Add(versionMethod);
}
}
#endregion
void GenerateConstants()
{
if (commands.Count == 0 && clientRpcs.Count == 0 && targetRpcs.Count == 0 && eventRpcs.Count == 0 && syncObjects.Count == 0)
return;
Weaver.DLog(netBehaviourSubclass, " GenerateConstants ");
// find static constructor
MethodDefinition cctor = null;
bool cctorFound = false;
foreach (MethodDefinition md in netBehaviourSubclass.Methods)
{
if (md.Name == ".cctor")
{
cctor = md;
cctorFound = true;
}
}
if (cctor != null)
{
// remove the return opcode from end of function. will add our own later.
if (cctor.Body.Instructions.Count != 0)
{
Instruction ret = cctor.Body.Instructions[cctor.Body.Instructions.Count - 1];
if (ret.OpCode == OpCodes.Ret)
{
cctor.Body.Instructions.RemoveAt(cctor.Body.Instructions.Count - 1);
}
else
{
Weaver.Error($"{netBehaviourSubclass} has invalid class constructor");
return;
}
}
}
else
{
// make one!
cctor = new MethodDefinition(".cctor", MethodAttributes.Private |
MethodAttributes.HideBySig |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName |
MethodAttributes.Static,
Weaver.voidType);
}
// find instance constructor
MethodDefinition ctor = null;
foreach (MethodDefinition md in netBehaviourSubclass.Methods)
{
if (md.Name == ".ctor")
{
ctor = md;
Instruction ret = ctor.Body.Instructions[ctor.Body.Instructions.Count - 1];
if (ret.OpCode == OpCodes.Ret)
{
ctor.Body.Instructions.RemoveAt(ctor.Body.Instructions.Count - 1);
}
else
{
Weaver.Error($"{netBehaviourSubclass} has invalid constructor");
return;
}
break;
}
}
if (ctor == null)
{
Weaver.Error($"{netBehaviourSubclass} has invalid constructor");
return;
}
ILProcessor ctorWorker = ctor.Body.GetILProcessor();
ILProcessor cctorWorker = cctor.Body.GetILProcessor();
for (int i = 0; i < commands.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerCommandDelegateReference, commandInvocationFuncs[i], commands[i].Name);
}
for (int i = 0; i < clientRpcs.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, clientRpcInvocationFuncs[i], clientRpcs[i].Name);
}
for (int i = 0; i < targetRpcs.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, targetRpcInvocationFuncs[i], targetRpcs[i].Name);
}
for (int i = 0; i < eventRpcs.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerEventDelegateReference, eventRpcInvocationFuncs[i], eventRpcs[i].Name);
}
foreach (FieldDefinition fd in syncObjects)
{
SyncObjectInitializer.GenerateSyncObjectInitializer(ctorWorker, fd);
}
cctorWorker.Append(cctorWorker.Create(OpCodes.Ret));
if (!cctorFound)
{
netBehaviourSubclass.Methods.Add(cctor);
}
// finish ctor
ctorWorker.Append(ctorWorker.Create(OpCodes.Ret));
// in case class had no cctor, it might have BeforeFieldInit, so injected cctor would be called too late
netBehaviourSubclass.Attributes &= ~TypeAttributes.BeforeFieldInit;
}
/*
// This generates code like:
NetworkBehaviour.RegisterCommandDelegate(base.GetType(), "CmdThrust", new NetworkBehaviour.CmdDelegate(ShipControl.InvokeCmdCmdThrust));
*/
void GenerateRegisterCommandDelegate(ILProcessor awakeWorker, MethodReference registerMethod, MethodDefinition func, string cmdName)
{
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldtoken, netBehaviourSubclass));
awakeWorker.Append(awakeWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldstr, cmdName));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldnull));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldftn, func));
awakeWorker.Append(awakeWorker.Create(OpCodes.Newobj, Weaver.CmdDelegateConstructor));
awakeWorker.Append(awakeWorker.Create(OpCodes.Call, registerMethod));
}
void GenerateSerialization()
{
Weaver.DLog(netBehaviourSubclass, " GenerateSerialization");
foreach (MethodDefinition m in netBehaviourSubclass.Methods)
{
if (m.Name == "OnSerialize")
return;
}
if (syncVars.Count == 0)
{
// no synvars, no need for custom OnSerialize
return;
}
MethodDefinition serialize = new MethodDefinition("OnSerialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.boolType);
serialize.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
serialize.Parameters.Add(new ParameterDefinition("forceAll", ParameterAttributes.None, Weaver.boolType));
ILProcessor serWorker = serialize.Body.GetILProcessor();
serialize.Body.InitLocals = true;
// loc_0, this local variable is to determine if any variable was dirty
VariableDefinition dirtyLocal = new VariableDefinition(Weaver.boolType);
serialize.Body.Variables.Add(dirtyLocal);
MethodReference baseSerialize = Resolvers.ResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, "OnSerialize");
if (baseSerialize != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // forceAll
serWorker.Append(serWorker.Create(OpCodes.Call, baseSerialize));
serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // set dirtyLocal to result of base.OnSerialize()
}
// Generates: if (forceAll);
Instruction initialStateLabel = serWorker.Create(OpCodes.Nop);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // forceAll
serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel));
foreach (FieldDefinition syncVar in syncVars)
{
// Generates a writer call for each sync variable
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this
serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar));
MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType);
if (writeFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead");
return;
}
}
// always return true if forceAll
// Generates: return true
serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_1));
serWorker.Append(serWorker.Create(OpCodes.Ret));
// Generates: end if (forceAll);
serWorker.Append(initialStateLabel);
// write dirty bits before the data fields
// Generates: writer.WritePackedUInt64 (base.get_syncVarDirtyBits ());
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkBehaviourDirtyBitsReference));
serWorker.Append(serWorker.Create(OpCodes.Call, Writers.GetWriteFunc(Weaver.uint64Type)));
// generate a writer call for any dirty variable in this class
// start at number of syncvars in parent
int dirtyBit = Weaver.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName);
foreach (FieldDefinition syncVar in syncVars)
{
Instruction varLabel = serWorker.Create(OpCodes.Nop);
// Generates: if ((base.get_syncVarDirtyBits() & 1uL) != 0uL)
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkBehaviourDirtyBitsReference));
serWorker.Append(serWorker.Create(OpCodes.Ldc_I8, 1L << dirtyBit)); // 8 bytes = long
serWorker.Append(serWorker.Create(OpCodes.And));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
// Generates a call to the writer for that field
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar));
MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType);
if (writeFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead");
return;
}
// something was dirty
serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_1));
serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // set dirtyLocal to true
serWorker.Append(varLabel);
dirtyBit += 1;
}
if (Weaver.GenerateLogErrors)
{
serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Serialize " + netBehaviourSubclass.Name));
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference));
}
// generate: return dirtyLocal
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
serWorker.Append(serWorker.Create(OpCodes.Ret));
netBehaviourSubclass.Methods.Add(serialize);
}
public static int GetChannelId(CustomAttribute ca)
{
foreach (CustomAttributeNamedArgument customField in ca.Fields)
{
if (customField.Name == "channel")
{
return (int)customField.Argument.Value;
}
}
return 0;
}
void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize)
{
// check for Hook function
if (!SyncVarProcessor.CheckForHookFunction(netBehaviourSubclass, syncVar, out MethodDefinition foundMethod))
{
return;
}
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName ||
syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
// GameObject/NetworkIdentity SyncVar:
// OnSerialize sends writer.Write(go);
// OnDeserialize reads to __netId manually so we can use
// lookups in the getter (so it still works if objects
// move in and out of range repeatedly)
FieldDefinition netIdField = syncVarNetIds[syncVar];
VariableDefinition tmpValue = new VariableDefinition(Weaver.uint32Type);
deserialize.Body.Variables.Add(tmpValue);
// read id and store in a local variable
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint32Type)));
serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue));
if (foundMethod != null)
{
// call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32()))
// because we send/receive the netID, not the GameObject/NetworkIdentity
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this.
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar));
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName)
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarGameObjectReference));
else if (syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference));
serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
}
// set the netid field
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField));
}
else
{
MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType);
if (readFunc == null)
{
Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead");
return;
}
VariableDefinition tmpValue = new VariableDefinition(syncVar.FieldType);
deserialize.Body.Variables.Add(tmpValue);
// read value and put it in a local variable
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, readFunc));
serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue));
if (foundMethod != null)
{
// call hook
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
}
// set the property
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar));
}
}
void GenerateDeSerialization()
{
Weaver.DLog(netBehaviourSubclass, " GenerateDeSerialization");
foreach (MethodDefinition m in netBehaviourSubclass.Methods)
{
if (m.Name == "OnDeserialize")
return;
}
if (syncVars.Count == 0)
{
// no synvars, no need for custom OnDeserialize
return;
}
MethodDefinition serialize = new MethodDefinition("OnDeserialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.voidType);
serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
serialize.Parameters.Add(new ParameterDefinition("initialState", ParameterAttributes.None, Weaver.boolType));
ILProcessor serWorker = serialize.Body.GetILProcessor();
// setup local for dirty bits
serialize.Body.InitLocals = true;
VariableDefinition dirtyBitsLocal = new VariableDefinition(Weaver.int64Type);
serialize.Body.Variables.Add(dirtyBitsLocal);
MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, "OnDeserialize");
if (baseDeserialize != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // initialState
serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize));
}
// Generates: if (initialState);
Instruction initialStateLabel = serWorker.Create(OpCodes.Nop);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel));
foreach (FieldDefinition syncVar in syncVars)
{
DeserializeField(syncVar, serWorker, serialize);
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
// Generates: end if (initialState);
serWorker.Append(initialStateLabel);
// get dirty bits
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint64Type)));
serWorker.Append(serWorker.Create(OpCodes.Stloc_0));
// conditionally read each syncvar
int dirtyBit = Weaver.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName); // start at number of syncvars in parent
foreach (FieldDefinition syncVar in syncVars)
{
Instruction varLabel = serWorker.Create(OpCodes.Nop);
// check if dirty bit is set
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
serWorker.Append(serWorker.Create(OpCodes.Ldc_I8, 1L << dirtyBit));
serWorker.Append(serWorker.Create(OpCodes.And));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
DeserializeField(syncVar, serWorker, serialize);
serWorker.Append(varLabel);
dirtyBit += 1;
}
if (Weaver.GenerateLogErrors)
{
serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Deserialize " + netBehaviourSubclass.Name));
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference));
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
netBehaviourSubclass.Methods.Add(serialize);
}
public static bool ProcessNetworkReaderParameters(MethodDefinition md, ILProcessor worker, bool skipFirst)
{
int count = 0;
// read cmd args from NetworkReader
foreach (ParameterDefinition arg in md.Parameters)
{
if (count++ == 0 && skipFirst)
{
continue;
}
MethodReference readFunc = Readers.GetReadFunc(arg.ParameterType); //?
if (readFunc != null)
{
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Call, readFunc));
// conversion.. is this needed?
if (arg.ParameterType.FullName == Weaver.singleType.FullName)
{
worker.Append(worker.Create(OpCodes.Conv_R4));
}
else if (arg.ParameterType.FullName == Weaver.doubleType.FullName)
{
worker.Append(worker.Create(OpCodes.Conv_R8));
}
}
else
{
Weaver.Error($"{md} has invalid parameter {arg}. Unsupported type {arg.ParameterType}, use a supported Mirror type instead");
return false;
}
}
return true;
}
public static void AddInvokeParameters(ICollection<ParameterDefinition> collection)
{
collection.Add(new ParameterDefinition("obj", ParameterAttributes.None, Weaver.NetworkBehaviourType2));
collection.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
}
public static bool ProcessMethodsValidateFunction(MethodReference md)
{
if (md.ReturnType.FullName == Weaver.IEnumeratorType.FullName)
{
Weaver.Error($"{md} cannot be a coroutine");
return false;
}
if (md.ReturnType.FullName != Weaver.voidType.FullName)
{
Weaver.Error($"{md} cannot return a value. Make it void instead");
return false;
}
if (md.HasGenericParameters)
{
Weaver.Error($"{md} cannot have generic parameters");
return false;
}
return true;
}
public static bool ProcessMethodsValidateParameters(MethodReference md, CustomAttribute ca)
{
for (int i = 0; i < md.Parameters.Count; ++i)
{
ParameterDefinition p = md.Parameters[i];
if (p.IsOut)
{
Weaver.Error($"{md} cannot have out parameters");
return false;
}
if (p.IsOptional)
{
Weaver.Error($"{md} cannot have optional parameters");
return false;
}
if (p.ParameterType.Resolve().IsAbstract)
{
Weaver.Error($"{md} has invalid parameter {p}. Use concrete type instead of abstract type {p.ParameterType}");
return false;
}
if (p.ParameterType.IsByReference)
{
Weaver.Error($"{md} has invalid parameter {p}. Use supported type instead of reference type {p.ParameterType}");
return false;
}
// TargetRPC is an exception to this rule and can have a NetworkConnection as first parameter
if (p.ParameterType.FullName == Weaver.NetworkConnectionType.FullName &&
!(ca.AttributeType.FullName == Weaver.TargetRpcType.FullName && i == 0))
{
Weaver.Error($"{md} has invalid parameer {p}. Cannot pass NeworkConnections");
return false;
}
if (p.ParameterType.Resolve().IsDerivedFrom(Weaver.ComponentType))
{
if (p.ParameterType.FullName != Weaver.NetworkIdentityType.FullName)
{
Weaver.Error($"{md} has invalid parameter {p}. Cannot pass components in remote method calls");
return false;
}
}
}
return true;
}
void ProcessMethods()
{
HashSet<string> names = new HashSet<string>();
// find command and RPC functions
foreach (MethodDefinition md in netBehaviourSubclass.Methods)
{
foreach (CustomAttribute ca in md.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.CommandType.FullName)
{
ProcessCommand(names, md, ca);
break;
}
if (ca.AttributeType.FullName == Weaver.TargetRpcType.FullName)
{
ProcessTargetRpc(names, md, ca);
break;
}
if (ca.AttributeType.FullName == Weaver.ClientRpcType.FullName)
{
ProcessClientRpc(names, md, ca);
break;
}
}
}
// cmds
foreach (MethodDefinition md in commandInvocationFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
foreach (MethodDefinition md in commandCallFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
// rpcs
foreach (MethodDefinition md in clientRpcInvocationFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
foreach (MethodDefinition md in targetRpcInvocationFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
foreach (MethodDefinition md in clientRpcCallFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
foreach (MethodDefinition md in targetRpcCallFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
}
void ProcessClientRpc(HashSet<string> names, MethodDefinition md, CustomAttribute ca)
{
if (!RpcProcessor.ProcessMethodsValidateRpc(md, ca))
{
return;
}
if (names.Contains(md.Name))
{
Weaver.Error("Duplicate ClientRpc name [" + netBehaviourSubclass.FullName + ":" + md.Name + "]");
return;
}
names.Add(md.Name);
clientRpcs.Add(md);
MethodDefinition rpcFunc = RpcProcessor.ProcessRpcInvoke(netBehaviourSubclass, md);
if (rpcFunc != null)
{
clientRpcInvocationFuncs.Add(rpcFunc);
}
MethodDefinition rpcCallFunc = RpcProcessor.ProcessRpcCall(netBehaviourSubclass, md, ca);
if (rpcCallFunc != null)
{
clientRpcCallFuncs.Add(rpcCallFunc);
Weaver.WeaveLists.replaceMethods[md.FullName] = rpcCallFunc;
}
}
void ProcessTargetRpc(HashSet<string> names, MethodDefinition md, CustomAttribute ca)
{
if (!TargetRpcProcessor.ProcessMethodsValidateTargetRpc(md, ca))
return;
if (names.Contains(md.Name))
{
Weaver.Error("Duplicate Target Rpc name [" + netBehaviourSubclass.FullName + ":" + md.Name + "]");
return;
}
names.Add(md.Name);
targetRpcs.Add(md);
MethodDefinition rpcFunc = TargetRpcProcessor.ProcessTargetRpcInvoke(netBehaviourSubclass, md);
if (rpcFunc != null)
{
targetRpcInvocationFuncs.Add(rpcFunc);
}
MethodDefinition rpcCallFunc = TargetRpcProcessor.ProcessTargetRpcCall(netBehaviourSubclass, md, ca);
if (rpcCallFunc != null)
{
targetRpcCallFuncs.Add(rpcCallFunc);
Weaver.WeaveLists.replaceMethods[md.FullName] = rpcCallFunc;
}
}
void ProcessCommand(HashSet<string> names, MethodDefinition md, CustomAttribute ca)
{
if (!CommandProcessor.ProcessMethodsValidateCommand(md, ca))
return;
if (names.Contains(md.Name))
{
Weaver.Error("Duplicate Command name [" + netBehaviourSubclass.FullName + ":" + md.Name + "]");
return;
}
names.Add(md.Name);
commands.Add(md);
MethodDefinition cmdFunc = CommandProcessor.ProcessCommandInvoke(netBehaviourSubclass, md);
if (cmdFunc != null)
{
commandInvocationFuncs.Add(cmdFunc);
}
MethodDefinition cmdCallFunc = CommandProcessor.ProcessCommandCall(netBehaviourSubclass, md, ca);
if (cmdCallFunc != null)
{
commandCallFuncs.Add(cmdCallFunc);
Weaver.WeaveLists.replaceMethods[md.FullName] = cmdCallFunc;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8118d606be3214e5d99943ec39530dd8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,356 @@
using System;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class PropertySiteProcessor
{
public static void ProcessSitesModule(ModuleDefinition moduleDef)
{
DateTime startTime = DateTime.Now;
//Search through the types
foreach (TypeDefinition td in moduleDef.Types)
{
if (td.IsClass)
{
ProcessSiteClass(td);
}
}
if (Weaver.WeaveLists.generateContainerClass != null)
{
moduleDef.Types.Add(Weaver.WeaveLists.generateContainerClass);
Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.WeaveLists.generateContainerClass);
foreach (MethodDefinition f in Weaver.WeaveLists.generatedReadFunctions)
{
Weaver.CurrentAssembly.MainModule.ImportReference(f);
}
foreach (MethodDefinition f in Weaver.WeaveLists.generatedWriteFunctions)
{
Weaver.CurrentAssembly.MainModule.ImportReference(f);
}
}
Console.WriteLine(" ProcessSitesModule " + moduleDef.Name + " elapsed time:" + (DateTime.Now - startTime));
}
static void ProcessSiteClass(TypeDefinition td)
{
//Console.WriteLine(" ProcessSiteClass " + td);
foreach (MethodDefinition md in td.Methods)
{
ProcessSiteMethod(td, md);
}
foreach (TypeDefinition nested in td.NestedTypes)
{
ProcessSiteClass(nested);
}
}
static void ProcessSiteMethod(TypeDefinition td, MethodDefinition md)
{
// process all references to replaced members with properties
//Weaver.DLog(td, " ProcessSiteMethod " + md);
if (md.Name == ".cctor" ||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
md.Name.StartsWith("CallCmd") ||
md.Name.StartsWith("InvokeCmd") ||
md.Name.StartsWith("InvokeRpc") ||
md.Name.StartsWith("InvokeSyn"))
return;
if (md.Body != null && md.Body.Instructions != null)
{
// TODO move this to NetworkBehaviourProcessor
foreach (CustomAttribute attr in md.CustomAttributes)
{
switch (attr.Constructor.DeclaringType.ToString())
{
case "Mirror.ServerAttribute":
InjectServerGuard(td, md, true);
break;
case "Mirror.ServerCallbackAttribute":
InjectServerGuard(td, md, false);
break;
case "Mirror.ClientAttribute":
InjectClientGuard(td, md, true);
break;
case "Mirror.ClientCallbackAttribute":
InjectClientGuard(td, md, false);
break;
}
}
for (int iCount= 0; iCount < md.Body.Instructions.Count;)
{
Instruction instr = md.Body.Instructions[iCount];
iCount += ProcessInstruction(md, instr, iCount);
}
}
}
static void InjectServerGuard(TypeDefinition td, MethodDefinition md, bool logWarning)
{
if (!Weaver.IsNetworkBehaviour(td))
{
Weaver.Error($"[Server] {md} must be declared in a NetworkBehaviour");
return;
}
ILProcessor worker = md.Body.GetILProcessor();
Instruction top = md.Body.Instructions[0];
worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.NetworkServerGetActive));
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
if (logWarning)
{
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, "[Server] function '" + md.FullName + "' called on client"));
worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.logWarningReference));
}
InjectGuardParameters(md, worker, top);
InjectGuardReturnValue(md, worker, top);
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
}
static void InjectClientGuard(TypeDefinition td, MethodDefinition md, bool logWarning)
{
if (!Weaver.IsNetworkBehaviour(td))
{
Weaver.Error($"[Client] {md} must be declared in a NetworkBehaviour");
return;
}
ILProcessor worker = md.Body.GetILProcessor();
Instruction top = md.Body.Instructions[0];
worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.NetworkClientGetActive));
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
if (logWarning)
{
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, "[Client] function '" + md.FullName + "' called on server"));
worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.logWarningReference));
}
InjectGuardParameters(md, worker, top);
InjectGuardReturnValue(md, worker, top);
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
}
// replaces syncvar write access with the NetworkXYZ.get property calls
static void ProcessInstructionSetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
{
// dont replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
//replace with property
//DLog(td, " replacing " + md.Name + ":" + i);
i.OpCode = OpCodes.Call;
i.Operand = replacement;
//DLog(td, " replaced " + md.Name + ":" + i);
}
}
// replaces syncvar read access with the NetworkXYZ.get property calls
static void ProcessInstructionGetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
{
// dont replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
if (Weaver.WeaveLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
//replace with property
//DLog(td, " replacing " + md.Name + ":" + i);
i.OpCode = OpCodes.Call;
i.Operand = replacement;
//DLog(td, " replaced " + md.Name + ":" + i);
}
}
static int ProcessInstruction(MethodDefinition md, Instruction instr, int iCount)
{
if (instr.OpCode == OpCodes.Call || instr.OpCode == OpCodes.Callvirt)
{
if (instr.Operand is MethodReference opMethod)
{
ProcessInstructionMethod(md, instr, opMethod, iCount);
}
}
if (instr.OpCode == OpCodes.Stfld)
{
// this instruction sets the value of a field. cache the field reference.
if (instr.Operand is FieldDefinition opField)
{
ProcessInstructionSetterField(md, instr, opField);
}
}
if (instr.OpCode == OpCodes.Ldfld)
{
// this instruction gets the value of a field. cache the field reference.
if (instr.Operand is FieldDefinition opField)
{
ProcessInstructionGetterField(md, instr, opField);
}
}
if (instr.OpCode == OpCodes.Ldflda)
{
// loading a field by reference, watch out for initobj instruction
// see https://github.com/vis2k/Mirror/issues/696
if (instr.Operand is FieldDefinition opField)
{
return ProcessInstructionLoadAddress(md, instr, opField, iCount);
}
}
return 1;
}
static int ProcessInstructionLoadAddress(MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
{
// dont replace property call sites in constructors
if (md.Name == ".ctor")
return 1;
// does it set a field that we replaced?
if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
// we have a replacement for this property
// is the next instruction a initobj?
Instruction nextInstr = md.Body.Instructions[iCount + 1];
if (nextInstr.OpCode == OpCodes.Initobj)
{
// we need to replace this code with:
// var tmp = new MyStruct();
// this.set_Networkxxxx(tmp);
ILProcessor worker = md.Body.GetILProcessor();
VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
md.Body.Variables.Add(tmpVariable);
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
worker.Remove(instr);
worker.Remove(nextInstr);
return 4;
}
}
return 1;
}
static void ProcessInstructionMethod(MethodDefinition md, Instruction instr, MethodReference opMethodRef, int iCount)
{
//DLog(td, "ProcessInstructionMethod " + opMethod.Name);
if (opMethodRef.Name == "Invoke")
{
// Events use an "Invoke" method to call the delegate.
// this code replaces the "Invoke" instruction with the generated "Call***" instruction which send the event to the server.
// but the "Invoke" instruction is called on the event field - where the "call" instruction is not.
// so the earlier instruction that loads the event field is replaced with a Noop.
// go backwards until find a ldfld instruction that matches ANY event
bool found = false;
while (iCount > 0 && !found)
{
iCount -= 1;
Instruction inst = md.Body.Instructions[iCount];
if (inst.OpCode == OpCodes.Ldfld)
{
FieldReference opField = inst.Operand as FieldReference;
// find replaceEvent with matching name
// NOTE: original weaver compared .Name, not just the MethodDefinition,
// that's why we use dict<string,method>.
if (Weaver.WeaveLists.replaceEvents.TryGetValue(opField.Name, out MethodDefinition replacement))
{
instr.Operand = replacement;
inst.OpCode = OpCodes.Nop;
found = true;
}
}
}
}
else
{
// should it be replaced?
// NOTE: original weaver compared .FullName, not just the MethodDefinition,
// that's why we use dict<string,method>.
if (Weaver.WeaveLists.replaceMethods.TryGetValue(opMethodRef.FullName, out MethodDefinition replacement))
{
//DLog(td, " replacing " + md.Name + ":" + i);
instr.Operand = replacement;
//DLog(td, " replaced " + md.Name + ":" + i);
}
}
}
// this is required to early-out from a function with "ref" or "out" parameters
static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top)
{
int offset = md.Resolve().IsStatic ? 0 : 1;
for (int index = 0; index < md.Parameters.Count; index++)
{
ParameterDefinition param = md.Parameters[index];
if (param.IsOut)
{
TypeReference elementType = param.ParameterType.GetElementType();
if (elementType.IsPrimitive)
{
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
worker.InsertBefore(top, worker.Create(OpCodes.Ldc_I4_0));
worker.InsertBefore(top, worker.Create(OpCodes.Stind_I4));
}
else
{
md.Body.Variables.Add(new VariableDefinition(elementType));
md.Body.InitLocals = true;
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType));
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType));
}
}
}
}
// this is required to early-out from a function with a return value.
static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top)
{
if (md.ReturnType.FullName != Weaver.voidType.FullName)
{
if (md.ReturnType.IsPrimitive)
{
worker.InsertBefore(top, worker.Create(OpCodes.Ldc_I4_0));
}
else
{
md.Body.Variables.Add(new VariableDefinition(md.ReturnType));
md.Body.InitLocals = true;
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, md.ReturnType));
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d48f1ab125e9940a995603796bccc59e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,98 @@
using System;
using Mono.CecilX;
using UnityEditor.Compilation;
using System.Linq;
using System.Collections.Generic;
using System.IO;
namespace Mirror.Weaver
{
public static class ReaderWriterProcessor
{
// find all readers and writers and register them
public static void ProcessReadersAndWriters(AssemblyDefinition CurrentAssembly)
{
Readers.Init();
Writers.Init();
foreach (Assembly unityAsm in CompilationPipeline.GetAssemblies())
{
if (unityAsm.name != CurrentAssembly.Name.Name)
{
try
{
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(unityAsm.outputPath, new ReaderParameters { ReadWrite = false, ReadSymbols = false, AssemblyResolver = asmResolver }))
{
ProcessAssemblyClasses(CurrentAssembly, assembly);
}
}
catch(FileNotFoundException)
{
// During first import, this gets called before some assemblies
// are built, just skip them
}
}
}
ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly);
}
static void ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly)
{
foreach (TypeDefinition klass in assembly.MainModule.Types)
{
// extension methods only live in static classes
// static classes are represented as sealed and abstract
if (klass.IsAbstract && klass.IsSealed)
{
LoadWriters(CurrentAssembly, klass);
LoadReaders(CurrentAssembly, klass);
}
}
}
static void LoadWriters(AssemblyDefinition currentAssembly, TypeDefinition klass)
{
// register all the writers in this class. Skip the ones with wrong signature
foreach (MethodDefinition method in klass.Methods)
{
if (method.Parameters.Count != 2)
continue;
if (method.Parameters[0].ParameterType.FullName != "Mirror.NetworkWriter")
continue;
if (method.ReturnType.FullName != "System.Void")
continue;
if (method.GetCustomAttribute("System.Runtime.CompilerServices.ExtensionAttribute") == null)
continue;
TypeReference dataType = method.Parameters[1].ParameterType;
Writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
}
}
static void LoadReaders(AssemblyDefinition currentAssembly, TypeDefinition klass)
{
// register all the reader in this class. Skip the ones with wrong signature
foreach (MethodDefinition method in klass.Methods)
{
if (method.Parameters.Count != 1)
continue;
if (method.Parameters[0].ParameterType.FullName != "Mirror.NetworkReader")
continue;
if (method.ReturnType.FullName == "System.Void")
continue;
if (method.GetCustomAttribute("System.Runtime.CompilerServices.ExtensionAttribute") == null)
continue;
Readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f3263602f0a374ecd8d08588b1fc2f76
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,110 @@
// all the [Rpc] code from NetworkBehaviourProcessor in one place
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class RpcProcessor
{
public const string RpcPrefix = "InvokeRpc";
public static MethodDefinition ProcessRpcInvoke(TypeDefinition td, MethodDefinition md)
{
MethodDefinition rpc = new MethodDefinition(
RpcPrefix + md.Name,
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor rpcWorker = rpc.Body.GetILProcessor();
Instruction label = rpcWorker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(rpcWorker, md.Name, label, "RPC");
// setup for reader
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0));
rpcWorker.Append(rpcWorker.Create(OpCodes.Castclass, td));
if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(md, rpcWorker, false))
return null;
// invoke actual command function
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, md));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
return rpc;
}
/* generates code like:
public void CallRpcTest (int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32((uint)param);
base.SendRPCInternal(typeof(class),"RpcTest", writer, 0);
}
*/
public static MethodDefinition ProcessRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute ca)
{
MethodDefinition rpc = new MethodDefinition("Call" + md.Name, MethodAttributes.Public |
MethodAttributes.HideBySig,
Weaver.voidType);
// add paramters
foreach (ParameterDefinition pd in md.Parameters)
{
rpc.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
ILProcessor rpcWorker = rpc.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(rpcWorker);
NetworkBehaviourProcessor.WriteCreateWriter(rpcWorker);
// write all the arguments that the user passed to the Rpc call
if (!NetworkBehaviourProcessor.WriteArguments(rpcWorker, md, false))
return null;
string rpcName = md.Name;
int index = rpcName.IndexOf(RpcPrefix);
if (index > -1)
{
rpcName = rpcName.Substring(RpcPrefix.Length);
}
// invoke SendInternal and return
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0)); // this
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldtoken, td));
rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference)); // invokerClass
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldstr, rpcName));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldloc_0)); // writer
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldc_I4, NetworkBehaviourProcessor.GetChannelId(ca)));
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.sendRpcInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(rpcWorker);
rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
return rpc;
}
public static bool ProcessMethodsValidateRpc(MethodDefinition md, CustomAttribute ca)
{
if (!md.Name.StartsWith("Rpc"))
{
Weaver.Error($"{md} must start with Rpc. Consider renaming it to Rpc{md.Name}");
return false;
}
if (md.IsStatic)
{
Weaver.Error($"{md} must not be static");
return false;
}
// validate
return NetworkBehaviourProcessor.ProcessMethodsValidateFunction(md) &&
NetworkBehaviourProcessor.ProcessMethodsValidateParameters(md, ca);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3cb7051ff41947e59bba58bdd2b73fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,19 @@
// this class generates OnSerialize/OnDeserialize for SyncLists
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
static class SyncDictionaryProcessor
{
/// <summary>
/// Generates serialization methods for synclists
/// </summary>
/// <param name="td">The synclist class</param>
public static void Process(TypeDefinition td)
{
SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeKey", "DeserializeKey");
SyncObjectProcessor.GenerateSerialization(td, 1, "SerializeItem", "DeserializeItem");
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29e4a45f69822462ab0b15adda962a29
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show more