Add StickGame Assets

This commit is contained in:
Dzejkobik007
2024-03-24 22:21:16 +01:00
parent 5a5812c0c7
commit 6c8b523d1f
6643 changed files with 596260 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,51 @@
using FishNet.Connection;
using FishNet.Managing;
using System;
using UnityEngine;
namespace FishNet.Authenticating
{
/// <summary>
/// When inherited from this can be used to create a custom authentication process before clients may communicate with the server.
/// </summary>
public abstract class Authenticator : MonoBehaviour
{
#region Public.
/// <summary>
/// True if this authenticator has been intiialzied.
/// </summary>
public bool Initialized { get; private set; }
#endregion
#region Protected.
/// <summary>
/// NetworkManager for this Authenticator.
/// </summary>
protected NetworkManager NetworkManager { get; private set; }
#endregion
/// <summary>
/// Called when authenticator has concluded a result for a connection. Boolean is true if authentication passed, false if failed.
/// Server listens for this event automatically.
/// </summary>
public abstract event Action<NetworkConnection, bool> OnAuthenticationResult;
/// <summary>
/// Initializes this script for use.
/// </summary>
/// <param name="networkManager"></param>
public virtual void InitializeOnce(NetworkManager networkManager)
{
NetworkManager = networkManager;
Initialized = true;
}
/// <summary>
/// Called on the server immediately after a client connects. Can be used to send data to the client for authentication.
/// </summary>
/// <param name="connection">Connection which is not yet authenticated.</param>
public virtual void OnRemoteConnection(NetworkConnection connection) { }
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,19 @@
using GameKit.Utilities;
namespace FishNet.Broadcast.Helping
{
internal static class BroadcastHelper
{
/// <summary>
/// Gets the key for a broadcast type.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="broadcastType"></param>
/// <returns></returns>
public static ushort GetKey<T>()
{
return typeof(T).FullName.GetStableHashU16();
}
}
}

View File

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

View File

@@ -0,0 +1,8 @@

namespace FishNet.Broadcast
{
/// <summary>
/// Include this interface on types intended to be used with Broadcast.
/// </summary>
public interface IBroadcast { }
}

View File

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

View File

@@ -0,0 +1 @@
{"StripReleaseBuilds":true}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9f1ece47c2d48194ea4827bf592a2279
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,296 @@
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Transporting;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Utility.Performance;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.Connection
{
/// <summary>
/// A byte buffer that automatically resizes.
/// </summary>
internal class ByteBuffer
{
/// <summary>
/// How many more bytes may fit into the buffer.
/// </summary>
internal int Remaining => (Size - Length);
/// <summary>
/// Buffer data.
/// </summary>
internal byte[] Data { get; private set; }
/// <summary>
/// How many bytes currently into Data. This will include the reserve.
/// </summary>
internal int Length { get; private set; }
/// <summary>
/// Size of the buffer. Data.Length may exceed this value as it uses a pooled array.
/// </summary>
internal int Size { get; private set; }
/// <summary>
/// True if data has been written.
/// </summary>
internal bool HasData { get; private set; }
/// <summary>
/// Bytes to reserve when resetting.
/// </summary>
private int _reserve;
internal ByteBuffer(int size, int reserve = 0)
{
Data = ByteArrayPool.Retrieve(size);
Size = size;
_reserve = reserve;
Reset();
}
public void Dispose()
{
if (Data != null)
ByteArrayPool.Store(Data);
Data = null;
}
/// <summary>
/// Resets instance without clearing Data.
/// </summary>
internal void Reset()
{
Length = _reserve;
HasData = false;
}
/// <summary>
/// Copies segments without error checking, including tick for the first time data is added.
/// </summary>
/// <param name="segment"></param>
internal void CopySegment(uint tick, ArraySegment<byte> segment)
{
/* If data has not been written to buffer yet
* then write tick to the start. */
if (!HasData)
{
int pos = 0;
WriterExtensions.WriteUInt32(Data, tick, ref pos);
}
Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count);
Length += segment.Count;
HasData = true;
}
/// <summary>
/// Copies segments without error checking.
/// </summary>
/// <param name="segment"></param>
internal void CopySegment(ArraySegment<byte> segment)
{
Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count);
Length += segment.Count;
HasData = true;
}
}
internal class PacketBundle
{
/// <summary>
/// True if data has been written.
/// </summary>
internal bool HasData => (_buffers[0].HasData || (!_isSendLastBundle && _sendLastBundle.HasData));
/// <summary>
/// All buffers written. Collection is not cleared when reset but rather the index in which to write is.
/// </summary>
private List<ByteBuffer> _buffers = new List<ByteBuffer>();
/// <summary>
/// Buffer which is being written to.
/// </summary>
private int _bufferIndex;
/// <summary>
/// Maximum size packet the transport can handle.
/// </summary>
private int _maximumTransportUnit;
/// <summary>
/// Number of buffers written to. Will return 0 if nothing has been written.
/// </summary>
public int WrittenBuffers => (!HasData) ? 0 : (_bufferIndex + 1);
/// <summary>
/// Number of bytes to reserve at the beginning of each buffer.
/// </summary>
private int _reserve;
/// <summary>
/// NetworkManager this is for.
/// </summary>
private NetworkManager _networkManager;
/// <summary>
/// Packet bundle to use for last enqueued data.
/// </summary>
private PacketBundle _sendLastBundle;
/// <summary>
/// True if being used as an sendLast bundle.
/// </summary>
private bool _isSendLastBundle;
internal PacketBundle(NetworkManager manager, int mtu, int reserve = 0, DataOrderType orderType = DataOrderType.Default)
{
_isSendLastBundle = (orderType == DataOrderType.Last);
//If this is not the send last packetbundle then make a new one.
if (!_isSendLastBundle)
_sendLastBundle = new PacketBundle(manager, mtu, reserve, DataOrderType.Last);
_networkManager = manager;
_maximumTransportUnit = mtu;
/* Allow bytes for the tick.
* Modify reserve after making sendLast bundle
* so that the wrong reserve is not passed into
* the sendLast bundle. */
reserve += TransportManager.TICK_BYTES;
_reserve = reserve;
//Add buffer requires the right reserve so call after setting.
AddBuffer();
Reset(false);
}
public void Dispose()
{
for (int i = 0; i < _buffers.Count; i++)
_buffers[i].Dispose();
_sendLastBundle?.Dispose();
}
/// <summary>
/// Adds a buffer using current settings.
/// </summary>
private ByteBuffer AddBuffer()
{
ByteBuffer ba = new ByteBuffer(_maximumTransportUnit, _reserve);
_buffers.Add(ba);
return ba;
}
/// <summary>
/// Resets using current settings.
/// </summary>
internal void Reset(bool resetSendLast)
{
_bufferIndex = 0;
for (int i = 0; i < _buffers.Count; i++)
_buffers[i].Reset();
if (resetSendLast)
_sendLastBundle.Reset(false);
}
/// <summary>
/// Writes a segment to this packet bundle using the current WriteIndex.
/// </summary>
/// <param name="forceNewBuffer">True to force data into a new buffer.</param>
internal void Write(ArraySegment<byte> segment, bool forceNewBuffer = false, DataOrderType orderType = DataOrderType.Default)
{
/* If not the send last bundle and to send data last
* then send using the send last bundle. */
if (!_isSendLastBundle && orderType == DataOrderType.Last)
{
_sendLastBundle.Write(segment, forceNewBuffer, orderType);
return;
}
//Nothing to be written.
if (segment.Count == 0)
return;
/* If the segment count is larger than the mtu then
* something went wrong. Nothing should call this method
* directly except the TransportManager, which will automatically
* split packets that exceed MTU into reliable ordered. */
if (segment.Count > _maximumTransportUnit)
{
_networkManager.LogError($"Segment is length of {segment.Count} while MTU is {_maximumTransportUnit}. Packet was not split properly and will not be sent.");
return;
}
ByteBuffer ba = _buffers[_bufferIndex];
/* Make a new buffer if...
* forcing a new buffer and data has already been written to the current.
* or---
* segment.Count is more than what is remaining in the buffer. */
bool useNewBuffer = (forceNewBuffer && ba.Length > _reserve) ||
(segment.Count > ba.Remaining);
if (useNewBuffer)
{
_bufferIndex++;
//If need to make a new buffer then do so.
if (_buffers.Count <= _bufferIndex)
{
ba = AddBuffer();
}
else
{
ba = _buffers[_bufferIndex];
ba.Reset();
}
}
uint tick = _networkManager.TimeManager.LocalTick;
ba.CopySegment(tick, segment);
}
/// <summary>
/// Returns the packetBundle for send last.
/// </summary>
/// <returns></returns>
internal PacketBundle GetSendLastBundle() => _sendLastBundle;
/// <summary>
/// Gets a buffer for the specified index. Returns true and outputs the buffer if it was successfully found.
/// </summary>
/// <param name="index">Index of the buffer to retrieve.</param>
/// <param name="bb">Buffer retrieved from the list. Null if the specified buffer was not found.</param>
internal bool GetBuffer(int index, out ByteBuffer bb)
{
bb = null;
if (index >= _buffers.Count || index < 0)
{
_networkManager.LogError($"Index of {index} is out of bounds. There are {_buffers.Count} available.");
return false;
}
if (index > _bufferIndex)
{
_networkManager.LogError($"Index of {index} exceeds the number of written buffers. There are {WrittenBuffers} written buffers.");
return false;
}
bb = _buffers[index];
return bb.HasData;
}
/// <summary>
/// Returns a PacketBundle for a channel. ResetPackets must be called afterwards.
/// </summary>
/// <param name="channel"></param>
/// <returns>True if PacketBundle is valid on the index and contains data.</returns>
internal static bool GetPacketBundle(int channel, List<PacketBundle> bundles, out PacketBundle mtuBuffer)
{
//Out of bounds.
if (channel >= bundles.Count)
{
mtuBuffer = null;
return false;
}
mtuBuffer = bundles[channel];
return mtuBuffer.HasData;
}
}
}

View File

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

View File

@@ -0,0 +1,125 @@
using System.Runtime.CompilerServices;
namespace FishNet.Managing.Timing
{
public struct EstimatedTick
{
/// <summary>
/// How to handle old ticks, specifically related to EstimatedTick.
/// </summary>
public enum OldTickOption : byte
{
/// <summary>
/// Completely ignore old ticks.
/// </summary>
Discard = 0,
/// <summary>
/// Set LastRemoteTick but do not update RemoteTick.
/// </summary>
SetLastRemoteTick = 1,
/// <summary>
/// Set LastRemoteTick and RemoteTick.
/// </summary>
SetRemoteTick = 2,
}
/// <summary>
/// Local tick when this was last updated.
/// </summary>
public uint LocalTick;
/// <summary>
/// Last remote tick this was updated with that was not out of order or a duplicate.
/// </summary>
public uint RemoteTick;
/// <summary>
/// Last remote tick received regardless if it was out of order or a duplicate.
/// </summary>
public uint LastRemoteTick;
/// <summary>
/// True if the value was updated this tick.
/// </summary>
public bool IsCurrent(TimeManager tm) => (!IsUnset && LocalTick == tm.LocalTick);
/// <summary>
/// True if value is unset.
/// </summary>
//Only need to check one value for unset as they all would be if not set.
public bool IsUnset => (LocalTick == 0);
/// <summary>
/// Number of ticks LocalTick is being current LocalTick.
/// </summary>
public uint LocalTickDifference(TimeManager tm)
{
long value = (tm.LocalTick - LocalTick);
//Shouldn't be possible to be less than 0.
if (value < 0)
return 0;
else if (value > uint.MaxValue)
value = uint.MaxValue;
return (uint)value;
}
/// <summary>
/// Updates values.
/// </summary>
/// <param name="nm">NetworkManager to use.</param>
/// <param name="remoteTick">Remote tick being updated.</param>
/// <param name="ignoreOldTicks">True to not update if remote tick is older or equal to the last updated value.</param>
/// <returns>True if was able to update values.</returns>
public bool Update(TimeManager tm, uint remoteTick, OldTickOption oldTickOption = OldTickOption.Discard)
{
//Always set LastRemoteTick even if out of order.
LastRemoteTick = remoteTick;
//If cannot update with old values return.
if (oldTickOption != OldTickOption.SetRemoteTick && remoteTick <= RemoteTick)
return false;
//nm is assumed set here.
LocalTick = tm.LocalTick;
RemoteTick = remoteTick;
return true;
}
/// <summary>
/// Current estimated value.
/// </summary>
/// <param name="nm">NetworkManager to use. When null default value will be returned.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint Value(TimeManager tm)
{
return Value(tm, out _);
}
/// <summary>
/// Current estimated value. Outputs if value is current.
/// </summary>
/// <param name="nm">NetworkManager to use. When null default value will be returned.</param>
/// <param name="isCurrent">True if the value was updated this local tick.</param>
public uint Value(TimeManager tm, out bool isCurrent)
{
isCurrent = IsCurrent(tm);
if (tm == null)
return 0;
if (IsUnset)
return 0;
uint diff = (tm.LocalTick - LocalTick);
return (diff + RemoteTick);
}
/// <summary>
/// Resets values to unset.
/// </summary>
public void Reset()
{
LocalTick = 0;
RemoteTick = 0;
LastRemoteTick = 0;
}
}
}

View File

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

View File

