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:
parent
e601d3f4b6
commit
6f6d436551
515 changed files with 43532 additions and 98 deletions
|
@ -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>
|
||||
|
|
|
@ -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
11
Assets/Code/ActPhase.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
|
||||
[Serializable]
|
||||
public enum ActPhase {
|
||||
NONE,
|
||||
INIT,
|
||||
START,
|
||||
END,
|
||||
CLEANUP
|
||||
}
|
11
Assets/Code/ActPhase.cs.meta
Normal file
11
Assets/Code/ActPhase.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7e1d130c18e44bc4fa2a106ff85bd45d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
5
Assets/Code/ActResults.cs
Normal file
5
Assets/Code/ActResults.cs
Normal file
|
@ -0,0 +1,5 @@
|
|||
using System;
|
||||
|
||||
|
||||
[Serializable]
|
||||
public class ActResults {};
|
11
Assets/Code/ActResults.cs.meta
Normal file
11
Assets/Code/ActResults.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ff258aa9f24aa1b4d9c0c402166784d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
7
Assets/Code/ActSettings.cs
Normal file
7
Assets/Code/ActSettings.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
|
||||
|
||||
[Serializable]
|
||||
public class ActSettings {
|
||||
public string type;
|
||||
}
|
11
Assets/Code/ActSettings.cs.meta
Normal file
11
Assets/Code/ActSettings.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cb49ab9cfe218ab47bececca8851077c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -5,7 +5,7 @@ MonoImporter:
|
|||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
public class DrawableFrame : MonoBehaviour
|
||||
{
|
||||
|
|
|
@ -5,7 +5,7 @@ MonoImporter:
|
|||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -50
|
||||
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
|
@ -23,35 +23,6 @@ public class DrawingController : ActController
|
|||
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();
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
11
Assets/Code/DrawingResults.cs
Normal file
11
Assets/Code/DrawingResults.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
|
||||
[Serializable]
|
||||
public class DrawingResults : ActResults {
|
||||
public readonly byte[] pngBytes;
|
||||
|
||||
public DrawingResults(byte[] pngBytes) {
|
||||
this.pngBytes = pngBytes;
|
||||
}
|
||||
}
|
11
Assets/Code/DrawingResults.cs.meta
Normal file
11
Assets/Code/DrawingResults.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c8c417ef78cbd61438bff61a5dda66de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
24
Assets/Code/DrawingSettings.cs
Normal file
24
Assets/Code/DrawingSettings.cs
Normal 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;
|
||||
}
|
||||
}
|
11
Assets/Code/DrawingSettings.cs.meta
Normal file
11
Assets/Code/DrawingSettings.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b882786176f59d04b897dfa2cfbe3f95
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
48
Assets/Code/NetworkMessages.cs
Normal file
48
Assets/Code/NetworkMessages.cs
Normal 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;
|
||||
}
|
||||
}
|
11
Assets/Code/NetworkMessages.cs.meta
Normal file
11
Assets/Code/NetworkMessages.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 26964efd4b147744fb4bb183d75abaa6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -5,7 +5,7 @@ MonoImporter:
|
|||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
|
@ -5,7 +5,7 @@ MonoImporter:
|
|||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -5,7 +5,7 @@ MonoImporter:
|
|||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
15
Assets/Code/ServerMainController.cs
Normal file
15
Assets/Code/ServerMainController.cs
Normal 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;
|
||||
}
|
||||
}
|
11
Assets/Code/ServerMainController.cs.meta
Normal file
11
Assets/Code/ServerMainController.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 89fe2c8d614a60e42b42812f5bdcfb7f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -5,7 +5,7 @@ MonoImporter:
|
|||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
|
@ -5,7 +5,7 @@ MonoImporter:
|
|||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
|
@ -5,7 +5,7 @@ MonoImporter:
|
|||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 4422084297763085224, guid: 0000000000000000d000000000000000, type: 0}
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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:
|
||||
|
|
12
Assets/Code/TypingResults.cs
Normal file
12
Assets/Code/TypingResults.cs
Normal 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;
|
||||
}
|
||||
}
|
11
Assets/Code/TypingResults.cs.meta
Normal file
11
Assets/Code/TypingResults.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 400c8f3c2f7b6cf468dc2eb5fcccea62
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
18
Assets/Code/TypingSettings.cs
Normal file
18
Assets/Code/TypingSettings.cs
Normal 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;
|
||||
}
|
||||
}
|
11
Assets/Code/TypingSettings.cs.meta
Normal file
11
Assets/Code/TypingSettings.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5f1b1ace24b994a418057ba3c7e97810
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
8
Assets/Packages.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d2723bee01450514382d1e32aa01f968
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Packages/Mirror.meta
Normal file
8
Assets/Packages/Mirror.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5cf8eb36be0834b3da408c694a41cb88
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Packages/Mirror/Components.meta
Normal file
8
Assets/Packages/Mirror/Components.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9bee879fbc8ef4b1a9a9f7088bfbf726
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
14
Assets/Packages/Mirror/Components/Mirror.Components.asmdef
Normal file
14
Assets/Packages/Mirror/Components/Mirror.Components.asmdef
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "Mirror.Components",
|
||||
"references": [
|
||||
"Mirror"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 72872094b21c16e48b631b2224833d49
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
427
Assets/Packages/Mirror/Components/NetworkAnimator.cs
Normal file
427
Assets/Packages/Mirror/Components/NetworkAnimator.cs
Normal 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
|
||||
}
|
||||
}
|
11
Assets/Packages/Mirror/Components/NetworkAnimator.cs.meta
Normal file
11
Assets/Packages/Mirror/Components/NetworkAnimator.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7f6f3bf89aa97405989c802ba270f815
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
659
Assets/Packages/Mirror/Components/NetworkLobbyManager.cs
Normal file
659
Assets/Packages/Mirror/Components/NetworkLobbyManager.cs
Normal 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 615e6c6589cf9e54cad646b5a11e0529
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
155
Assets/Packages/Mirror/Components/NetworkLobbyPlayer.cs
Normal file
155
Assets/Packages/Mirror/Components/NetworkLobbyPlayer.cs
Normal 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
|
||||
}
|
||||
}
|
11
Assets/Packages/Mirror/Components/NetworkLobbyPlayer.cs.meta
Normal file
11
Assets/Packages/Mirror/Components/NetworkLobbyPlayer.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 79874ac94d5b1314788ecf0e86bd23fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
171
Assets/Packages/Mirror/Components/NetworkProximityChecker.cs
Normal file
171
Assets/Packages/Mirror/Components/NetworkProximityChecker.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1731d8de2d0c84333b08ebe1e79f4118
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
12
Assets/Packages/Mirror/Components/NetworkTransform.cs
Normal file
12
Assets/Packages/Mirror/Components/NetworkTransform.cs
Normal 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;
|
||||
}
|
||||
}
|
11
Assets/Packages/Mirror/Components/NetworkTransform.cs.meta
Normal file
11
Assets/Packages/Mirror/Components/NetworkTransform.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2f74aedd71d9a4f55b3ce499326d45fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
447
Assets/Packages/Mirror/Components/NetworkTransformBase.cs
Normal file
447
Assets/Packages/Mirror/Components/NetworkTransformBase.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2e77294d8ccbc4e7cb8ca2bd0d3e99ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
16
Assets/Packages/Mirror/Components/NetworkTransformChild.cs
Normal file
16
Assets/Packages/Mirror/Components/NetworkTransformChild.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 734b48bea0b204338958ee3d885e11f0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Packages/Mirror/Editor.meta
Normal file
8
Assets/Packages/Mirror/Editor.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2539267b6934a4026a505690a1e1eda2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
16
Assets/Packages/Mirror/Editor/Mirror.Editor.asmdef
Normal file
16
Assets/Packages/Mirror/Editor/Mirror.Editor.asmdef
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "Mirror.Editor",
|
||||
"references": [
|
||||
"Mirror"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
7
Assets/Packages/Mirror/Editor/Mirror.Editor.asmdef.meta
Normal file
7
Assets/Packages/Mirror/Editor/Mirror.Editor.asmdef.meta
Normal file
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1c7c33eb5480dd24c9e29a8250c1a775
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
5
Assets/Packages/Mirror/Editor/NetworkAnimatorEditor.cs
Normal file
5
Assets/Packages/Mirror/Editor/NetworkAnimatorEditor.cs
Normal 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
|
11
Assets/Packages/Mirror/Editor/NetworkAnimatorEditor.cs.meta
Normal file
11
Assets/Packages/Mirror/Editor/NetworkAnimatorEditor.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9589e903d4e98490fb1157762a307fd7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
197
Assets/Packages/Mirror/Editor/NetworkBehaviourInspector.cs
Normal file
197
Assets/Packages/Mirror/Editor/NetworkBehaviourInspector.cs
Normal 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
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f02853db46b6346e4866594a96c3b0e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
106
Assets/Packages/Mirror/Editor/NetworkIdentityEditor.cs
Normal file
106
Assets/Packages/Mirror/Editor/NetworkIdentityEditor.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Packages/Mirror/Editor/NetworkIdentityEditor.cs.meta
Normal file
11
Assets/Packages/Mirror/Editor/NetworkIdentityEditor.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1b6e3680cc14b4769bff378e5dbc3544
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
280
Assets/Packages/Mirror/Editor/NetworkInformationPreview.cs
Normal file
280
Assets/Packages/Mirror/Editor/NetworkInformationPreview.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 51a99294efe134232932c34606737356
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
112
Assets/Packages/Mirror/Editor/NetworkManagerEditor.cs
Normal file
112
Assets/Packages/Mirror/Editor/NetworkManagerEditor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Packages/Mirror/Editor/NetworkManagerEditor.cs.meta
Normal file
11
Assets/Packages/Mirror/Editor/NetworkManagerEditor.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 519712eb07f7a44039df57664811c2c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
90
Assets/Packages/Mirror/Editor/NetworkScenePostProcess.cs
Normal file
90
Assets/Packages/Mirror/Editor/NetworkScenePostProcess.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a3ec1c414d821444a9e77f18a2c130ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
24
Assets/Packages/Mirror/Editor/PreprocessorDefine.cs
Normal file
24
Assets/Packages/Mirror/Editor/PreprocessorDefine.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Packages/Mirror/Editor/PreprocessorDefine.cs.meta
Normal file
11
Assets/Packages/Mirror/Editor/PreprocessorDefine.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f1d66fe74ec6f42dd974cba37d25d453
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
56
Assets/Packages/Mirror/Editor/SceneDrawer.cs
Normal file
56
Assets/Packages/Mirror/Editor/SceneDrawer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Packages/Mirror/Editor/SceneDrawer.cs.meta
Normal file
11
Assets/Packages/Mirror/Editor/SceneDrawer.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b24704a46211b4ea294aba8f58715cea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Packages/Mirror/Editor/Weaver.meta
Normal file
8
Assets/Packages/Mirror/Editor/Weaver.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d9f8e6274119b4ce29e498cfb8aca8a4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
141
Assets/Packages/Mirror/Editor/Weaver/CompilationFinishedHook.cs
Normal file
141
Assets/Packages/Mirror/Editor/Weaver/CompilationFinishedHook.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de2aeb2e8068f421a9a1febe408f7051
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
151
Assets/Packages/Mirror/Editor/Weaver/Extensions.cs
Normal file
151
Assets/Packages/Mirror/Editor/Weaver/Extensions.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
11
Assets/Packages/Mirror/Editor/Weaver/Extensions.cs.meta
Normal file
11
Assets/Packages/Mirror/Editor/Weaver/Extensions.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 562a5cf0254cc45738e9aa549a7100b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
124
Assets/Packages/Mirror/Editor/Weaver/Helpers.cs
Normal file
124
Assets/Packages/Mirror/Editor/Weaver/Helpers.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Packages/Mirror/Editor/Weaver/Helpers.cs.meta
Normal file
11
Assets/Packages/Mirror/Editor/Weaver/Helpers.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6c4ed76daf48547c5abb7c58f8d20886
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
14
Assets/Packages/Mirror/Editor/Weaver/Mirror.Weaver.asmdef
Normal file
14
Assets/Packages/Mirror/Editor/Weaver/Mirror.Weaver.asmdef
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "Mirror.Weaver",
|
||||
"references": [],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1d0b9d21c3ff546a4aa32399dfd33474
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Packages/Mirror/Editor/Weaver/Processors.meta
Normal file
8
Assets/Packages/Mirror/Editor/Weaver/Processors.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e538d627280d2471b8c72fdea822ca49
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3544c9f00f6e5443ea3c30873c5a06ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 35c16722912b64af894e4f6668f2e54c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8118d606be3214e5d99943ec39530dd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d48f1ab125e9940a995603796bccc59e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f3263602f0a374ecd8d08588b1fc2f76
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
110
Assets/Packages/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
Normal file
110
Assets/Packages/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a3cb7051ff41947e59bba58bdd2b73fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in a new issue