@@ -0,0 +1,111 @@
using FishNet.Broadcast;
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Transporting;
using FishNet.Object;
using FishNet.Transporting;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.Connection
{
public partial class NetworkConnection
{
#region Private.
/// <summary>
/// PacketBundles to send to this connection. An entry will be made for each channel.
/// </summary>
private List<PacketBundle> _toClientBundles = new List<PacketBundle>();
/// <summary>
/// True if this object has been dirtied.
/// </summary>
private bool _serverDirtied;
#endregion
/// <summary>
/// Initializes this script.
/// </summary>
private void InitializeBuffer()
{
for (byte i = 0; i < TransportManager.CHANNEL_COUNT; i++)
{
int mtu = NetworkManager.TransportManager.GetLowestMTU(i);
_toClientBundles.Add(new PacketBundle(NetworkManager, mtu));
}
}
/// <summary>
/// Sends a broadcast to this connection.
/// </summary>
/// <typeparam name="T">Type of broadcast to send.</typeparam>
/// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
/// <param name="requireAuthenticated">True if the client must be authenticated for this broadcast to send.</param>
/// <param name="channel">Channel to send on.</param>
public void Broadcast<T>(T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
{
if (!IsActive)
NetworkManager.LogError($"Connection is not valid, cannot send broadcast.");
else
NetworkManager.ServerManager.Broadcast<T>(this, message, requireAuthenticated, channel);
}
/// <summary>
/// Sends data from the server to a client.
/// </summary>
/// <param name="forceNewBuffer">True to force data into a new buffer.</param>
internal void SendToClient(byte channel, ArraySegment<byte> segment, bool forceNewBuffer = false, DataOrderType orderType = DataOrderType.Default)
{
//Cannot send data when disconnecting.
if (Disconnecting)
return;
if (!IsActive)
{
NetworkManager?.LogWarning($"Data cannot be sent to connection {ClientId} because it is not active.");
return;
}
//If channel is out of bounds then default to the first channel.
if (channel >= _toClientBundles.Count)
channel = 0;
_toClientBundles[channel].Write(segment, forceNewBuffer, orderType);
ServerDirty();
}
/// <summary>
/// Returns a PacketBundle for a channel. ResetPackets must be called afterwards.
/// </summary>
/// <param name="channel"></param>
/// <returns>True if PacketBundle is valid on the index and contains data.</returns>
internal bool GetPacketBundle(int channel, out PacketBundle packetBundle)
{
return PacketBundle.GetPacketBundle(channel, _toClientBundles, out packetBundle);
}
/// <summary>
/// Indicates the server has data to send to this connection.
/// </summary>
private void ServerDirty()
{
bool wasDirty = _serverDirtied;
_serverDirtied = true;
//If not yet dirty then tell transport manager this is dirty.
if (!wasDirty)
NetworkManager.TransportManager.ServerDirty(this);
}
/// <summary>
/// Resets that there is data to send.
/// </summary>
internal void ResetServerDirty()
{
_serverDirtied = false;
}
}
}

View File

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

View File

@@ -0,0 +1,85 @@
using FishNet.Object;
using GameKit.Utilities;
using System;
using System.Collections.Generic;
namespace FishNet.Connection
{
/// <summary>
/// A container for a connected client used to perform actions on and gather information for the declared client.
/// </summary>
public partial class NetworkConnection
{
public class LevelOfDetailData : IResettable
{
/// <summary>
/// Current level of detail for a NetworkObject.
/// </summary>
public byte CurrentLevelOfDetail;
/// <summary>
/// Previous level of detail for a NetworkObject.
/// </summary>
public byte PreviousLevelOfDetail;
internal void Update(byte lodLevel)
{
PreviousLevelOfDetail = CurrentLevelOfDetail;
CurrentLevelOfDetail = lodLevel;
}
public void ResetState()
{
CurrentLevelOfDetail = 0;
PreviousLevelOfDetail = 0;
}
public void InitializeState() { }
}
/// <summary>
/// Level of detail for each NetworkObject.
/// Since this is called frequently this field is intentionally not an accessor to increase performance.
/// </summary>
public Dictionary<NetworkObject, LevelOfDetailData> LevelOfDetails = new Dictionary<NetworkObject, LevelOfDetailData>(new NetworkObjectIdComparer());
/// <summary>
/// Number oftimes this connection may send a forced LOD update.
/// </summary>
internal int AllowedForcedLodUpdates;
/// <summary>
/// Last tick an LOD was sent.
/// On client and clientHost this is LocalTick.
/// On server only this is LastPacketTick for the connection.
/// </summary>
internal uint LastLevelOfDetailUpdate;
/// <summary>
/// Returns if the client has not sent an LOD update for expectedInterval.
/// </summary>
/// <returns></returns>
internal bool IsLateForLevelOfDetail(uint expectedInterval)
{
//Local client is immune since server and client share ticks.
if (IsLocalClient)
return false;
uint lastPacketTick = PacketTick.RemoteTick;
return ((lastPacketTick - LastLevelOfDetailUpdate) > expectedInterval);
}
/// <summary>
/// Number of level of detail update infractions for this connection.
/// </summary>
internal int LevelOfDetailInfractions;
private void ResetStates_Lod()
{
foreach (LevelOfDetailData data in LevelOfDetails.Values)
ResettableObjectCaches<LevelOfDetailData>.Store(data);
LevelOfDetails.Clear();
}
}
}

View File

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

View File

@@ -0,0 +1,98 @@
using FishNet.Component.Observing;
using FishNet.Managing;
using UnityEngine;
namespace FishNet.Connection
{
/// <summary>
/// A container for a connected client used to perform actions on and gather information for the declared client.
/// </summary>
public partial class NetworkConnection
{
#region Internal.
/// <summary>
/// Current GridEntry this connection is in.
/// </summary>
internal GridEntry HashGridEntry = HashGrid.EmptyGridEntry;
#endregion
#region Private.
/// <summary>
/// HashGrid for the NetworkManager on this connection.
/// </summary>
private HashGrid _hashGrid;
/// <summary>
/// Last unscaled time the HashGrid position was updated with this connections Objects.
/// </summary>
private float _nextHashGridUpdateTime;
/// <summary>
/// Current GridPosition this connection is in.
/// </summary>
private Vector2Int _hashGridPosition = HashGrid.UnsetGridPosition;
#endregion
/// <summary>
/// Called when the FirstObject changes for this connection.
/// </summary>
private void Observers_FirstObjectChanged()
{
UpdateHashGridPositions(true);
}
/// <summary>
/// Initializes this for use.
/// </summary>
private void Observers_Initialize(NetworkManager nm)
{
nm.TryGetInstance<HashGrid>(out _hashGrid);
}
/// <summary>
/// Updates the HashGridPosition value for FirstObject.
/// </summary>
internal void UpdateHashGridPositions(bool force)
{
if (_hashGrid == null)
return;
float unscaledTime = Time.unscaledTime;
//Not enough time has passed to update.
if (!force && unscaledTime < _nextHashGridUpdateTime)
return;
const float updateInterval = 1f;
_nextHashGridUpdateTime = unscaledTime + updateInterval;
if (FirstObject == null)
{
HashGridEntry = HashGrid.EmptyGridEntry;
_hashGridPosition = HashGrid.UnsetGridPosition;
}
else
{
Vector2Int newPosition = _hashGrid.GetHashGridPosition(FirstObject);
if (newPosition != _hashGridPosition)
{
_hashGridPosition = newPosition;
HashGridEntry = _hashGrid.GetGridEntry(newPosition);
}
}
}
/// <summary>
/// Resets values.
/// </summary>
private void Observers_Reset()
{
_hashGrid = null;
_hashGridPosition = HashGrid.UnsetGridPosition;
_nextHashGridUpdateTime = 0f;
}
}
}

View File

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

View File

@@ -0,0 +1,105 @@
using FishNet.Managing;
using FishNet.Managing.Timing;
using System;
using UnityEngine;
namespace FishNet.Connection
{
/// <summary>
/// A container for a connected client used to perform actions on and gather information for the declared client.
/// </summary>
public partial class NetworkConnection
{
#pragma warning disable CS0414
#region Private.
/// <summary>
/// Last tick this connection sent a ping.
/// </summary>
private uint _lastPingTick;
///// <summary>
///// Number of times client has excessively sent a ping.
///// </summary>
//private float _excessivePingCount;
/// <summary>
/// Ticks expected between each ping.
/// </summary>
private uint _requiredPingTicks;
#endregion
#region Const.
/// <summary>
/// Number of times a ping may occur excessively before server will punish connection.
/// </summary>
private const byte EXCESSIVE_PING_LIMIT = 10;
#endregion
#pragma warning restore CS0414
/// <summary>
/// Initializes for ping.
/// </summary>
private void InitializePing()
{
//Give the client some room for error.
float requiredInterval = (NetworkManager.TimeManager.PingInterval * 0.85f);
//Round down so required ticks is lower.
_requiredPingTicks = NetworkManager.TimeManager.TimeToTicks(requiredInterval, TickRounding.RoundDown);
}
/// <summary>
/// Resets PingPong values.
/// </summary>
private void ResetPingPong()
{
//_excessivePingCount = 0;
_lastPingTick = 0;
}
/// <summary>
/// Called when a ping is received from this connection. Returns if can respond to ping.
/// </summary>
/// <returns>True to respond to ping, false to kick connection.</returns>
internal bool CanPingPong()
{
/* Only check ping conditions in build. Editors are prone to pausing which can
* improperly kick clients. */
TimeManager tm = (NetworkManager == null) ? InstanceFinder.TimeManager : NetworkManager.TimeManager;
/* Server FPS is running low, timing isn't reliable enough to kick clients.
* Respond with clients ping and remove infractions just in case the
* client received some from other server instabilities. */
if (tm.LowFrameRate)
{
//_excessivePingCount = 0f;
return false;
}
uint currentTick = tm.Tick;
uint difference = (currentTick - _lastPingTick);
_lastPingTick = currentTick;
//Ping sent too quickly.
if (difference < _requiredPingTicks)
{
//_excessivePingCount += 1f;
////Ping limit hit.
//if (_excessivePingCount >= EXCESSIVE_PING_LIMIT)
//{
// NetworkManager.LogWarning($"Kicked connectionId {ClientId} for excessive pings.");
// Disconnect(true);
//}
//Return to not send pong back.
return false;
}
//Ping isnt too fast.
else
{
//_excessivePingCount = UnityEngine.Mathf.Max(0f, _excessivePingCount - 0.5f);
return true;
}
}
}
}

View File

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

View File

@@ -0,0 +1,85 @@
using FishNet.Managing;
using FishNet.Managing.Predicting;
using FishNet.Managing.Timing;
using FishNet.Serializing;
using FishNet.Transporting;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Connection
{
/// <summary>
/// A container for a connected client used to perform actions on and gather information for the declared client.
/// </summary>
public partial class NetworkConnection
{
/// <summary>
/// Average number of replicates in queue for the past x received replicates.
/// </summary>
private MovingAverage _replicateQueueAverage;
/// <summary>
/// Last tick replicateQueueAverage was updated.
/// </summary>
private uint _lastAverageQueueAddTick;
internal void Prediction_Initialize(NetworkManager manager, bool asServer)
{
if (asServer)
{
int movingAverageCount = (int)Mathf.Max((float)manager.TimeManager.TickRate * 0.25f, 3f);
_replicateQueueAverage = new MovingAverage(movingAverageCount);
}
}
/// <summary>
/// Adds to the average number of queued replicates.
/// </summary>
internal void AddAverageQueueCount(ushort value, uint tick)
{
/* If have not added anything to the averages for several ticks
* then reset average. */
if ((tick - _lastAverageQueueAddTick) > _replicateQueueAverage.SampleSize)
_replicateQueueAverage.Reset();
_lastAverageQueueAddTick = tick;
_replicateQueueAverage.ComputeAverage((float)value);
}
/// <summary>
/// Returns the highest queue count after resetting it.
/// </summary>
/// <returns></returns>
internal ushort GetAndResetAverageQueueCount()
{
if (_replicateQueueAverage == null)
return 0;
int avg = (int)(_replicateQueueAverage.Average);
if (avg < 0)
avg = 0;
return (ushort)avg;
}
/// <summary>
/// Local tick when the connection last replicated.
/// </summary>
public uint LocalReplicateTick { get; internal set; }
/// <summary>
/// Resets NetworkConnection.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Prediction_Reset()
{
GetAndResetAverageQueueCount();
}
}
}

View File

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

View File

@@ -0,0 +1,69 @@
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Server;
using FishNet.Serializing;
using System;
namespace FishNet.Connection
{
/// <summary>
/// A container for a connected client used to perform actions on and gather information for the declared client.
/// </summary>
public partial class NetworkConnection
{
#region Public.
/// <summary>
/// Returns true if this connection is a clientHost.
/// </summary>
public bool IsHost => (NetworkManager == null) ? false : (NetworkManager.IsServer && (this == NetworkManager.ClientManager.Connection));
/// <summary>
/// Returns if this connection is for the local client.
/// </summary>
public bool IsLocalClient => (NetworkManager == null) ? false : (NetworkManager.ClientManager.Connection == this);
#endregion
/// <summary>
/// Returns the address of this connection.
/// </summary>
/// <returns></returns>
public string GetAddress()
{
if (!IsValid)
return string.Empty;
if (NetworkManager == null)
return string.Empty;
return NetworkManager.TransportManager.Transport.GetConnectionAddress(ClientId);
}
/// <summary>
/// Kicks a connection immediately while invoking OnClientKick.
/// </summary>
/// <param name="kickReason">Reason client is being kicked.</param>
/// <param name="loggingType">How to print logging as.</param>
/// <param name="log">Optional message to be debug logged.</param>
public void Kick(KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
{
NetworkManager.ServerManager.Kick(this, kickReason, loggingType, log);
}
/// <summary>
/// Kicks a connection immediately while invoking OnClientKick.
/// </summary>
/// <param name="reader">Reader to clear before kicking.</param>
/// <param name="kickReason">Reason client is being kicked.</param>
/// <param name="loggingType">How to print logging as.</param>
/// <param name="log">Optional message to be debug logged.</param>
public void Kick(Reader reader, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
{
NetworkManager.ServerManager.Kick(this, reader, kickReason, loggingType, log);
}
}
}

View File

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

View File

@@ -0,0 +1,433 @@
using FishNet.Component.Observing;
using FishNet.Documenting;
using FishNet.Managing;
using FishNet.Managing.Timing;
using FishNet.Object;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.SceneManagement;
using static FishNet.Managing.Timing.EstimatedTick;
namespace FishNet.Connection
{
/// <summary>
/// A container for a connected client used to perform actions on and gather information for the declared client.
/// </summary>
public partial class NetworkConnection : IEquatable<NetworkConnection>
{
#region Public.
/// <summary>
/// Called after this connection has loaded start scenes. Boolean will be true if asServer. Available to this connection and server.
/// </summary>
public event Action<NetworkConnection, bool> OnLoadedStartScenes;
/// <summary>
/// Called after connection gains ownership of an object, and after the object has been added to Objects. Available to this connection and server.
/// </summary>
public event Action<NetworkObject> OnObjectAdded;
/// <summary>
/// Called after connection loses ownership of an object, and after the object has been removed from Objects. Available to this connection and server.
/// </summary>
public event Action<NetworkObject> OnObjectRemoved;
/// <summary>
/// NetworkManager managing this class.
/// </summary>
public NetworkManager NetworkManager { get; private set; }
/// <summary>
/// True if connection has loaded start scenes. Available to this connection and server.
/// </summary>
public bool LoadedStartScenes() => (_loadedStartScenesAsServer || _loadedStartScenesAsClient);
/// <summary>
///
/// </summary>
public bool LoadedStartScenes(bool asServer)
{
if (asServer)
return _loadedStartScenesAsServer;
else
return _loadedStartScenesAsClient;
}
/// <summary>
/// True if loaded start scenes as server.
/// </summary>
private bool _loadedStartScenesAsServer;
/// <summary>
/// True if loaded start scenes as client.
/// </summary>
private bool _loadedStartScenesAsClient;
/// <summary>
/// ObjectIds to use for predicted spawning.
/// </summary>
internal Queue<int> PredictedObjectIds = new Queue<int>();
/// <summary>
/// TransportIndex this connection is on.
/// For security reasons this value will be unset on clients if this is not their connection.
/// </summary>
public int TransportIndex { get; internal set; } = -1;
/// <summary>
/// True if this connection is authenticated. Only available to server.
/// </summary>
public bool Authenticated { get; private set; }
/// <summary>
/// True if this connection IsValid and not Disconnecting.
/// </summary>
public bool IsActive => (ClientId >= 0 && !Disconnecting);
/// <summary>
/// True if this connection is valid. An invalid connection indicates no client is set for this reference.
/// </summary>
public bool IsValid => (ClientId >= 0);
/// <summary>
/// Unique Id for this connection.
/// </summary>
public int ClientId = -1;
/// <summary>
/// Objects owned by this connection. Available to this connection and server.
/// </summary>
public HashSet<NetworkObject> Objects = new HashSet<NetworkObject>();
/// <summary>
/// The first object within Objects.
/// </summary>
public NetworkObject FirstObject { get; private set; }
/// <summary>
/// Sets a custom FirstObject. This connection must be owner of the specified object.
/// </summary>
/// <param name="nob"></param>
public void SetFirstObject(NetworkObject nob)
{
//Invalid object.
if (!Objects.Contains(nob))
{
string errMessage = $"FirstObject for {ClientId} cannot be set to {nob.name} as it's not within Objects for this connection.";
if (NetworkManager == null)
NetworkManager.StaticLogError(errMessage);
else
NetworkManager.LogError(errMessage);
return;
}
FirstObject = nob;
}
/// <summary>
/// Scenes this connection is in. Available to this connection and server.
/// </summary>
public HashSet<Scene> Scenes { get; private set; } = new HashSet<Scene>();
/// <summary>
/// True if this connection is being disconnected. Only available to server.
/// </summary>
public bool Disconnecting { get; private set; }
/// <summary>
/// Tick when Disconnecting was set.
/// </summary>
internal uint DisconnectingTick { get; private set; }
/// <summary>
/// Custom data associated with this connection which may be modified by the user.
/// The value of this field are not synchronized over the network.
/// </summary>
public object CustomData = null;
/// <summary>
/// LocalTick of the server when this connection was established. This value is not set for clients.
/// </summary>
internal uint ServerConnectionTick;
/// <summary>
/// Tick of the last packet received from this connection which was not out of order.
/// This value is only available on the server.
/// </summary>
public EstimatedTick PacketTick;
[Obsolete("Use LocalTick instead.")] //Remove on 2023/06/01
public uint Tick => LocalTick.Value(NetworkManager.TimeManager);
/// <summary>
/// Approximate local tick as it is on this connection.
/// This also contains the last set value for local and remote.
/// </summary>
public EstimatedTick LocalTick;
#endregion
#region Const.
/// <summary>
/// Value used when ClientId has not been set.
/// </summary>
public const int UNSET_CLIENTID_VALUE = -1;
#endregion
#region Comparers.
public override bool Equals(object obj)
{
if (obj is NetworkConnection nc)
return (nc.ClientId == this.ClientId);
else
return false;
}
public bool Equals(NetworkConnection nc)
{
if (nc is null)
return false;
//If either is -1 Id.
if (this.ClientId == NetworkConnection.UNSET_CLIENTID_VALUE || nc.ClientId == NetworkConnection.UNSET_CLIENTID_VALUE)
return false;
//Same object.
if (System.Object.ReferenceEquals(this, nc))
return true;
return (this.ClientId == nc.ClientId);
}
public override int GetHashCode()
{
return ClientId;
}
public static bool operator ==(NetworkConnection a, NetworkConnection b)
{
if (a is null && b is null)
return true;
if (a is null && !(b is null))
return false;
return (b == null) ? a.Equals(b) : b.Equals(a);
}
public static bool operator !=(NetworkConnection a, NetworkConnection b)
{
return !(a == b);
}
#endregion
[APIExclude]
public NetworkConnection() { }
[APIExclude]
public NetworkConnection(NetworkManager manager, int clientId, int transportIndex, bool asServer)
{
Initialize(manager, clientId, transportIndex, asServer);
}
internal void Dispose()
{
Deinitialize();
}
/// <summary>
/// Outputs data about this connection as a string.
/// </summary>
/// <returns></returns>
public override string ToString()
{
int clientId = ClientId;
string ip = (NetworkManager != null) ? NetworkManager.TransportManager.Transport.GetConnectionAddress(clientId) : "Unset";
return $"Id [{ClientId}] Address [{ip}]";
}
/// <summary>
/// Initializes this for use.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Initialize(NetworkManager nm, int clientId, int transportIndex, bool asServer)
{
NetworkManager = nm;
if (asServer)
ServerConnectionTick = nm.TimeManager.LocalTick;
TransportIndex = transportIndex;
ClientId = clientId;
/* Set PacketTick to current values so
* that timeouts and other things around
* first packet do not occur due to an unset value. */
PacketTick.Update(nm.TimeManager, 0, OldTickOption.SetLastRemoteTick);
Observers_Initialize(nm);
Prediction_Initialize(nm, asServer);
//Only the server uses the ping and buffer.
if (asServer)
{
InitializeBuffer();
InitializePing();
}
}
/// <summary>
/// Deinitializes this NetworkConnection. This is called prior to resetting.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Deinitialize()
{
MatchCondition.RemoveFromMatchesWithoutRebuild(this, NetworkManager);
foreach (PacketBundle p in _toClientBundles)
p.Dispose();
_toClientBundles.Clear();
ServerConnectionTick = 0;
PacketTick.Reset();
TransportIndex = -1;
ClientId = -1;
ClearObjects();
Authenticated = false;
NetworkManager = null;
_loadedStartScenesAsClient = false;
_loadedStartScenesAsServer = false;
SetDisconnecting(false);
Scenes.Clear();
PredictedObjectIds.Clear();
ResetPingPong();
ResetStates_Lod();
AllowedForcedLodUpdates = 0;
LastLevelOfDetailUpdate = 0;
LevelOfDetailInfractions = 0;
Observers_Reset();
Prediction_Reset();
}
/// <summary>
/// Sets Disconnecting boolean for this connection.
/// </summary>
internal void SetDisconnecting(bool value)
{
Disconnecting = value;
if (Disconnecting)
DisconnectingTick = NetworkManager.TimeManager.LocalTick;
}
/// <summary>
/// Disconnects this connection.
/// </summary>
/// <param name="immediately">True to disconnect immediately. False to send any pending data first.</param>
public void Disconnect(bool immediately)
{
if (!IsValid)
{
//NetworkManager is likely null if invalid.
NetworkManager.StaticLogWarning($"Disconnect called on an invalid connection.");
return;
}
if (Disconnecting)
{
NetworkManager.LogWarning($"ClientId {ClientId} is already disconnecting.");
return;
}
SetDisconnecting(true);
//If immediately then force disconnect through transport.
if (immediately)
NetworkManager.TransportManager.Transport.StopConnection(ClientId, true);
//Otherwise mark dirty so server will push out any pending information, and then disconnect.
else
ServerDirty();
}
/// <summary>
/// Returns if just loaded start scenes and sets them as loaded if not.
/// </summary>
/// <returns></returns>
internal bool SetLoadedStartScenes(bool asServer)
{
bool loadedToCheck = (asServer) ? _loadedStartScenesAsServer : _loadedStartScenesAsClient;
//Result becomes true if not yet loaded start scenes.
bool result = !loadedToCheck;
if (asServer)
_loadedStartScenesAsServer = true;
else
_loadedStartScenesAsClient = true;
OnLoadedStartScenes?.Invoke(this, asServer);
return result;
}
/// <summary>
/// Sets connection as authenticated.
/// </summary>
internal void ConnectionAuthenticated()
{
Authenticated = true;
}
/// <summary>
/// Adds to Objects owned by this connection.
/// </summary>
/// <param name="nob"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void AddObject(NetworkObject nob)
{
if (!IsValid)
return;
Objects.Add(nob);
//If adding the first object then set new FirstObject.
if (Objects.Count == 1)
SetFirstObject();
OnObjectAdded?.Invoke(nob);
}
/// <summary>
/// Removes from Objects owned by this connection.
/// </summary>
/// <param name="nob"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RemoveObject(NetworkObject nob)
{
if (!IsValid)
{
ClearObjects();
return;
}
Objects.Remove(nob);
//If removing the first object then set a new one.
if (nob == FirstObject)
SetFirstObject();
OnObjectRemoved?.Invoke(nob);
}
/// <summary>
/// Clears all Objects.
/// </summary>
private void ClearObjects()
{
Objects.Clear();
FirstObject = null;
}
/// <summary>
/// Sets FirstObject using the first element in Objects.
/// </summary>
private void SetFirstObject()
{
if (Objects.Count == 0)
{
FirstObject = null;
}
else
{
foreach (NetworkObject nob in Objects)
{
FirstObject = nob;
Observers_FirstObjectChanged();
break;
}
}
}
/// <summary>
/// Adds a scene to this connections Scenes.
/// </summary>
/// <param name="scene"></param>
/// <returns></returns>
internal bool AddToScene(Scene scene)
{
return Scenes.Add(scene);
}
/// <summary>
/// Removes a scene to this connections Scenes.
/// </summary>
/// <param name="scene"></param>
/// <returns></returns>
internal bool RemoveFromScene(Scene scene)
{
return Scenes.Remove(scene);
}
}
}

View File

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

View File

@@ -0,0 +1,7 @@
using System.Runtime.CompilerServices;
namespace FishNet.Managing.Timing
{
}

View File

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

View File

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

View File

@@ -0,0 +1,8 @@

using System;
namespace FishNet.Documenting
{
public class APIExcludeAttribute : Attribute { }
}

View File

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

View File

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

View File

@@ -0,0 +1,116 @@
#if UNITY_EDITOR
using FishNet.Configuring;
using System.IO;
using UnityEngine;
using System.Xml.Serialization;
using FishNet.Editing.PrefabCollectionGenerator;
using UnityEditor.Compilation;
using UnityEditor.Build.Reporting;
using UnityEditor;
using UnityEditor.Build;
namespace FishNet.Configuring
{
public class CodeStripping
{
/// <summary>
/// True if making a release build for client.
/// </summary>
public static bool ReleasingForClient => (Configuration.Configurations.CodeStripping.IsBuilding && !Configuration.Configurations.CodeStripping.IsHeadless && !Configuration.Configurations.CodeStripping.IsDevelopment);
/// <summary>
/// True if making a release build for server.
/// </summary>
public static bool ReleasingForServer => (Configuration.Configurations.CodeStripping.IsBuilding && Configuration.Configurations.CodeStripping.IsHeadless && !Configuration.Configurations.CodeStripping.IsDevelopment);
/// <summary>
/// Returns if to remove server logic.
/// </summary>
/// <returns></returns>
public static bool RemoveServerLogic
{
get
{
/* This is to protect non pro users from enabling this
* without the extra logic code. */
#pragma warning disable CS0162 // Unreachable code detected
return false;
#pragma warning restore CS0162 // Unreachable code detected
}
}
/// <summary>
/// True if building and stripping is enabled.
/// </summary>
public static bool StripBuild
{
get
{
/* This is to protect non pro users from enabling this
* without the extra logic code. */
#pragma warning disable CS0162 // Unreachable code detected
return false;
#pragma warning restore CS0162 // Unreachable code detected
}
}
/// <summary>
/// Technique to strip methods.
/// </summary>
public static StrippingTypes StrippingType => (StrippingTypes)Configuration.Configurations.CodeStripping.StrippingType;
private static object _compilationContext;
public int callbackOrder => 0;
public void OnPreprocessBuild(BuildReport report)
{
Generator.IgnorePostProcess = true;
Generator.GenerateFull();
CompilationPipeline.compilationStarted += CompilationPipelineOnCompilationStarted;
CompilationPipeline.compilationFinished += CompilationPipelineOnCompilationFinished;
}
/* Solution for builds ending with errors and not triggering OnPostprocessBuild.
* Link: https://gamedev.stackexchange.com/questions/181611/custom-build-failure-callback
*/
private void CompilationPipelineOnCompilationStarted(object compilationContext)
{
_compilationContext = compilationContext;
}
private void CompilationPipelineOnCompilationFinished(object compilationContext)
{
if (compilationContext != _compilationContext)
return;
_compilationContext = null;
CompilationPipeline.compilationStarted -= CompilationPipelineOnCompilationStarted;
CompilationPipeline.compilationFinished -= CompilationPipelineOnCompilationFinished;
BuildingEnded();
}
private void BuildingEnded()
{
Generator.IgnorePostProcess = false;
}
public void OnPostprocessBuild(BuildReport report)
{
BuildingEnded();
}
}
}
#endif

View File

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

View File

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

View File

@@ -0,0 +1,147 @@
#if UNITY_EDITOR
using FishNet.Editing.PrefabCollectionGenerator;
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using UnityEditor;
namespace FishNet.Configuring
{
public enum StrippingTypes : int
{
Redirect = 0,
Empty_Experimental = 1,
}
public enum SearchScopeType : int
{
EntireProject = 0,
SpecificFolders = 1,
}
public class PrefabGeneratorConfigurations
{
public bool Enabled = true;
public bool LogToConsole = true;
public bool FullRebuild = false;
public bool SaveChanges = true;
public string DefaultPrefabObjectsPath = Path.Combine("Assets", "DefaultPrefabObjects.asset");
internal string DefaultPrefabObjectsPath_Platform => Generator.GetPlatformPath(DefaultPrefabObjectsPath);
public int SearchScope = (int)SearchScopeType.EntireProject;
public List<string> ExcludedFolders = new List<string>();
public List<string> IncludedFolders = new List<string>();
}
public class CodeStrippingConfigurations
{
public bool IsBuilding = false;
public bool IsDevelopment = false;
public bool IsHeadless = false;
public bool StripReleaseBuilds = false;
public int StrippingType = (int)StrippingTypes.Redirect;
}
public class ConfigurationData
{
//Non serialized doesn't really do anything, its just for me.
[System.NonSerialized]
public bool Loaded;
public PrefabGeneratorConfigurations PrefabGenerator = new PrefabGeneratorConfigurations();
public CodeStrippingConfigurations CodeStripping = new CodeStrippingConfigurations();
}
public static class ConfigurationDataExtension
{
/// <summary>
/// Returns if a differs from b.
/// </summary>
public static bool HasChanged(this ConfigurationData a, ConfigurationData b)
{
return (a.CodeStripping.StripReleaseBuilds != b.CodeStripping.StripReleaseBuilds);
}
/// <summary>
/// Copies all values from source to target.
/// </summary>
public static void CopyTo(this ConfigurationData source, ConfigurationData target)
{
target.CodeStripping.StripReleaseBuilds = source.CodeStripping.StripReleaseBuilds;
}
/// <summary>
/// Writes a configuration data.
/// </summary>
public static void Write(this ConfigurationData cd, bool refreshAssetDatabase)
{
/* Why is this a thing you ask? Because Unity makes it VERY difficult to read values from
* memory during builds since on some Unity versions the building application is on a different
* processor. In result instead of using memory to read configurationdata the values
* must be written to disk then load the disk values as needed.
*
* Fortunatelly the file is extremely small and this does not occur often at all. The disk read
* will occur once per script save, and once per assembly when building. */
try
{
string path = Configuration.GetAssetsPath(Configuration.CONFIG_FILE_NAME);
XmlSerializer serializer = new XmlSerializer(typeof(ConfigurationData));
TextWriter writer = new StreamWriter(path);
serializer.Serialize(writer, cd);
writer.Close();
#if UNITY_EDITOR
if (refreshAssetDatabase)
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
#endif
}
catch (Exception ex)
{
throw new Exception($"An error occurred while writing ConfigurationData. Message: {ex.Message}");
}
}
/// <summary>
/// Writes a configuration data.
/// </summary>
public static void Write(this ConfigurationData cd, string path, bool refreshAssetDatabase)
{
/* Why is this a thing you ask? Because Unity makes it VERY difficult to read values from
* memory during builds since on some Unity versions the building application is on a different
* processor. In result instead of using memory to read configurationdata the values
* must be written to disk then load the disk values as needed.
*
* Fortunatelly the file is extremely small and this does not occur often at all. The disk read
* will occur once per script save, and once per assembly when building. */
try
{
XmlSerializer serializer = new XmlSerializer(typeof(ConfigurationData));
TextWriter writer = new StreamWriter(path);
serializer.Serialize(writer, cd);
writer.Close();
#if UNITY_EDITOR
if (refreshAssetDatabase)
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
#endif
}
catch (Exception ex)
{
throw new Exception($"An error occurred while writing ConfigurationData. Message: {ex.Message}");
}
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,231 @@
#if UNITY_EDITOR
using FishNet.Editing.PrefabCollectionGenerator;
using FishNet.Object;
using FishNet.Utility.Extension;
using FishNet.Utility.Performance;
using GameKit.Utilities;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace FishNet.Editing
{
public class ConfigurationEditor : EditorWindow
{
[MenuItem("Fish-Networking/Configuration", false, 0)]
public static void ShowConfiguration()
{
SettingsService.OpenProjectSettings("Project/Fish-Networking/Configuration");
}
}
public class DeveloperMenu : MonoBehaviour
{
#region const.
private const string RELEASE_DEFINE = "FISHNET_RELEASE_MODE";
private const string QOL_ATTRIBUTES_DEFINE = "DISABLE_QOL_ATTRIBUTES";
private const string DEVELOPER_ONLY_WARNING = "If you are not a developer or were not instructed to do this by a developer things are likely to break. You have been warned.";
#endregion
#region Release mode.
#if !FISHNET_RELEASE_MODE
[MenuItem("Fish-Networking/Switch to Release Mode", false, -1100)]
private static void SwitchToReleaseMode()
{
bool result = RemoveOrAddDefine(RELEASE_DEFINE, false);
if (result)
Debug.LogWarning($"Release mode has been enabled. Please note that experimental features may not function in release mode.");
}
#else
[MenuItem("Fish-Networking/Switch to Development Mode", false, -1100)]
private static void SwitchToReleaseMode()
{
bool result = RemoveOrAddDefine(RELEASE_DEFINE, true);
if (result)
Debug.LogWarning($"Development mode has been enabled.");
}
#endif
#endregion
#region QOL Attributes
#if DISABLE_QOL_ATTRIBUTES
[MenuItem("Fish-Networking/Experimental/Quality of Life Attributes/Enable", false, -999)]
private static void EnableQOLAttributes()
{
bool result = RemoveOrAddDefine(QOL_ATTRIBUTES_DEFINE, true);
if (result)
Debug.LogWarning($"Quality of Life Attributes have been enabled.");
}
#else
[MenuItem("Fish-Networking/Experimental/Quality of Life Attributes/Disable", false, -998)]
private static void DisableQOLAttributes()
{
bool result = RemoveOrAddDefine(QOL_ATTRIBUTES_DEFINE, false);
if (result)
Debug.LogWarning($"Quality of Life Attributes have been disabled. {DEVELOPER_ONLY_WARNING}");
}
#endif
#endregion
private static bool RemoveOrAddDefine(string define, bool removeDefine)
{
string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
HashSet<string> definesHs = new HashSet<string>();
string[] currentArr = currentDefines.Split(';');
//Add any define which doesn't contain MIRROR.
foreach (string item in currentArr)
definesHs.Add(item);
int startingCount = definesHs.Count;
if (removeDefine)
definesHs.Remove(define);
else
definesHs.Add(define);
bool modified = (definesHs.Count != startingCount);
if (modified)
{
string changedDefines = string.Join(";", definesHs);
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, changedDefines);
}
return modified;
}
}
public class RebuildSceneIdMenu : MonoBehaviour
{
/// <summary>
/// Rebuilds sceneIds for open scenes.
/// </summary>
[MenuItem("Fish-Networking/Rebuild SceneIds", false, 20)]
public static void RebuildSceneIds()
{
#if PARRELSYNC
if (ParrelSync.ClonesManager.IsClone() && ParrelSync.Preferences.AssetModPref.Value)
{
Debug.Log("Cannot perform this operation on a ParrelSync clone");
return;
}
#endif
int generatedCount = 0;
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene s = SceneManager.GetSceneAt(i);
List<NetworkObject> nobs = CollectionCaches<NetworkObject>.RetrieveList();
Scenes.GetSceneNetworkObjects(s, false, ref nobs);
int nobCount = nobs.Count;
for (int z = 0; z < nobCount; z++)
{
NetworkObject nob = nobs[z];
nob.TryCreateSceneID();
EditorUtility.SetDirty(nob);
}
generatedCount += nobCount;
CollectionCaches<NetworkObject>.Store(nobs);
}
Debug.Log($"Generated sceneIds for {generatedCount} objects over {SceneManager.sceneCount} scenes. Please save your open scenes.");
}
}
public class RefreshDefaultPrefabsMenu : MonoBehaviour
{
/// <summary>
/// Rebuilds the DefaultPrefabsCollection file.
/// </summary>
[MenuItem("Fish-Networking/Refresh Default Prefabs", false, 22)]
public static void RebuildDefaultPrefabs()
{
#if PARRELSYNC
if (ParrelSync.ClonesManager.IsClone() && ParrelSync.Preferences.AssetModPref.Value)
{
Debug.Log("Cannot perform this operation on a ParrelSync clone");
return;
}
#endif
Debug.Log("Refreshing default prefabs.");
Generator.GenerateFull(null, true);
}
}
public class RemoveDuplicateNetworkObjectsMenu : MonoBehaviour
{
/// <summary>
/// Iterates all network object prefabs in the project and open scenes, removing NetworkObject components which exist multiple times on a single object.
/// </summary>
[MenuItem("Fish-Networking/Remove Duplicate NetworkObjects", false, 21)]
public static void RemoveDuplicateNetworkObjects()
{
#if PARRELSYNC
if (ParrelSync.ClonesManager.IsClone() && ParrelSync.Preferences.AssetModPref.Value)
{
Debug.Log("Cannot perform this operation on a ParrelSync clone");
return;
}
#endif
List<NetworkObject> foundNobs = new List<NetworkObject>();
foreach (string path in Generator.GetPrefabFiles("Assets", new HashSet<string>(), true))
{
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(path);
if (nob != null)
foundNobs.Add(nob);
}
//Now add scene objects.
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene s = SceneManager.GetSceneAt(i);
List<NetworkObject> nobs = CollectionCaches<NetworkObject>.RetrieveList();
Scenes.GetSceneNetworkObjects(s, false, ref nobs);
int nobsCount = nobs.Count;
for (int z = 0; z < nobsCount; z++)
{
NetworkObject nob = nobs[z];
nob.TryCreateSceneID();
EditorUtility.SetDirty(nob);
}
for (int z = 0; z < nobsCount; z++)
foundNobs.Add(nobs[i]);
CollectionCaches<NetworkObject>.Store(nobs);
}
//Remove duplicates.
int removed = 0;
foreach (NetworkObject nob in foundNobs)
{
int count = nob.RemoveDuplicateNetworkObjects();
if (count > 0)
removed += count;
}
Debug.Log($"Removed {removed} duplicate NetworkObjects. Please save your open scenes and project.");
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,101 @@
#if UNITY_EDITOR
using System.IO;
using System.Xml.Serialization;
using UnityEngine;
using UnityEditor.Compilation;
using UnityEditor.Build.Reporting;
using UnityEditor;
using UnityEditor.Build;
namespace FishNet.Configuring
{
public class Configuration
{
/// <summary>
///
/// </summary>
private static ConfigurationData _configurations;
/// <summary>
/// ConfigurationData to use.
/// </summary>
public static ConfigurationData Configurations
{
get
{
if (_configurations == null)
_configurations = LoadConfigurationData();
if (_configurations == null)
throw new System.Exception("Fish-Networking Configurations could not be loaded. Certain features such as code-stripping may not function.");
return _configurations;
}
private set
{
_configurations = value;
}
}
/// <summary>
/// File name for configuration disk data.
/// </summary>
public const string CONFIG_FILE_NAME = "FishNet.Config.XML";
/// <summary>
/// Returns the path for the configuration file.
/// </summary>
/// <returns></returns>
internal static string GetAssetsPath(string additional = "")
{
string a = Path.Combine(System.IO.Directory.GetCurrentDirectory(), "Assets");
if (additional != "")
a = Path.Combine(a, additional);
return a;
}
/// <summary>
/// Returns FishNetworking ConfigurationData.
/// </summary>
/// <returns></returns>
internal static ConfigurationData LoadConfigurationData()
{
//return new ConfigurationData();
if (_configurations == null || !_configurations.Loaded)
{
string configPath = GetAssetsPath(CONFIG_FILE_NAME);
//string configPath = string.Empty;
//File is on disk.
if (File.Exists(configPath))
{
FileStream fs = null;
try
{
XmlSerializer serializer = new XmlSerializer(typeof(ConfigurationData));
fs = new FileStream(configPath, FileMode.Open, FileAccess.Read, FileShare.Read);
_configurations = (ConfigurationData)serializer.Deserialize(fs);
}
finally
{
fs?.Close();
}
_configurations.Loaded = true;
}
else
{
//If null then make a new instance.
if (_configurations == null)
_configurations = new ConfigurationData();
//Don't unset loaded, if its true then it should have proper info.
//_configurationData.Loaded = false;
}
}
return _configurations;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,172 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing
{
/// <summary>
/// Contributed by YarnCat! Thank you!
/// </summary>
public class FishNetGettingStartedEditor : EditorWindow
{
private Texture2D _fishnetLogo, _reviewButtonBg, _reviewButtonBgHover;
private GUIStyle _labelStyle, _reviewButtonStyle;
private const string SHOWED_GETTING_STARTED = "ShowedFishNetGettingStarted";
[MenuItem("Fish-Networking/Getting Started")]
public static void GettingStartedMenu()
{
FishNetGettingStartedEditor window = (FishNetGettingStartedEditor)EditorWindow.GetWindow(typeof(FishNetGettingStartedEditor));
window.position = new Rect(0, 0, 320, 355);
Rect mainPos;
#if UNITY_2020_1_OR_NEWER
mainPos = EditorGUIUtility.GetMainWindowPosition();
#else
mainPos = new Rect(Vector2.zero, Vector2.zero);
#endif
var pos = window.position;
float w = (mainPos.width - pos.width) * 0.5f;
float h = (mainPos.height - pos.height) * 0.5f;
pos.x = mainPos.x + w;
pos.y = mainPos.y + h;
window.position = pos;
window._fishnetLogo = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/FishNet/Runtime/Editor/Textures/UI/Logo_With_Text.png", typeof(Texture));
window._labelStyle = new GUIStyle("label");
window._labelStyle.fontSize = 24;
window._labelStyle.wordWrap = true;
//window.labelStyle.alignment = TextAnchor.MiddleCenter;
window._labelStyle.normal.textColor = new Color32(74, 195, 255, 255);
window._reviewButtonBg = MakeBackgroundTexture(1, 1, new Color32(52, 111, 255, 255));
window._reviewButtonBgHover = MakeBackgroundTexture(1, 1, new Color32(99, 153, 255, 255));
window._reviewButtonStyle = new GUIStyle("button");
window._reviewButtonStyle.fontSize = 18;
window._reviewButtonStyle.fontStyle = FontStyle.Bold;
window._reviewButtonStyle.normal.background = window._reviewButtonBg;
window._reviewButtonStyle.active.background = window._reviewButtonBgHover;
window._reviewButtonStyle.focused.background = window._reviewButtonBgHover;
window._reviewButtonStyle.onFocused.background = window._reviewButtonBgHover;
window._reviewButtonStyle.hover.background = window._reviewButtonBgHover;
window._reviewButtonStyle.onHover.background = window._reviewButtonBgHover;
window._reviewButtonStyle.alignment = TextAnchor.MiddleCenter;
window._reviewButtonStyle.normal.textColor = new Color(1, 1, 1, 1);
}
private static bool _subscribed;
[InitializeOnLoadMethod]
private static void Initialize()
{
SubscribeToUpdate();
}
private static void SubscribeToUpdate()
{
if (Application.isBatchMode)
return;
if (!_subscribed && !EditorApplication.isPlayingOrWillChangePlaymode)
{
_subscribed = true;
EditorApplication.update += ShowGettingStarted;
}
}
private static void ShowGettingStarted()
{
EditorApplication.update -= ShowGettingStarted;
bool shown = EditorPrefs.GetBool(SHOWED_GETTING_STARTED, false);
if (!shown)
{
EditorPrefs.SetBool(SHOWED_GETTING_STARTED, true);
ReviewReminderEditor.ResetDateTimeReminded();
GettingStartedMenu();
}
//If was already shown then check review reminder instead.
else
{
ReviewReminderEditor.CheckRemindToReview();
}
}
void OnGUI()
{
GUILayout.Box(_fishnetLogo, GUILayout.Width(this.position.width), GUILayout.Height(128));
GUILayout.Space(20);
GUILayout.Label("Have you considered leaving us a review?", _labelStyle, GUILayout.Width(280));
GUILayout.Space(10);
if (GUILayout.Button("Leave us a review!", _reviewButtonStyle))
{
Application.OpenURL("https://assetstore.unity.com/packages/tools/network/fish-net-networking-evolved-207815");
}
GUILayout.Space(20);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Documentation", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://fish-networking.gitbook.io/docs/");
}
if (GUILayout.Button("Discord", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://discord.gg/Ta9HgDh4Hj");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("FishNet Pro", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://fish-networking.gitbook.io/docs/master/pro");
}
if (GUILayout.Button("Github", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://github.com/FirstGearGames/FishNet");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Pro Downloads", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://www.firstgeargames.com/");
}
if (GUILayout.Button("Examples", GUILayout.Width(this.position.width * 0.485f)))
{
Application.OpenURL("https://fish-networking.gitbook.io/docs/manual/tutorials/example-projects");
}
EditorGUILayout.EndHorizontal();
//GUILayout.Space(20);
//_showOnStartupSelected = EditorGUILayout.Popup("Show on Startup", _showOnStartupSelected, showOnStartupOptions);
}
//private string[] showOnStartupOptions = new string[] { "Always", "On new version", "Never", };
//private int _showOnStartupSelected = 1;
private static Texture2D MakeBackgroundTexture(int width, int height, Color color)
{
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
pixels[i] = color;
Texture2D backgroundTexture = new Texture2D(width, height);
backgroundTexture.SetPixels(pixels);
backgroundTexture.Apply();
return backgroundTexture;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,171 @@
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing
{
/// <summary>
/// Contributed by YarnCat! Thank you!
/// </summary>
public class ReviewReminderEditor : EditorWindow
{
private Texture2D _fishnetLogo, _reviewButtonBg, _reviewButtonBgHover;
private GUIStyle _labelStyle, _reviewButtonStyle;
private const string DATETIME_REMINDED = "ReviewDateTimeReminded";
private const string CHECK_REMIND_COUNT = "CheckRemindCount";
private const string IS_ENABLED = "ReminderEnabled";
private static ReviewReminderEditor _window;
internal static void CheckRemindToReview()
{
bool reminderEnabled = EditorPrefs.GetBool(IS_ENABLED, true);
if (!reminderEnabled)
return;
/* Require at least two opens and 10 days
* to be passed before reminding. */
int checkRemindCount = (EditorPrefs.GetInt(CHECK_REMIND_COUNT, 0) + 1);
EditorPrefs.SetInt(CHECK_REMIND_COUNT, checkRemindCount);
//Not enough checks.
if (checkRemindCount < 2)
return;
string dtStr = EditorPrefs.GetString(DATETIME_REMINDED, string.Empty);
//Somehow got cleared. Reset.
if (string.IsNullOrWhiteSpace(dtStr))
{
ResetDateTimeReminded();
return;
}
long binary;
//Failed to parse.
if (!long.TryParse(dtStr, out binary))
{
ResetDateTimeReminded();
return;
}
//Not enough time passed.
DateTime dt = DateTime.FromBinary(binary);
if ((DateTime.Now - dt).TotalDays < 10)
return;
//If here then the reminder can be shown.
EditorPrefs.SetInt(CHECK_REMIND_COUNT, 0);
ShowReminder();
}
internal static void ResetDateTimeReminded()
{
EditorPrefs.SetString(DATETIME_REMINDED, DateTime.Now.ToBinary().ToString());
}
private static void ShowReminder()
{
InitializeWindow();
}
static void InitializeWindow()
{
if (_window != null)
return;
_window = (ReviewReminderEditor)EditorWindow.GetWindow(typeof(ReviewReminderEditor));
_window.position = new Rect(0f, 0f, 320f, 300f);
Rect mainPos;
#if UNITY_2020_1_OR_NEWER
mainPos = EditorGUIUtility.GetMainWindowPosition();
#else
mainPos = new Rect(Vector2.zero, Vector2.zero);
#endif
var pos = _window.position;
float w = (mainPos.width - pos.width) * 0.5f;
float h = (mainPos.height - pos.height) * 0.5f;
pos.x = mainPos.x + w;
pos.y = mainPos.y + h;
_window.position = pos;
}
static void StyleWindow()
{
InitializeWindow();
_window._fishnetLogo = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/FishNet/Runtime/Editor/Textures/UI/Logo_With_Text.png", typeof(Texture));
_window._labelStyle = new GUIStyle("label");
_window._labelStyle.fontSize = 24;
_window._labelStyle.wordWrap = true;
//window.labelStyle.alignment = TextAnchor.MiddleCenter;
_window._labelStyle.normal.textColor = new Color32(74, 195, 255, 255);
_window._reviewButtonBg = MakeBackgroundTexture(1, 1, new Color32(52, 111, 255, 255));
_window._reviewButtonBgHover = MakeBackgroundTexture(1, 1, new Color32(99, 153, 255, 255));
_window._reviewButtonStyle = new GUIStyle("button");
_window._reviewButtonStyle.fontSize = 18;
_window._reviewButtonStyle.fontStyle = FontStyle.Bold;
_window._reviewButtonStyle.normal.background = _window._reviewButtonBg;
_window._reviewButtonStyle.active.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.focused.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.onFocused.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.hover.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.onHover.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.alignment = TextAnchor.MiddleCenter;
_window._reviewButtonStyle.normal.textColor = new Color(1, 1, 1, 1);
}
void OnGUI()
{
float thisWidth = this.position.width;
StyleWindow();
GUILayout.Box(_fishnetLogo, GUILayout.Width(this.position.width), GUILayout.Height(160f));
EditorGUILayout.BeginHorizontal();
GUILayout.Space(8f);
GUILayout.Label("Have you considered leaving us a review?", _labelStyle, GUILayout.Width(thisWidth * 0.95f));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Don't Ask Again", GUILayout.Width(this.position.width)))
{
this.Close();
EditorPrefs.SetBool(IS_ENABLED, false);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Ask Later", GUILayout.Width(this.position.width)))
{
this.Close();
//Application.OpenURL("https://discord.gg/Ta9HgDh4Hj");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Leave A Review", GUILayout.Width(this.position.width)))
{
this.Close();
EditorPrefs.SetBool(IS_ENABLED, false);
Application.OpenURL("https://assetstore.unity.com/packages/tools/network/fish-net-networking-evolved-207815");
}
EditorGUILayout.EndHorizontal();
//GUILayout.Space(20);
//_showOnStartupSelected = EditorGUILayout.Popup("Show on Startup", _showOnStartupSelected, showOnStartupOptions);
}
private static Texture2D MakeBackgroundTexture(int width, int height, Color color)
{
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
pixels[i] = color;
Texture2D backgroundTexture = new Texture2D(width, height);
backgroundTexture.SetPixels(pixels);
backgroundTexture.Apply();
return backgroundTexture;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,86 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using FishNet.Configuring;
using UnitySettingsProviderAttribute = UnityEditor.SettingsProviderAttribute;
using UnitySettingsProvider = UnityEditor.SettingsProvider;
using System.Collections.Generic;
namespace FishNet.Configuring.Editing
{
internal static class SettingsProvider
{
private static Vector2 _scrollView;
[UnitySettingsProvider]
private static UnitySettingsProvider Create()
{
return new UnitySettingsProvider("Project/Fish-Networking/Configuration", SettingsScope.Project)
{
label = "Configuration",
guiHandler = OnGUI,
keywords = new string[]
{
"Fish",
"Networking",
"Configuration",
},
};
}
private static void OnGUI(string searchContext)
{
ConfigurationData configuration = Configuration.LoadConfigurationData();
if (configuration == null)
{
EditorGUILayout.HelpBox("Unable to load configuration data.", MessageType.Error);
return;
}
EditorGUI.BeginChangeCheck();
GUIStyle scrollViewStyle = new GUIStyle()
{
padding = new RectOffset(10, 10, 10, 10),
};
_scrollView = GUILayout.BeginScrollView(_scrollView, scrollViewStyle);
EditorGUILayout.BeginHorizontal();
GUIStyle toggleStyle = new GUIStyle(EditorStyles.toggle)
{
richText = true,
};
configuration.CodeStripping.StripReleaseBuilds = GUILayout.Toggle(configuration.CodeStripping.StripReleaseBuilds, $"{ObjectNames.NicifyVariableName(nameof(configuration.CodeStripping.StripReleaseBuilds))} <color=yellow>(Pro Only)</color>", toggleStyle);
EditorGUILayout.EndHorizontal();
if (configuration.CodeStripping.StripReleaseBuilds)
{
EditorGUI.indentLevel++;
//Stripping Method.
List<string> enumStrings = new List<string>();
foreach (string item in System.Enum.GetNames(typeof(StrippingTypes)))
enumStrings.Add(item);
configuration.CodeStripping.StrippingType = EditorGUILayout.Popup($"{ObjectNames.NicifyVariableName(nameof(configuration.CodeStripping.StrippingType))}", (int)configuration.CodeStripping.StrippingType, enumStrings.ToArray());
EditorGUILayout.HelpBox("Development builds will not have code stripped. Additionally, if you plan to run as host disable code stripping.", MessageType.Warning);
EditorGUI.indentLevel--;
}
GUILayout.EndScrollView();
if (EditorGUI.EndChangeCheck()) Configuration.Configurations.Write(true);
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,13 @@
using FishNet.Documenting;
namespace FishNet.Editing
{
[APIExclude]
public static class EditingConstants
{
public const string PRO_ASSETS_LOCKED_TEXT = "Fields marked with * are only active with Fish-Networking Pro.";
public const string PRO_ASSETS_UNLOCKED_TEXT = "Thank you for supporting Fish-Networking! Pro asset features are unlocked.";
}
}

View File

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

View File

@@ -0,0 +1,126 @@
//#if UNITY_EDITOR
//using FishNet.Managing.Object;
//using FishNet.Object;
//using System.Collections.Generic;
//using System.IO;
//using UnityEditor;
//using UnityEngine;
//namespace FishNet.Editing
//{
// [InitializeOnLoad]
// internal static class DefaultPrefabsFinder
// {
// /// <summary>
// /// True if initialized.
// /// </summary>
// [System.NonSerialized]
// private static bool _initialized;
// /// <summary>
// /// Found default prefabs.
// /// </summary>
// private static DefaultPrefabObjects _defaultPrefabs;
// static DefaultPrefabsFinder()
// {
// EditorApplication.update += InitializeOnce;
// }
// /// <summary>
// /// Finds and sets the default prefabs reference.
// /// </summary>
// internal static DefaultPrefabObjects GetDefaultPrefabsFile(out bool justPopulated)
// {
// if (_defaultPrefabs == null)
// {
// List<UnityEngine.Object> results = Finding.GetScriptableObjects<DefaultPrefabObjects>(true, true);
// if (results.Count > 0)
// _defaultPrefabs = (DefaultPrefabObjects)results[0];
// }
// justPopulated = false;
// //If not found then try to create file.
// if (_defaultPrefabs == null)
// {
// if (DefaultPrefabObjects.CanAutomate)
// {
// DefaultPrefabObjects dpo = ScriptableObject.CreateInstance<DefaultPrefabObjects>();
// //Get save directory.
// string savePath = Finding.GetFishNetRuntimePath(true);
// AssetDatabase.CreateAsset(dpo, Path.Combine(savePath, $"{nameof(DefaultPrefabObjects)}.asset"));
// }
// else
// {
// Debug.LogError($"Cannot create DefaultPrefabs because auto create is blocked.");
// }
// }
// //If still null.
// if (_defaultPrefabs == null)
// Debug.LogWarning($"DefaultPrefabObjects not found. Prefabs list will not be automatically populated.");
// else
// justPopulated = PopulateDefaultPrefabs();
// return _defaultPrefabs;
// }
// /// <summary>
// /// Initializes the default prefab.
// /// </summary>
// private static void InitializeOnce()
// {
// if (_initialized)
// return;
// _initialized = true;
// Finding.GetFishNetRuntimePath(false);
// GetDefaultPrefabsFile(out _);
// if (_defaultPrefabs != null)
// {
// //Populate any missing.
// if (_defaultPrefabs.GetObjectCount() == 0)
// PopulateDefaultPrefabs();
// }
// }
// /// <summary>
// /// Finds all NetworkObjects in project and adds them to defaultPrefabs.
// /// </summary>
// /// <returns>True if was populated from assets.</returns>
// internal static bool PopulateDefaultPrefabs(bool log = true, bool clear = false)
// {
// if (_defaultPrefabs == null)
// return false;
// if (!DefaultPrefabObjects.CanAutomate)
// return false;
// if (clear)
// _defaultPrefabs.Clear();
// if (_defaultPrefabs.GetObjectCount() > 0)
// return false;
// List<GameObject> gameObjects = Finding.GetGameObjects(true, true, false);
// foreach (GameObject go in gameObjects)
// {
// if (go.TryGetComponent(out NetworkObject nob))
// _defaultPrefabs.AddObject(nob);
// }
// _defaultPrefabs.Sort();
// int entriesAdded = _defaultPrefabs.GetObjectCount();
// //Only print if some were added.
// if (log && entriesAdded > 0)
// Debug.Log($"Default prefabs was populated with {entriesAdded} prefabs.");
// EditorUtility.SetDirty(_defaultPrefabs);
// return true;
// }
// }
//}
//#endif

View File

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

View File

@@ -0,0 +1,216 @@
#if UNITY_EDITOR
using FishNet.Utility.Constant;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace FishNet.Editing
{
public static class Finding
{
#region Private.
/// <summary>
/// Path where the FishNet.Runtime assembly is.
/// </summary>
[System.NonSerialized]
private static string _fishNetRuntimePath = string.Empty;
/// <summary>
/// Path where the FishNet.Generated assembly is.
/// </summary>
private static string _fishNetGeneratedPath = string.Empty;
#endregion
/// <summary>
/// Sets FishNet assembly paths.
/// </summary>
/// <param name="error"></param>
private static void SetPaths(bool error)
{
if (_fishNetGeneratedPath != string.Empty && _fishNetRuntimePath != string.Empty)
return;
string[] guids = AssetDatabase.FindAssets("t:asmdef", new string[] { "Assets" });
string[] objectPaths = new string[guids.Length];
for (int i = 0; i < guids.Length; i++)
objectPaths[i] = AssetDatabase.GUIDToAssetPath(guids[i]);
string runtimeName = (UtilityConstants.RUNTIME_ASSEMBLY_NAME + ".asmdef").ToLower();
string generatedName = (UtilityConstants.GENERATED_ASSEMBLY_NAME + ".asmdef").ToLower();
/* Find all network managers which use Single prefab linking
* as well all network object prefabs. */
foreach (string item in objectPaths)
{
//Found directory to create object in.
if (item.ToLower().Contains(runtimeName))
_fishNetRuntimePath = System.IO.Path.GetDirectoryName(item);
else if (item.ToLower().Contains(generatedName))
_fishNetGeneratedPath = System.IO.Path.GetDirectoryName(item);
if (_fishNetGeneratedPath != string.Empty && _fishNetRuntimePath != string.Empty)
return;
}
}
/// <summary>
/// Gets path for where the FishNet.Runtime assembly is.
/// </summary>
/// <returns></returns>
public static string GetFishNetRuntimePath(bool error)
{
SetPaths(error);
return _fishNetRuntimePath;
}
/// <summary>
/// Gets path for where the FishNet.Generated assembly is.
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
public static string GetFishNetGeneratedPath(bool error)
{
SetPaths(error);
return _fishNetGeneratedPath;
}
/// <summary>
/// Gets all GameObjects in Assets and optionally scenes.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static List<GameObject> GetGameObjects(bool userAssemblies, bool fishNetAssembly, bool includeScenes, string[] ignoredPaths = null)
{
List<GameObject> results = new List<GameObject>();
string[] guids;
string[] objectPaths;
guids = AssetDatabase.FindAssets("t:GameObject", null);
objectPaths = new string[guids.Length];
for (int i = 0; i < guids.Length; i++)
objectPaths[i] = AssetDatabase.GUIDToAssetPath(guids[i]);
foreach (string item in objectPaths)
{
bool inFishNet = item.Contains(_fishNetRuntimePath);
if (inFishNet && !fishNetAssembly)
continue;
if (!inFishNet && !userAssemblies)
continue;
if (ignoredPaths != null)
{
bool ignore = false;
foreach (string path in ignoredPaths)
{
if (item.Contains(path))
{
ignore = true;
break;
}
}
if (ignore)
continue;
}
GameObject go = (GameObject)AssetDatabase.LoadAssetAtPath(item, typeof(GameObject));
results.Add(go);
}
if (includeScenes)
results.AddRange(GetSceneGameObjects());
return results;
}
/// <summary>
/// Gets all GameObjects in all open scenes.
/// </summary>
/// <returns></returns>
private static List<GameObject> GetSceneGameObjects()
{
List<GameObject> results = new List<GameObject>();
for (int i = 0; i < SceneManager.sceneCount; i++)
results.AddRange(GetSceneGameObjects(SceneManager.GetSceneAt(i)));
return results;
}
/// <summary>
/// Gets all GameObjects in a scene.
/// </summary>
private static List<GameObject> GetSceneGameObjects(Scene s)
{
List<GameObject> results = new List<GameObject>();
List<Transform> buffer = new List<Transform>();
//Iterate all root objects for the scene.
GameObject[] gos = s.GetRootGameObjects();
for (int i = 0; i < gos.Length; i++)
{
/* Get GameObjects within children of each
* root object then add them to the cache. */
gos[i].GetComponentsInChildren<Transform>(true, buffer);
foreach (Transform t in buffer)
results.Add(t.gameObject);
}
return results;
}
/// <summary>
/// Gets created ScriptableObjects of T.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static List<UnityEngine.Object> GetScriptableObjects<T>(bool fishNetAssembly, bool breakOnFirst = false)
{
System.Type tType = typeof(T);
List<UnityEngine.Object> results = new List<UnityEngine.Object>();
string[] guids = AssetDatabase.FindAssets("t:ScriptableObject", new string[] { "Assets" });
string[] objectPaths = new string[guids.Length];
for (int i = 0; i < guids.Length; i++)
objectPaths[i] = AssetDatabase.GUIDToAssetPath(guids[i]);
/* This might be faster than using directory comparers.
* Don't really care since this occurs only at edit. */
List<string> fishNetPaths = new List<string>();
fishNetPaths.Add(_fishNetGeneratedPath.Replace(@"/", @"\"));
fishNetPaths.Add(_fishNetGeneratedPath.Replace(@"\", @"/"));
fishNetPaths.Add(_fishNetRuntimePath.Replace(@"/", @"\"));
fishNetPaths.Add(_fishNetRuntimePath.Replace(@"\", @"/"));
/* Find all network managers which use Single prefab linking
* as well all network object prefabs. */
foreach (string item in objectPaths)
{
//This will skip hidden unity types.
if (!item.EndsWith(".asset"))
continue;
if (fishNetAssembly)
{
bool found = false;
foreach (string path in fishNetPaths)
{
if (item.Contains(path))
{
found = true;
break;
}
}
if (!found)
continue;
}
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath(item, tType);
if (obj != null && tType != null && obj.GetType() == tType)
{
results.Add(obj);
if (breakOnFirst)
return results;
}
}
return results;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,51 @@
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing
{
[InitializeOnLoad]
public class PlayModeTracker
{
static PlayModeTracker()
{
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
~PlayModeTracker()
{
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}
/// <summary>
/// DateTime when the editor last exited playmode.
/// </summary>
private static DateTime _quitTime = DateTime.MaxValue;
/// <summary>
/// True if the editor has exited playmode within past.
/// </summary>
/// <param name="past"></param>
/// <returns></returns>
internal static bool QuitRecently(float past)
{
past *= 1000;
return ((DateTime.Now - _quitTime).TotalMilliseconds < past);
}
private static void OnPlayModeStateChanged(PlayModeStateChange stateChange)
{
switch (stateChange)
{
case (PlayModeStateChange.ExitingPlayMode):
_quitTime = DateTime.Now;
break;
}
}
}
}
#endif

View File

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

View File

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

View File

@@ -0,0 +1,651 @@
#if UNITY_EDITOR
using FishNet.Configuring;
using FishNet.Managing.Object;
using FishNet.Object;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityDebug = UnityEngine.Debug;
namespace FishNet.Editing.PrefabCollectionGenerator
{
internal sealed class Generator : AssetPostprocessor
{
public Generator()
{
if (!_subscribed)
{
_subscribed = true;
EditorApplication.update += OnEditorUpdate;
}
}
~Generator()
{
if (_subscribed)
{
_subscribed = false;
EditorApplication.update -= OnEditorUpdate;
}
}
#region Types.
private struct SpecifiedFolder
{
public string Path;
public bool Recursive;
public SpecifiedFolder(string path, bool recursive)
{
Path = path;
Recursive = recursive;
}
}
#endregion
#region Public.
/// <summary>
/// True to ignore post process changes.
/// </summary>
public static bool IgnorePostProcess = false;
#endregion
#region Private.
/// <summary>
/// Last asset to import when there was only one imported asset and no other changes.
/// </summary>
private static string _lastSingleImportedAsset = string.Empty;
/// <summary>
/// Cached DefaultPrefabObjects reference.
/// </summary>
private static DefaultPrefabObjects _cachedDefaultPrefabs;
/// <summary>
/// True to refresh prefabs next update.
/// </summary>
private static bool _retryRefreshDefaultPrefabs;
/// <summary>
/// True if already subscribed to EditorApplication.Update.
/// </summary>
private static bool _subscribed;
/// <summary>
/// True if ran once since editor started.
/// </summary>
[System.NonSerialized]
private static bool _ranOnce;
/// <summary>
/// Last paths of updated nobs during a changed update.
/// </summary>
[System.NonSerialized]
private static List<string> _lastUpdatedNamePaths = new List<string>();
/// <summary>
/// Last frame changed was updated.
/// </summary>
[System.NonSerialized]
private static int _lastUpdatedFrame = -1;
/// <summary>
/// Length of assets strings during the last update.
/// </summary>
[System.NonSerialized]
private static int _lastUpdatedLengths = -1;
#endregion
public static string[] GetPrefabFiles(string startingPath, HashSet<string> excludedPaths, bool recursive)
{
//Opportunity to exit early if there are no excluded paths.
if (excludedPaths.Count == 0)
{
string[] strResults = Directory.GetFiles(startingPath, "*.prefab", SearchOption.AllDirectories);
return strResults;
}
//starting path is excluded.
if (excludedPaths.Contains(startingPath))
return new string[0];
//Folders remaining to be iterated.
List<string> enumeratedCollection = new List<string>() { startingPath };
//Only check other directories if recursive.
if (recursive)
{
//Find all folders which aren't excluded.
for (int i = 0; i < enumeratedCollection.Count; i++)
{
string[] allFolders = Directory.GetDirectories(enumeratedCollection[i], "*", SearchOption.TopDirectoryOnly);
for (int z = 0; z < allFolders.Length; z++)
{
string current = allFolders[z];
//Not excluded.
if (!excludedPaths.Contains(current))
enumeratedCollection.Add(current);
}
}
}
//Valid prefab files.
List<string> results = new List<string>();
//Build files from folders.
int count = enumeratedCollection.Count;
for (int i = 0; i < count; i++)
{
string[] r = Directory.GetFiles(enumeratedCollection[i], "*.prefab", SearchOption.TopDirectoryOnly);
results.AddRange(r);
}
return results.ToArray();
}
/// <summary>
/// Removes paths which may overlap each other, such as sub directories.
/// </summary>
private static void RemoveOverlappingFolders(List<SpecifiedFolder> folders)
{
for (int z = 0; z < folders.Count; z++)
{
for (int i = 0; i < folders.Count; i++)
{
//Do not check against self.
if (i == z)
continue;
//Duplicate.
if (folders[z].Path.Equals(folders[i].Path, System.StringComparison.OrdinalIgnoreCase))
{
UnityDebug.LogError($"The same path is specified multiple times in the DefaultPrefabGenerator settings. Remove the duplicate to clear this error.");
folders.RemoveAt(i);
break;
}
/* We are checking if i can be within
* z. This is only possible if i is longer
* than z. */
if (folders[i].Path.Length < folders[z].Path.Length)
continue;
/* Do not need to check if not recursive.
* Only recursive needs to be checked because
* a shorter recursive path could contain
* a longer path. */
if (!folders[z].Recursive)
continue;
//Compare paths.
string zPath = GetPathWithSeparator(folders[z].Path);
string iPath = zPath.Substring(0, zPath.Length);
//If paths match.
if (iPath.Equals(zPath, System.StringComparison.OrdinalIgnoreCase))
{
UnityDebug.LogError($"Path {folders[i].Path} is included within recursive path {folders[z].Path}. Remove path {folders[i].Path} to clear this error.");
folders.RemoveAt(i);
break;
}
}
}
string GetPathWithSeparator(string txt)
{
return txt.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
}
}
/// <summary>
/// Returns a message to attach to logs if objects were dirtied.
/// </summary>
private static string GetDirtiedMessage(PrefabGeneratorConfigurations settings, bool dirtied)
{
if (!settings.SaveChanges && dirtied)
return " One or more NetworkObjects were dirtied. Please save your project.";
else
return string.Empty;
}
/// <summary>
/// Updates prefabs by using only changed information.
/// </summary>
public static void GenerateChanged(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, PrefabGeneratorConfigurations settings = null)
{
#if PARRELSYNC
if (ParrelSync.ClonesManager.IsClone() && ParrelSync.Preferences.AssetModPref.Value)
{
UnityDebug.Log("Skipping prefab generation in ParrelSync clone");
return;
}
#endif
if (settings == null)
settings = Configuration.Configurations.PrefabGenerator;
if (!settings.Enabled)
return;
bool log = settings.LogToConsole;
Stopwatch sw = (log) ? Stopwatch.StartNew() : null;
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects(settings);
//No need to error if nto found, GetDefaultPrefabObjects will.
if (prefabCollection == null)
return;
int assetsLength = (importedAssets.Length + deletedAssets.Length + movedAssets.Length + movedFromAssetPaths.Length);
List<string> changedNobPaths = new List<string>();
System.Type goType = typeof(UnityEngine.GameObject);
IterateAssetCollection(importedAssets);
IterateAssetCollection(movedAssets);
//True if dirtied by changes.
bool dirtied;
//First remove null entries.
int startCount = prefabCollection.GetObjectCount();
prefabCollection.RemoveNull();
dirtied = (prefabCollection.GetObjectCount() != startCount);
//First index which new objects will be added to.
int firstAddIndex = (prefabCollection.GetObjectCount() - 1);
//Iterates strings adding prefabs to collection.
void IterateAssetCollection(string[] c)
{
foreach (string item in c)
{
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(item);
if (assetType != goType)
continue;
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(item);
if (nob != null)
{
changedNobPaths.Add(item);
prefabCollection.AddObject(nob, true);
dirtied = true;
}
}
}
//To prevent out of range.
if (firstAddIndex < 0 || firstAddIndex >= prefabCollection.GetObjectCount())
firstAddIndex = 0;
dirtied |= prefabCollection.SetAssetPathHashes(firstAddIndex);
if (log && dirtied)
UnityDebug.Log($"Default prefab generator updated prefabs in {sw.ElapsedMilliseconds}ms.{GetDirtiedMessage(settings, dirtied)}");
//Check for redundancy.
int frameCount = Time.frameCount;
int changedCount = changedNobPaths.Count;
if (frameCount == _lastUpdatedFrame && assetsLength == _lastUpdatedLengths && (changedCount == _lastUpdatedNamePaths.Count) && changedCount > 0)
{
bool allMatch = true;
for (int i = 0; i < changedCount; i++)
{
if (changedNobPaths[i] != _lastUpdatedNamePaths[i])
{
allMatch = false;
break;
}
}
/* If the import results are the same as the last attempt, on the same frame
* then there is likely an issue saving the assets. */
if (allMatch)
{
//Unset dirtied to prevent a save.
dirtied = false;
//Log this no matter what, it's critical.
UnityDebug.LogError($"Default prefab generator had a problem saving one or more assets. " +
$"This usually occurs when the assets cannot be saved due to missing scripts or serialization errors. " +
$"Please see above any prefabs which could not save any make corrections.");
}
}
//Set last values.
_lastUpdatedFrame = Time.frameCount;
_lastUpdatedNamePaths = changedNobPaths;
_lastUpdatedLengths = assetsLength;
EditorUtility.SetDirty(prefabCollection);
if (dirtied && settings.SaveChanges)
AssetDatabase.SaveAssets();
}
/// <summary>
/// Generates prefabs by iterating all files within settings parameters.
/// </summary>
public static void GenerateFull(PrefabGeneratorConfigurations settings = null, bool forced = false)
{
#if PARRELSYNC
if (ParrelSync.ClonesManager.IsClone() && ParrelSync.Preferences.AssetModPref.Value)
{
UnityDebug.Log("Skipping prefab generation in ParrelSync clone");
return;
}
#endif
if (settings == null)
settings = Configuration.Configurations.PrefabGenerator;
if (!forced && !settings.Enabled)
return;
bool log = settings.LogToConsole;
Stopwatch sw = (log) ? Stopwatch.StartNew() : null;
List<NetworkObject> foundNobs = new List<NetworkObject>();
HashSet<string> excludedPaths = new HashSet<string>(settings.ExcludedFolders);
//If searching the entire project.
if (settings.SearchScope == (int)SearchScopeType.EntireProject)
{
foreach (string path in GetPrefabFiles("Assets", excludedPaths, true))
{
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(path);
if (nob != null)
foundNobs.Add(nob);
}
}
//Specific folders.
else if (settings.SearchScope == (int)SearchScopeType.SpecificFolders)
{
List<SpecifiedFolder> folders = GetSpecifiedFolders(settings.IncludedFolders.ToList());
RemoveOverlappingFolders(folders);
foreach (SpecifiedFolder sf in folders)
{
//If specified folder doesn't exist then continue.
if (!Directory.Exists(sf.Path))
continue;
foreach (string path in GetPrefabFiles(sf.Path, excludedPaths, sf.Recursive))
{
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(path);
if (nob != null)
foundNobs.Add(nob);
}
}
}
//Unhandled.
else
{
UnityDebug.LogError($"{settings.SearchScope} is not handled; default prefabs will not generator properly.");
}
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects(settings);
//No need to error if not found, GetDefaultPrefabObjects will throw.
if (prefabCollection == null)
return;
//Clear and add built list.
prefabCollection.Clear();
prefabCollection.AddObjects(foundNobs, false);
bool dirtied = prefabCollection.SetAssetPathHashes(0);
int newCount = prefabCollection.GetObjectCount();
if (log)
{
string dirtiedMessage = (newCount > 0) ? GetDirtiedMessage(settings, dirtied) : string.Empty;
UnityDebug.Log($"Default prefab generator found {newCount} prefabs in {sw.ElapsedMilliseconds}ms.{dirtiedMessage}");
}
//Only set dirty if and save if prefabs were found.
if (newCount > 0)
{
EditorUtility.SetDirty(prefabCollection);
if (settings.SaveChanges)
AssetDatabase.SaveAssets();
}
}
/// <summary>
/// Iterates folders building them into SpecifiedFolders.
/// </summary>
private static List<SpecifiedFolder> GetSpecifiedFolders(List<string> folders)
{
List<SpecifiedFolder> results = new List<SpecifiedFolder>();
//Remove astericks.
foreach (string path in folders)
{
int pLength = path.Length;
if (pLength == 0)
continue;
bool recursive;
string p;
//If the last character indicates resursive.
if (path.Substring(pLength - 1, 1) == "*")
{
p = path.Substring(0, pLength - 1);
recursive = true;
}
else
{
p = path;
recursive = false;
}
p = GetPlatformPath(p);
results.Add(new SpecifiedFolder(p, recursive));
}
return results;
}
internal static string GetPlatformPath(string path)
{
if (string.IsNullOrEmpty(path))
return path;
path = path.Replace(@"\"[0], Path.DirectorySeparatorChar);
path = path.Replace(@"/"[0], Path.DirectorySeparatorChar);
return path;
}
/// <summary>
/// Returns the DefaultPrefabObjects file.
/// </summary>
private static DefaultPrefabObjects GetDefaultPrefabObjects(PrefabGeneratorConfigurations settings = null)
{
if (settings == null)
settings = Configuration.Configurations.PrefabGenerator;
//Load the prefab collection
string defaultPrefabsPath = settings.DefaultPrefabObjectsPath_Platform;
string fullDefaultPrefabsPath = (defaultPrefabsPath.Length > 0) ? Path.GetFullPath(defaultPrefabsPath) : string.Empty;
//If cached prefabs is not the same path as assetPath.
if (_cachedDefaultPrefabs != null)
{
string unityAssetPath = AssetDatabase.GetAssetPath(_cachedDefaultPrefabs);
string fullCachedPath = (unityAssetPath.Length > 0) ? Path.GetFullPath(unityAssetPath) : string.Empty;
if (fullCachedPath != fullDefaultPrefabsPath)
_cachedDefaultPrefabs = null;
}
//If cached is null try to get it.
if (_cachedDefaultPrefabs == null)
{
//Only try to load it if file exist.
if (File.Exists(fullDefaultPrefabsPath))
{
_cachedDefaultPrefabs = AssetDatabase.LoadAssetAtPath<DefaultPrefabObjects>(defaultPrefabsPath);
if (_cachedDefaultPrefabs == null)
{
//If already retried then throw an error.
if (_retryRefreshDefaultPrefabs)
{
UnityDebug.LogError("DefaultPrefabObjects file exists but it could not be loaded by Unity. Use the Fish-Networking menu to Refresh Default Prefabs.");
}
else
{
UnityDebug.Log("DefaultPrefabObjects file exists but it could not be loaded by Unity. Trying to reload the file next frame.");
_retryRefreshDefaultPrefabs = true;
}
return null;
}
}
}
#if PARRELSYNC
if (!ParrelSync.ClonesManager.IsClone() && ParrelSync.Preferences.AssetModPref.Value)
{
#endif
if (_cachedDefaultPrefabs == null)
{
string fullPath = Path.GetFullPath(defaultPrefabsPath);
UnityDebug.Log($"Creating a new DefaultPrefabsObject at {fullPath}.");
string directory = Path.GetDirectoryName(fullPath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
AssetDatabase.Refresh();
}
_cachedDefaultPrefabs = ScriptableObject.CreateInstance<DefaultPrefabObjects>();
AssetDatabase.CreateAsset(_cachedDefaultPrefabs, defaultPrefabsPath);
AssetDatabase.SaveAssets();
}
#if PARRELSYNC
}
#endif
if (_cachedDefaultPrefabs != null && _retryRefreshDefaultPrefabs)
UnityDebug.Log("DefaultPrefabObjects found on the second iteration.");
return _cachedDefaultPrefabs;
}
/// <summary>
/// Called every frame the editor updates.
/// </summary>
private static void OnEditorUpdate()
{
if (!_retryRefreshDefaultPrefabs)
return;
GenerateFull();
_retryRefreshDefaultPrefabs = false;
}
/// <summary>
/// Called by Unity when assets are modified.
/// </summary>
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
if (Application.isPlaying)
return;
//If retrying next frame don't bother updating, next frame will do a full refresh.
if (_retryRefreshDefaultPrefabs)
return;
//Post process is being ignored. Could be temporary or user has disabled this feature.
if (IgnorePostProcess)
return;
/* Don't iterate if updating or compiling as that could cause an infinite loop
* due to the prefabs being generated during an update, which causes the update
* to start over, which causes the generator to run again, which... you get the idea. */
if (EditorApplication.isCompiling)
return;
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects();
if (prefabCollection == null)
return;
PrefabGeneratorConfigurations settings = Configuration.Configurations.PrefabGenerator;
if (prefabCollection.GetObjectCount() == 0)
{
//If there are no prefabs then do a full rebuild. Odds of there being none are pretty much nill.
GenerateFull(settings);
}
else
{
int totalChanges = importedAssets.Length + deletedAssets.Length + movedAssets.Length + movedFromAssetPaths.Length;
//Nothing has changed. This shouldn't occur but unity is funny so we're going to check anyway.
if (totalChanges == 0)
return;
//normalizes path.
string dpoPath = Path.GetFullPath(settings.DefaultPrefabObjectsPath_Platform);
//If total changes is 1 and the only changed file is the default prefab collection then do nothing.
if (totalChanges == 1)
{
//Do not need to check movedFromAssetPaths because that's not possible for this check.
if ((importedAssets.Length == 1 && Path.GetFullPath(importedAssets[0]) == dpoPath)
|| (deletedAssets.Length == 1 && Path.GetFullPath(deletedAssets[0]) == dpoPath)
|| (movedAssets.Length == 1 && Path.GetFullPath(movedAssets[0]) == dpoPath))
return;
/* If the only change is an import then check if the imported file
* is the same as the last, and if so check into returning early.
* For some reason occasionally when files are saved unity runs postprocess
* multiple times on the same file. */
string imported = (importedAssets.Length == 1) ? importedAssets[0] : null;
if (imported != null && imported == _lastSingleImportedAsset)
{
//If here then the file is the same. Make sure it's already in the collection before returning.
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(imported);
//Not a gameObject, no reason to continue.
if (assetType != typeof(GameObject))
return;
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(imported);
//If is a networked object.
if (nob != null)
{
//Already added!
if (prefabCollection.Prefabs.Contains(nob))
return;
}
}
else if (imported != null)
{
_lastSingleImportedAsset = imported;
}
}
bool fullRebuild = settings.FullRebuild;
/* If updating FN. This needs to be done a better way.
* Parsing the actual version file would be better.
* I'll get to it next release. */
if (!_ranOnce)
{
_ranOnce = true;
fullRebuild = true;
}
else
{
CheckForVersionFile(importedAssets);
CheckForVersionFile(deletedAssets);
CheckForVersionFile(movedAssets);
CheckForVersionFile(movedFromAssetPaths);
}
/* See if any of the changed files are the version file.
* A new version file suggests an update. Granted, this could occur if
* other assets imported a new version file as well but better
* safe than sorry. */
void CheckForVersionFile(string[] arr)
{
string targetText = "VERSION.txt".ToLower();
int targetLength = targetText.Length;
for (int i = 0; i < arr.Length; i++)
{
string item = arr[i];
int itemLength = item.Length;
if (itemLength < targetLength)
continue;
item = item.ToLower();
int startIndex = (itemLength - targetLength);
if (item.Substring(startIndex, targetLength) == targetText)
{
fullRebuild = true;
return;
}
}
}
if (fullRebuild)
GenerateFull(settings);
else
GenerateChanged(importedAssets, deletedAssets, movedAssets, movedFromAssetPaths, settings);
}
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,238 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using UnitySettingsProviderAttribute = UnityEditor.SettingsProviderAttribute;
using UnitySettingsProvider = UnityEditor.SettingsProvider;
using FishNet.Configuring;
using System.Linq;
namespace FishNet.Editing.PrefabCollectionGenerator
{
internal static class SettingsProvider
{
private static readonly Regex SlashRegex = new Regex(@"[\\//]");
private static PrefabGeneratorConfigurations _settings;
private static GUIContent _folderIcon;
private static GUIContent _deleteIcon;
private static Vector2 _scrollVector;
private static bool _showFolders;
[UnitySettingsProvider]
private static UnitySettingsProvider Create()
{
return new UnitySettingsProvider("Project/Fish-Networking/Prefab Objects Generator", SettingsScope.Project)
{
label = "Prefab Objects Generator",
guiHandler = OnGUI,
keywords = new string[]
{
"Fish",
"Networking",
"Prefab",
"Objects",
"Generator",
},
};
}
private static void OnGUI(string searchContext)
{
if (_settings == null)
_settings = Configuration.Configurations.PrefabGenerator;
if (_folderIcon == null)
_folderIcon = EditorGUIUtility.IconContent("d_FolderOpened Icon");
if (_deleteIcon == null)
_deleteIcon = EditorGUIUtility.IconContent("P4_DeletedLocal");
EditorGUI.BeginChangeCheck();
GUIStyle scrollViewStyle = new GUIStyle()
{
padding = new RectOffset(10, 10, 10, 10),
};
_scrollVector = EditorGUILayout.BeginScrollView(_scrollVector, scrollViewStyle);
_settings.Enabled = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.Enabled)), _settings.Enabled);
_settings.LogToConsole = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.LogToConsole)), _settings.LogToConsole);
_settings.FullRebuild = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.FullRebuild)), _settings.FullRebuild);
_settings.SaveChanges = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.SaveChanges)), _settings.SaveChanges);
GUILayoutOption iconWidthConstraint = GUILayout.MaxWidth(32.0f);
GUILayoutOption iconHeightConstraint = GUILayout.MaxHeight(EditorGUIUtility.singleLineHeight);
EditorGUILayout.BeginHorizontal();
string oldAssetPath = _settings.DefaultPrefabObjectsPath;
string newAssetPath = EditorGUILayout.DelayedTextField(ObjectNames.NicifyVariableName(nameof(_settings.DefaultPrefabObjectsPath)), oldAssetPath);
if (GUILayout.Button(_folderIcon, iconWidthConstraint, iconHeightConstraint))
{
if (TrySaveFilePathInsideAssetsFolder(null, Application.dataPath, "DefaultPrefabObjects", "asset", out string result))
newAssetPath = result;
else
EditorWindow.focusedWindow.ShowNotification(new GUIContent($"{ObjectNames.NicifyVariableName(nameof(_settings.DefaultPrefabObjectsPath))} must be inside the Assets folder."));
}
if (!newAssetPath.Equals(oldAssetPath, StringComparison.OrdinalIgnoreCase))
{
if (newAssetPath.StartsWith($"Assets{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase))
{
if (File.Exists(newAssetPath))
{
EditorWindow.focusedWindow.ShowNotification(new GUIContent("Another asset already exists at the new path."));
}
else
{
Generator.IgnorePostProcess = true;
if (File.Exists(oldAssetPath))
AssetDatabase.MoveAsset(oldAssetPath, newAssetPath);
_settings.DefaultPrefabObjectsPath = newAssetPath;
Generator.IgnorePostProcess = false;
}
}
else
{
EditorWindow.focusedWindow.ShowNotification(new GUIContent($"{ObjectNames.NicifyVariableName(nameof(_settings.DefaultPrefabObjectsPath))} must be inside the Assets folder."));
}
}
EditorGUILayout.EndHorizontal();
int currentSearchScope = _settings.SearchScope;
SearchScopeType searchScopeType = (SearchScopeType)EditorGUILayout.EnumPopup(ValueToSearchScope(_settings.SearchScope));
_settings.SearchScope = (int)searchScopeType;
SearchScopeType ValueToSearchScope(int value) => (SearchScopeType)value;
if (_settings.SearchScope == (int)SearchScopeType.EntireProject)
{
EditorGUILayout.HelpBox("Searching the entire project for prefabs can become very slow. Consider switching the search scope to specific folders instead.", MessageType.Warning);
if (GUILayout.Button("Switch"))
_settings.SearchScope = (int)SearchScopeType.SpecificFolders;
}
//If search scope changed then update prefabs.
if (currentSearchScope != _settings.SearchScope && (SearchScopeType)_settings.SearchScope == SearchScopeType.EntireProject)
Generator.GenerateFull();
List<string> folders = null;
string foldersName = null;
if (_settings.SearchScope == (int)SearchScopeType.EntireProject)
{
folders = _settings.ExcludedFolders;
foldersName = ObjectNames.NicifyVariableName(nameof(_settings.ExcludedFolders));
}
else if (_settings.SearchScope == (int)SearchScopeType.SpecificFolders)
{
folders = _settings.IncludedFolders;
foldersName = ObjectNames.NicifyVariableName(nameof(_settings.IncludedFolders));
}
string folderName = foldersName.Substring(0, foldersName.Length - 1);
if ((_showFolders = EditorGUILayout.Foldout(_showFolders, $"{foldersName} ({folders.Count})")) && folders != null)
{
EditorGUI.indentLevel++;
for (int i = 0; i < folders.Count; i++)
{
EditorGUILayout.BeginHorizontal();
string oldFolder = folders[i];
string newFolder = SlashRegex.Replace(EditorGUILayout.DelayedTextField(oldFolder), Path.DirectorySeparatorChar.ToString());
if (!newFolder.Equals(oldFolder, StringComparison.OrdinalIgnoreCase))
{
if (newFolder.StartsWith($"Assets{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase))
folders[i] = newFolder;
else
EditorWindow.focusedWindow.ShowNotification(new GUIContent($"{folderName} must be inside the Assets folder."));
}
if (GUILayout.Button(_folderIcon, iconWidthConstraint, iconHeightConstraint))
{
if (TryOpenFolderPathInsideAssetsFolder(null, Application.dataPath, null, out string result))
folders[i] = result;
else
EditorWindow.focusedWindow.ShowNotification(new GUIContent($"{folderName} must be inside the Assets folder."));
}
if (GUILayout.Button(_deleteIcon, iconWidthConstraint, iconHeightConstraint)) folders.RemoveAt(i);
EditorGUILayout.EndHorizontal();
}
EditorGUI.indentLevel--;
if (_settings.SearchScope == (int)SearchScopeType.SpecificFolders) EditorGUILayout.HelpBox("You can include subfolders by appending an asterisk (*) to a path.", MessageType.None);
if (GUILayout.Button("Browse"))
{
if (TryOpenFolderPathInsideAssetsFolder(null, Application.dataPath, null, out string result))
{
folders.Add(result);
}
else
{
EditorWindow.focusedWindow.ShowNotification(new GUIContent($"{folderName} must be inside the Assets folder."));
}
}
}
if (EditorGUI.EndChangeCheck())
Configuration.Configurations.Write(true);
if (GUILayout.Button("Generate"))
Generator.GenerateFull();
EditorGUILayout.HelpBox("Consider pressing 'Generate' after changing the settings.", MessageType.Info);
EditorGUILayout.EndScrollView();
}
private static bool TrySaveFilePathInsideAssetsFolder(string title, string directory, string name, string extension, out string result)
{
result = null;
string selectedPath = EditorUtility.SaveFilePanel(title, directory, name, extension);
if (selectedPath.StartsWith(Application.dataPath, StringComparison.OrdinalIgnoreCase))
{
result = SlashRegex.Replace(selectedPath.Remove(0, Path.GetDirectoryName(Application.dataPath).Length + 1), Path.DirectorySeparatorChar.ToString());
return true;
}
return false;
}
private static bool TryOpenFolderPathInsideAssetsFolder(string title, string folder, string name, out string result)
{
result = null;
string selectedPath = EditorUtility.OpenFolderPanel(title, folder, name);
if (selectedPath.StartsWith(Application.dataPath, StringComparison.OrdinalIgnoreCase))
{
result = SlashRegex.Replace(selectedPath.Remove(0, Path.GetDirectoryName(Application.dataPath).Length + 1), Path.DirectorySeparatorChar.ToString());
return true;
}
return false;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,85 @@
//#if UNITY_EDITOR
//using FishNet.Managing.Object;
//using FishNet.Object;
//using UnityEditor;
//using UnityEngine;
//namespace FishNet.Editing
//{
// internal class PrefabProcessor : AssetPostprocessor
// {
// #region Private.
// /// <summary>
// /// ScriptableObject to store default prefabs.
// /// </summary>
// private static DefaultPrefabObjects _defaultPrefabs;
// #endregion
// /// <summary>
// /// Called after assets are created or imported.
// /// </summary>
// /// <param name="importedAssets"></param>
// /// <param name="deletedAssets"></param>
// /// <param name="movedAssets"></param>
// /// <param name="movedFromAssetPaths"></param>
//#if UNITY_2021_3_OR_NEWER
// private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
//#else
// private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
//#endif
// {
//#if UNITY_2021_3_OR_NEWER
// if (didDomainReload)
// return;
//#endif
// bool justPopulated;
// if (_defaultPrefabs == null)
// _defaultPrefabs = DefaultPrefabsFinder.GetDefaultPrefabsFile(out justPopulated);
// else
// justPopulated = DefaultPrefabsFinder.PopulateDefaultPrefabs();
// //Not found.
// if (_defaultPrefabs == null)
// return;
// //True if null must be removed as well.
// bool removeNull = (deletedAssets.Length > 0 || movedAssets.Length > 0 || movedFromAssetPaths.Length > 0);
// if (removeNull)
// _defaultPrefabs.RemoveNull();
// /* Only need to add new prefabs if not justPopulated.
// * justPopulated would have already picked up the new prefabs. */
// if (justPopulated)
// return;
// System.Type goType = typeof(UnityEngine.GameObject);
// foreach (string item in importedAssets)
// {
// System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(item);
// if (assetType != goType)
// continue;
// GameObject go = (GameObject)AssetDatabase.LoadAssetAtPath(item, typeof(GameObject));
// //If is a gameobject.
// if (go != null)
// {
// NetworkObject nob;
// //Not a network object.
// if (!go.TryGetComponent<NetworkObject>(out nob))
// continue;
// /* Check for duplicates because adding a component to a prefab will also call this function
// * which will result in this function calling multiple times for the same object. */
// _defaultPrefabs.AddObject(nob, true);
// }
// }
// EditorUtility.SetDirty(_defaultPrefabs);
// }
// }
//}
//#endif

View File

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

View File

@@ -0,0 +1,101 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Build;
using UnityEngine;
namespace FishNet
{
internal static class ScriptingDefines
{
[InitializeOnLoadMethod]
public static void AddDefineSymbols()
{
#if UNITY_2021_3_OR_NEWER
// Get data about current target group
bool standaloneAndServer = false;
BuildTarget buildTarget = EditorUserBuildSettings.activeBuildTarget;
BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget);
if (buildTargetGroup == BuildTargetGroup.Standalone)
{
StandaloneBuildSubtarget standaloneSubTarget = EditorUserBuildSettings.standaloneBuildSubtarget;
if (standaloneSubTarget == StandaloneBuildSubtarget.Server)
standaloneAndServer = true;
}
// Prepare named target, depending on above stuff
NamedBuildTarget namedBuildTarget;
if (standaloneAndServer)
namedBuildTarget = NamedBuildTarget.Server;
else
namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup);
string currentDefines = PlayerSettings.GetScriptingDefineSymbols(namedBuildTarget);
#else
string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
#endif
/* Convert current defines into a hashset. This is so we can
* determine if any of our defines were added. Only save playersettings
* when a define is added. */
HashSet<string> definesHs = new HashSet<string>();
string[] currentArr = currentDefines.Split(';');
//Add current defines into hs.
foreach (string item in currentArr)
definesHs.Add(item);
string proDefine = "FISHNET_PRO";
string versionPrefix = "FISHNET_V";
string thisVersion = $"{versionPrefix}3";
string[] fishNetDefines = new string[]
{
"FISHNET",
thisVersion,
};
bool modified = false;
//Now add FN defines.
foreach (string item in fishNetDefines)
modified |= definesHs.Add(item);
/* Remove pro define if not on pro. This might look a little
* funny because the code below varies depending on if pro or not. */
#pragma warning disable CS0162 // Unreachable code detected
modified |= definesHs.Remove(proDefine);
#pragma warning restore CS0162 // Unreachable code detected
List<string> definesToRemove = new List<string>();
int versionPrefixLength = versionPrefix.Length;
//Remove old versions.
foreach (string item in definesHs)
{
//Do not remove this version.
if (item == thisVersion)
continue;
//If length is possible to be a version prefix and is so then remove it.
if (item.Length >= versionPrefixLength && item.Substring(0, versionPrefixLength) == versionPrefix)
definesToRemove.Add(item);
}
modified |= (definesToRemove.Count > 0);
foreach (string item in definesToRemove)
{
definesHs.Remove(item);
Debug.Log($"Removed unused Fish-Networking define {item}.");
}
if (modified)
{
Debug.Log("Added or removed Fish-Networking defines within player settings.");
string changedDefines = string.Join(";", definesHs);
#if UNITY_2021_3_OR_NEWER
PlayerSettings.SetScriptingDefineSymbols(namedBuildTarget, changedDefines);
#else
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, changedDefines);
#endif
}
}
}
}
#endif

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1,92 @@
fileFormatVersion: 2
guid: bf9191e2e07d29749bca3a1ae44e4bc8
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -0,0 +1,116 @@
fileFormatVersion: 2
guid: 2d50394614f8feb4eb0567fb7618d84d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,116 @@
fileFormatVersion: 2
guid: 2b3dca501a9d8c8479dc71dd068aa8b8
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -0,0 +1,116 @@
fileFormatVersion: 2
guid: 1b187e63031bf7849b249c8212440c3b
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,96 @@
fileFormatVersion: 2
guid: 5ea1cf1e0e57aff4e9ad3cd4246b0e80
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
{
"name": "FishNet.Runtime",
"references": [
"GUID:894a6cc6ed5cd2645bb542978cbed6a9",
"GUID:17013b2a21298c14ea4808251346a38a"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.veriorpies.parrelsync",
"expression": "1.2",
"define": "PARRELSYNC"
}
],
"noEngineReferences": false
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,188 @@
#if UNITY_EDITOR
using FishNet.Editing;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
namespace FishNet.Component.Animating.Editing
{
[CustomEditor(typeof(NetworkAnimator), true)]
[CanEditMultipleObjects]
public class NetworkAnimatorEditor : Editor
{
private SerializedProperty _animator;
private SerializedProperty _interpolation;
//private SerializedProperty _synchronizeInterval;
private SerializedProperty _smoothFloats;
private SerializedProperty _clientAuthoritative;
private SerializedProperty _sendToOwner;
private RuntimeAnimatorController _lastRuntimeAnimatorController;
private AnimatorController _lastAnimatorController;
protected virtual void OnEnable()
{
_animator = serializedObject.FindProperty("_animator");
_interpolation = serializedObject.FindProperty("_interpolation");
//_synchronizeInterval = serializedObject.FindProperty("_synchronizeInterval");
_smoothFloats = serializedObject.FindProperty("_smoothFloats");
_clientAuthoritative = serializedObject.FindProperty("_clientAuthoritative");
_sendToOwner = serializedObject.FindProperty("_sendToOwner");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
NetworkAnimator na = (NetworkAnimator)target;
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour(na), typeof(NetworkAnimator), false);
GUI.enabled = true;
#pragma warning disable CS0162 // Unreachable code detected
EditorGUILayout.HelpBox(EditingConstants.PRO_ASSETS_LOCKED_TEXT, MessageType.Warning);
#pragma warning restore CS0162 // Unreachable code detected
//Animator
EditorGUILayout.LabelField("Animator", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_animator);
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Synchronization Processing.
EditorGUILayout.LabelField("Synchronization Processing", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_interpolation);
//EditorGUILayout.PropertyField(_synchronizeInterval, new GUIContent("Synchronize Interval", "How often to synchronize this animator."));
EditorGUILayout.PropertyField(_smoothFloats);
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Authority.
EditorGUILayout.LabelField("Authority", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_clientAuthoritative);
if (_clientAuthoritative.boolValue == false)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_sendToOwner);
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
DrawParameters(na);
serializedObject.ApplyModifiedProperties();
}
private void DrawParameters(NetworkAnimator na)
{
EditorGUILayout.LabelField("* Synchronized Parameters", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("This setting allows you to optionally completely prevent the synchronization of certain parameters. Both Fish-Networking free and Pro will only synchronize changes as they occur.", MessageType.Info);
if (Application.isPlaying)
{
EditorGUILayout.HelpBox("This feature can only be configured while out of play mode.", MessageType.Info);
return;
}
if (na == null)
return;
Animator animator = na.Animator;
if (animator == null)
return;
RuntimeAnimatorController runtimeController = (animator.runtimeAnimatorController is AnimatorOverrideController aoc) ? aoc.runtimeAnimatorController : animator.runtimeAnimatorController;
if (runtimeController == null)
{
na.IgnoredParameters.Clear();
return;
}
/* If runtime controller changed
* or editor controller is null
* then get new editor controller. */
if (runtimeController != _lastRuntimeAnimatorController || _lastAnimatorController == null)
_lastAnimatorController = (AnimatorController)AssetDatabase.LoadAssetAtPath(AssetDatabase.GetAssetPath(runtimeController), typeof(AnimatorController));
_lastRuntimeAnimatorController = runtimeController;
Color defaultColor = GUI.backgroundColor;
float width = Screen.width;
float spacePerEntry = 125f;
//Buttons seem to be longer than spacePerEntry. Why, because who knows...
float extraSpaceJustBecause = 60;
float spacer = 20f;
width -= spacer;
int entriesPerWidth = Mathf.Max(1, Mathf.FloorToInt(width / (spacePerEntry + extraSpaceJustBecause)));
List<AnimatorControllerParameter> aps = new List<AnimatorControllerParameter>();
//Create a parameter detail for each parameter that can be synchronized.
int count = 0;
foreach (AnimatorControllerParameter item in _lastAnimatorController.parameters)
{
count++;
//Over 240 parameters; who would do this!?
if (count >= 240)
continue;
aps.Add(item);
}
int apsCount = aps.Count;
for (int i = 0; i < apsCount; i++)
{
using (GUILayout.HorizontalScope hs = new GUILayout.HorizontalScope())
{
GUILayout.Space(spacer);
int z = 0;
while (z < entriesPerWidth && (z + i < apsCount))
{
//If this z+i would exceed entries then break.
if (z + i >= apsCount)
break;
AnimatorControllerParameter item = aps[i + z];
string parameterName = item.name;
bool ignored = na.IgnoredParameters.Contains(parameterName);
Color c = (ignored) ? Color.gray : Color.green;
GUI.backgroundColor = c;
if (GUILayout.Button(item.name, GUILayout.Width(spacePerEntry)))
{
if (Application.isPlaying)
{
Debug.Log("Synchronized parameters may not be changed while playing.");
}
else
{
if (ignored)
na.IgnoredParameters.Remove(parameterName);
else
na.IgnoredParameters.Add(parameterName);
UnityEditor.EditorUtility.SetDirty(target);
}
}
z++;
}
i += (z - 1);
}
GUI.backgroundColor = defaultColor;
}
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8cac635f24954048aad3a6ff9110beb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

@@ -0,0 +1,152 @@
#if UNITY_EDITOR
using FishNet.Editing;
using GameKit.Utilities;
using UnityEditor;
using UnityEngine;
using GameKitEditing = GameKit.Utilities.Editing;
namespace FishNet.Component.Transforming.Editing
{
[CustomEditor(typeof(NetworkTransform), true)]
[CanEditMultipleObjects]
public class NetworkTransformEditor : Editor
{
private SerializedProperty _componentConfiguration;
private SerializedProperty _synchronizeParent;
private SerializedProperty _packing;
private SerializedProperty _interpolation;
private SerializedProperty _extrapolation;
private SerializedProperty _enableTeleport;
private SerializedProperty _teleportThreshold;
private SerializedProperty _scaleThreshold;
private SerializedProperty _clientAuthoritative;
private SerializedProperty _sendToOwner;
private SerializedProperty _enableNetworkLod;
private SerializedProperty _interval;
private SerializedProperty _synchronizePosition;
private SerializedProperty _positionSnapping;
private SerializedProperty _synchronizeRotation;
private SerializedProperty _rotationSnapping;
private SerializedProperty _synchronizeScale;
private SerializedProperty _scaleSnapping;
protected virtual void OnEnable()
{
_componentConfiguration = serializedObject.FindProperty(nameof(_componentConfiguration));
_synchronizeParent = serializedObject.FindProperty("_synchronizeParent");
_packing = serializedObject.FindProperty("_packing");
_interpolation = serializedObject.FindProperty("_interpolation");
_extrapolation = serializedObject.FindProperty("_extrapolation");
_enableTeleport = serializedObject.FindProperty("_enableTeleport");
_teleportThreshold = serializedObject.FindProperty("_teleportThreshold");
_scaleThreshold = serializedObject.FindProperty(nameof(_scaleThreshold));
_clientAuthoritative = serializedObject.FindProperty("_clientAuthoritative");
_sendToOwner = serializedObject.FindProperty("_sendToOwner");
_enableNetworkLod = serializedObject.FindProperty(nameof(_enableNetworkLod));
_interval = serializedObject.FindProperty(nameof(_interval));
_synchronizePosition = serializedObject.FindProperty("_synchronizePosition");
_positionSnapping = serializedObject.FindProperty("_positionSnapping");
_synchronizeRotation = serializedObject.FindProperty("_synchronizeRotation");
_rotationSnapping = serializedObject.FindProperty("_rotationSnapping");
_synchronizeScale = serializedObject.FindProperty("_synchronizeScale");
_scaleSnapping = serializedObject.FindProperty("_scaleSnapping");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
GameKitEditing.AddObjectField("Script:", MonoScript.FromMonoBehaviour((NetworkTransform)target), typeof(NetworkTransform), false, EditorLayoutEnableType.Disabled);
bool isPro = false;
if (isPro)
EditorGUILayout.HelpBox(EditingConstants.PRO_ASSETS_UNLOCKED_TEXT, MessageType.None);
else
EditorGUILayout.HelpBox(EditingConstants.PRO_ASSETS_LOCKED_TEXT, MessageType.Warning);
//Misc.
EditorGUILayout.LabelField("Misc", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_componentConfiguration);
EditorGUILayout.PropertyField(_synchronizeParent, new GUIContent("Synchronize Parent"));
EditorGUILayout.PropertyField(_packing);
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Smoothing.
EditorGUILayout.LabelField("Smoothing", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_interpolation);
EditorGUILayout.PropertyField(_extrapolation, new GUIContent("* Extrapolation"));
EditorGUILayout.PropertyField(_enableTeleport);
if (_enableTeleport.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_teleportThreshold);
if (_enableNetworkLod.boolValue)
EditorGUILayout.PropertyField(_scaleThreshold);
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Authority.
EditorGUILayout.LabelField("Authority", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_clientAuthoritative);
if (!_clientAuthoritative.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_sendToOwner);
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Synchronizing.
EditorGUILayout.LabelField("Synchronizing.", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
//LOD and interval.
GameKitEditing.AddPropertyField(_enableNetworkLod, new GUIContent("* Use Network Level of Detail"), EditorLayoutEnableType.DisabledWhilePlaying);
if (!_enableNetworkLod.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_interval, new GUIContent("Send Interval"));
EditorGUI.indentLevel--;
}
//Position.
EditorGUILayout.PropertyField(_synchronizePosition);
if (_synchronizePosition.boolValue)
{
EditorGUI.indentLevel += 2;
EditorGUILayout.PropertyField(_positionSnapping);
EditorGUI.indentLevel -= 2;
}
//Rotation.
EditorGUILayout.PropertyField(_synchronizeRotation);
if (_synchronizeRotation.boolValue)
{
EditorGUI.indentLevel += 2;
EditorGUILayout.PropertyField(_rotationSnapping);
EditorGUI.indentLevel -= 2;
}
//Scale.
EditorGUILayout.PropertyField(_synchronizeScale);
if (_synchronizeScale.boolValue)
{
EditorGUI.indentLevel += 2;
EditorGUILayout.PropertyField(_scaleSnapping);
EditorGUI.indentLevel -= 2;
}
EditorGUI.indentLevel--;
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a2836e36774ca1c4bbbee976e17b649c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
namespace FishNet.Component.Transforming
{
public enum SynchronizedProperty : byte
{
None = 0,
Parent = 1,
Position = 2,
Rotation = 4,
Scale = 8
}
}

View File

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

View File

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

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