mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
Pickup Party Physics, part 16!
This commit is contained in:
parent
91ac0f777f
commit
c7a8c34b7e
20
Assets/Mirror/Examples/BenchmarkStinkySteak/Prefabs/NetworkManager.prefab
Executable file → Normal file
20
Assets/Mirror/Examples/BenchmarkStinkySteak/Prefabs/NetworkManager.prefab
Executable file → Normal file
@ -25,13 +25,13 @@ Transform:
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6902534888765376181}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &6902534888765376180
|
||||
MonoBehaviour:
|
||||
@ -47,18 +47,21 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
dontDestroyOnLoad: 1
|
||||
runInBackground: 1
|
||||
autoStartServerBuild: 1
|
||||
autoConnectClientBuild: 0
|
||||
headlessStartMode: 1
|
||||
editorAutoStart: 0
|
||||
sendRate: 20
|
||||
offlineScene:
|
||||
onlineScene:
|
||||
autoStartServerBuild: 0
|
||||
autoConnectClientBuild: 0
|
||||
offlineScene: Assets/Mirror/Examples/BenchmarkStinkySteak/Main.unity
|
||||
onlineScene: Assets/Mirror/Examples/BenchmarkStinkySteak/Main.unity
|
||||
transport: {fileID: 6712455278405722842}
|
||||
networkAddress: localhost
|
||||
maxConnections: 100
|
||||
disconnectInactiveConnections: 0
|
||||
disconnectInactiveTimeout: 60
|
||||
authenticator: {fileID: 0}
|
||||
playerPrefab: {fileID: 388937345275023605, guid: fc40b1557a4729e4e9cfaad99e1097ff, type: 3}
|
||||
playerPrefab: {fileID: 388937345275023605, guid: fc40b1557a4729e4e9cfaad99e1097ff,
|
||||
type: 3}
|
||||
autoCreatePlayer: 1
|
||||
playerSpawnMethod: 0
|
||||
spawnPrefabs: []
|
||||
@ -74,7 +77,8 @@ MonoBehaviour:
|
||||
dynamicAdjustment: 1
|
||||
dynamicAdjustmentTolerance: 1
|
||||
deliveryTimeEmaDuration: 2
|
||||
connectionQualityInterval: 3
|
||||
evaluationMethod: 0
|
||||
evaluationInterval: 3
|
||||
timeInterpolationGui: 0
|
||||
--- !u!114 &6712455278405722842
|
||||
MonoBehaviour:
|
||||
@ -101,7 +105,7 @@ MonoBehaviour:
|
||||
MaxRetransmit: 40
|
||||
MaximizeSocketBuffers: 1
|
||||
ReliableMaxMessageSize: 297433
|
||||
UnreliableMaxMessageSize: 1195
|
||||
UnreliableMaxMessageSize: 1194
|
||||
debugLog: 0
|
||||
statisticsGUI: 0
|
||||
statisticsLog: 0
|
||||
|
@ -98,7 +98,7 @@ HingeJoint:
|
||||
m_Anchor: {x: 0, y: 0, z: 0}
|
||||
m_Axis: {x: 0, y: 0, z: 0}
|
||||
m_AutoConfigureConnectedAnchor: 1
|
||||
m_ConnectedAnchor: {x: -0.096000016, y: 0.19900016, z: 0}
|
||||
m_ConnectedAnchor: {x: -0.029999964, y: 0.5700001, z: 0}
|
||||
serializedVersion: 2
|
||||
m_UseSpring: 1
|
||||
m_Spring:
|
||||
@ -329,8 +329,8 @@ Transform:
|
||||
m_GameObject: {fileID: 723173361404745613}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0.005, z: 2}
|
||||
m_LocalScale: {x: 0.35, y: 0.35, z: 0.5727867}
|
||||
m_LocalPosition: {x: 0, y: 0.005, z: 1.95}
|
||||
m_LocalScale: {x: 0.35, y: 0.35, z: 0.8}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 6299839938573537759}
|
||||
@ -651,7 +651,7 @@ HingeJoint:
|
||||
m_Anchor: {x: 0, y: 0, z: 0}
|
||||
m_Axis: {x: 0, y: 0, z: 0}
|
||||
m_AutoConfigureConnectedAnchor: 1
|
||||
m_ConnectedAnchor: {x: 0, y: 0, z: 0}
|
||||
m_ConnectedAnchor: {x: 0.08600002, y: 1.9759998, z: 0}
|
||||
serializedVersion: 2
|
||||
m_UseSpring: 1
|
||||
m_Spring:
|
||||
@ -1386,7 +1386,6 @@ MonoBehaviour:
|
||||
showOverlay: 0
|
||||
overlayColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
bufferResetMultiplier: 3
|
||||
changedDetection: 1
|
||||
positionSensitivity: 0.01
|
||||
rotationSensitivity: 0.5
|
||||
scaleSensitivity: 0.01
|
||||
@ -1546,8 +1545,8 @@ Transform:
|
||||
m_GameObject: {fileID: 4115217665775924637}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
|
||||
m_LocalPosition: {x: -0, y: -0, z: 1.06}
|
||||
m_LocalScale: {x: 0.2, y: 1.0625, z: 0.2}
|
||||
m_LocalPosition: {x: -0, y: -0, z: 1.1}
|
||||
m_LocalScale: {x: 0.2, y: 1.15, z: 0.2}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2672433250560454}
|
||||
@ -1619,7 +1618,7 @@ CapsuleCollider:
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
m_Enabled: 0
|
||||
serializedVersion: 2
|
||||
m_Radius: 0.5000001
|
||||
m_Height: 2
|
||||
@ -2060,7 +2059,6 @@ MonoBehaviour:
|
||||
showOverlay: 0
|
||||
overlayColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
bufferResetMultiplier: 3
|
||||
changedDetection: 1
|
||||
positionSensitivity: 0.01
|
||||
rotationSensitivity: 0.01
|
||||
scaleSensitivity: 0.01
|
||||
@ -2484,8 +2482,8 @@ Transform:
|
||||
m_GameObject: {fileID: 7317027042101436788}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0.005, z: 2}
|
||||
m_LocalScale: {x: 0.35, y: 0.35, z: 0.5727868}
|
||||
m_LocalPosition: {x: 0, y: 0.005, z: 1.95}
|
||||
m_LocalScale: {x: 0.35, y: 0.35, z: 0.8}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2672433250560454}
|
||||
@ -2645,7 +2643,6 @@ MonoBehaviour:
|
||||
showOverlay: 0
|
||||
overlayColor: {r: 0, g: 0, b: 0, a: 0.5}
|
||||
bufferResetMultiplier: 3
|
||||
changedDetection: 1
|
||||
positionSensitivity: 0.01
|
||||
rotationSensitivity: 0.5
|
||||
scaleSensitivity: 0.01
|
||||
@ -2715,8 +2712,8 @@ Transform:
|
||||
m_GameObject: {fileID: 8938897292254725238}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
|
||||
m_LocalPosition: {x: -0, y: -0, z: 1.06}
|
||||
m_LocalScale: {x: 0.2, y: 1.0625, z: 0.2}
|
||||
m_LocalPosition: {x: -0, y: -0, z: 1.1}
|
||||
m_LocalScale: {x: 0.2, y: 1.15, z: 0.2}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 6299839938573537759}
|
||||
@ -2788,7 +2785,7 @@ CapsuleCollider:
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
m_Enabled: 0
|
||||
serializedVersion: 2
|
||||
m_Radius: 0.5000001
|
||||
m_Height: 2
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,9 +14,9 @@ void Start()
|
||||
lastPosition = characterController.transform.position;
|
||||
}
|
||||
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
// Apply force to joints depending on player characters movement
|
||||
Vector3 movementDelta = characterController.transform.position - lastPosition;
|
||||
Vector3 force = -movementDelta * forceMultiplier;
|
||||
jointRigidbody.AddForce(force);
|
||||
|
@ -17,8 +17,8 @@ public class PickupManager : NetworkBehaviour
|
||||
|
||||
//public override void OnStartServer()
|
||||
//{
|
||||
// removed in favour of waiting until game starts, and then call the interval
|
||||
// StartCoroutine(StartPickupInterval());
|
||||
// StartPickupInterval is called via start game timer, so objects do not appear upon server start
|
||||
// if you dont want objects in the scene to begin with, remove them, call X amount here (optional)
|
||||
//}
|
||||
|
||||
public IEnumerator StartPickupInterval()
|
||||
|
@ -15,7 +15,7 @@ public class PlayerArmSlot : NetworkBehaviour
|
||||
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
// disable trigger by default, and enable if client
|
||||
// disable trigger by default, and enable if local client
|
||||
// this is to lighten collision and calculations on dedicated server and non owners
|
||||
armTrigger.enabled = true;
|
||||
}
|
||||
@ -25,27 +25,23 @@ void OnTriggerEnter(Collider other)
|
||||
//print("OnTriggerEnter: " + other.gameObject.name);
|
||||
if (pickedUpNetworkObject != null) return;
|
||||
|
||||
|
||||
|
||||
// should be a tag, but we're not using tags in examples incase they do not copy across during import
|
||||
// could be a tag, but we're not using tags in examples incase they do not copy across during import
|
||||
pickupObject = other.GetComponent<PickupObject>();
|
||||
if (pickupObject != null)
|
||||
//if ( other.gameObject.name.Contains("PickupObject"))
|
||||
{
|
||||
playerPickupParty.canPickup = true;
|
||||
//collidedGameObject = other.transform;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (pickupObject == null) return;
|
||||
//print("OnTriggerExit: " + other.gameObject.name);
|
||||
|
||||
// should be a tag, but we're not using tags in examples incase they do not copy across during import
|
||||
if (pickupObject == null) return;
|
||||
|
||||
// could be a tag, but we're not using tags in examples incase they do not copy across during import
|
||||
pickupObject = other.GetComponent<PickupObject>();
|
||||
if (pickupObject != null)
|
||||
// if (other.gameObject.name.Contains("PickupObject"))
|
||||
{
|
||||
playerPickupParty.canPickup = false;
|
||||
pickupObject = null;
|
||||
@ -64,15 +60,13 @@ private IEnumerator WaitForResult()
|
||||
{
|
||||
// we use a delay, as network spawned objects may not be spawned in and ready, hooks get called very early
|
||||
yield return new WaitForEndOfFrame();
|
||||
//print("OnPickupChangedHook 1" + pickedUpNetworkObject);
|
||||
|
||||
if (pickedUpNetworkObject)
|
||||
{
|
||||
//print("OnPickupChangedHook 2" + pickedUpNetworkObject);
|
||||
PickupResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
//print("OnPickupChangedHook 3");
|
||||
DropResult();
|
||||
}
|
||||
}
|
||||
@ -81,20 +75,21 @@ private void Update()
|
||||
{
|
||||
if (pickedUpNetworkObject)
|
||||
{
|
||||
// set object to hand locally, without child/parenting, as this can cause weird results with multiplayer, best to avoid
|
||||
pickedUpNetworkObject.transform.position = this.transform.position;
|
||||
}
|
||||
}
|
||||
|
||||
public void PickupResult()
|
||||
{
|
||||
print("PickupResult 1");
|
||||
// we cache rigidbody on pickup, not on trigger detection, so GetComponent will be called less frequently
|
||||
pickupObject = pickedUpNetworkObject.GetComponent<PickupObject>();
|
||||
if (pickupObject)
|
||||
{
|
||||
print("PickupResult 2");
|
||||
pickupObject.playerHolder = this.transform.root.gameObject;
|
||||
//pickupObject.GetComponent<Collider>().enabled = false;
|
||||
|
||||
// set to trigger, to allow zones to detect object, disabling collider would stop the detection
|
||||
// keeping the collider active during pickup, can cause collision jank with local player
|
||||
pickupObject.GetComponent<Collider>().isTrigger = true;
|
||||
if (pickupObject.pickupRigidbody)
|
||||
{
|
||||
@ -115,7 +110,6 @@ public void DropResult()
|
||||
pickupObject.pickupRigidbody.useGravity = true;
|
||||
pickupObject.pickupRigidbody.constraints = RigidbodyConstraints.None;
|
||||
}
|
||||
//pickupObject.GetComponent<Collider>().enabled = true;
|
||||
pickupObject.GetComponent<Collider>().isTrigger = false;
|
||||
armTrigger.enabled = true;
|
||||
pickedUpNetworkObject = null;
|
||||
|
@ -45,7 +45,7 @@ void Awake()
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !UNITY_SERVER
|
||||
//#if !UNITY_SERVER
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
sceneReference.playerPickupParty = this;
|
||||
@ -61,7 +61,7 @@ public override void OnStartLocalPlayer()
|
||||
cameraFollow.enabled = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
//#endif
|
||||
|
||||
public override void OnStartServer()
|
||||
{
|
||||
@ -90,7 +90,7 @@ public void OnTeamChanged(int _old, int _new)
|
||||
}
|
||||
}
|
||||
|
||||
#if !UNITY_SERVER
|
||||
//#if !UNITY_SERVER
|
||||
[ClientCallback]
|
||||
void Update()
|
||||
{
|
||||
@ -136,9 +136,9 @@ void Update()
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
//#endif
|
||||
|
||||
#if !UNITY_SERVER
|
||||
//#if !UNITY_SERVER
|
||||
[ClientCallback]
|
||||
void PlayerMovement()
|
||||
{
|
||||
@ -176,9 +176,9 @@ void PlayerMovement()
|
||||
movement.y = verticalSpeed;
|
||||
characterController.Move(movement * Time.deltaTime);
|
||||
}
|
||||
#endif
|
||||
//#endif
|
||||
|
||||
#if !UNITY_SERVER
|
||||
//#if !UNITY_SERVER
|
||||
[ClientCallback]
|
||||
void RotatePlayerToMouse()
|
||||
{
|
||||
@ -192,20 +192,20 @@ void RotatePlayerToMouse()
|
||||
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, moveSpeed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
//#endif
|
||||
|
||||
#if !UNITY_SERVER
|
||||
//#if !UNITY_SERVER
|
||||
[ClientCallback]
|
||||
void RotateArmsToMouse()
|
||||
{
|
||||
if (slotActive == 0)
|
||||
{
|
||||
Quaternion defaultArmRotation = Quaternion.Euler(-45, -90, 0);
|
||||
Quaternion defaultArmRotation = Quaternion.Euler(-45, 0, 0);
|
||||
armPivots[1].localRotation = Quaternion.Slerp(armPivots[1].localRotation, defaultArmRotation, (armRotationSpeed / 5) * Time.deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
Quaternion defaultArmRotation = Quaternion.Euler(-45, 90, 0);
|
||||
Quaternion defaultArmRotation = Quaternion.Euler(-45, 180, 0);
|
||||
armPivots[0].localRotation = Quaternion.Slerp(armPivots[0].localRotation, defaultArmRotation, (armRotationSpeed / 5) * Time.deltaTime);
|
||||
}
|
||||
|
||||
@ -222,9 +222,9 @@ void RotateArmsToMouse()
|
||||
armPivots[slotActive].rotation = Quaternion.Slerp(armPivots[slotActive].rotation, targetRotation, armRotationSpeed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
//#endif
|
||||
|
||||
#if !UNITY_SERVER
|
||||
//#if !UNITY_SERVER
|
||||
[ClientCallback]
|
||||
void Interact()
|
||||
{
|
||||
@ -242,7 +242,7 @@ void Interact()
|
||||
CmdPlayAudio(2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
//#endif
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace Mirror.Examples.PhysicsPickupParty
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
public class RandomColor : NetworkBehaviour
|
||||
{
|
||||
// Unity clones the material when GetComponent<Renderer>().material is called
|
||||
@ -21,14 +20,13 @@ void SetColor(Color32 _, Color32 newColor)
|
||||
|
||||
public override void OnStartServer()
|
||||
{
|
||||
// Only set the color once. Players may be respawned,
|
||||
// and we don't want to keep changing their colors.
|
||||
if (color == Color.black)
|
||||
color = Random.ColorHSV(0f, 1f, 1f, 1f, 0.5f, 1f);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (cachedMaterial != null)
|
||||
Destroy(cachedMaterial);
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ void OnTriggerEnter(Collider other)
|
||||
{
|
||||
//print("OnTriggerEnter: " + other.gameObject.name);
|
||||
|
||||
// should be a tag, but we're not using tags in examples incase they do not copy across during import
|
||||
// could be a tag, but we're not using tags in examples incase they do not copy across during import
|
||||
pickupObject = other.GetComponent<PickupObject>();
|
||||
if (pickupObject != null)
|
||||
{
|
||||
@ -37,7 +37,7 @@ void OnTriggerExit(Collider other)
|
||||
{
|
||||
//print("OnTriggerExit: " + other.gameObject.name);
|
||||
|
||||
// should be a tag, but we're not using tags in examples incase they do not copy across during import
|
||||
// could be a tag, but we're not using tags in examples incase they do not copy across during import
|
||||
pickupObject = other.GetComponent<PickupObject>();
|
||||
if (pickupObject != null)
|
||||
{
|
||||
|
@ -21,6 +21,8 @@ public class ZonesManager : NetworkBehaviour
|
||||
|
||||
public override void OnStartServer()
|
||||
{
|
||||
// zone scripts disabled by default, and enabled only if server
|
||||
// as server is the one that calculates score
|
||||
foreach (Zones zone in zonesArray)
|
||||
{
|
||||
zone.enabled = true;
|
||||
@ -29,8 +31,6 @@ public override void OnStartServer()
|
||||
|
||||
void OnScoresChanged(int[] _old, int[] _new)
|
||||
{
|
||||
//print("OnScoresChanged");
|
||||
|
||||
for (int i = 0; i < zonesArray.Length; i++)
|
||||
{
|
||||
zonesArray[i].textMesh.text = "Score: " + scoresSyncVars[i].ToString();
|
||||
@ -40,18 +40,13 @@ void OnScoresChanged(int[] _old, int[] _new)
|
||||
|
||||
public void UpdateScores(int _zonesID, int _score)
|
||||
{
|
||||
//print("UpdateScores, for Zone ID: " + _zonesID);
|
||||
|
||||
// we need to call the sync var array this way, to trigger a change detection, so hook is called
|
||||
int[] temp = new int[scoresSyncVars.Length];
|
||||
Array.Copy(scoresSyncVars, temp, scoresSyncVars.Length);
|
||||
temp[_zonesID] += _score;
|
||||
scoresSyncVars = temp;
|
||||
|
||||
if (sceneReference.playerPickupParty.teamID == _zonesID)
|
||||
{
|
||||
RpcPlayAudio();
|
||||
}
|
||||
RpcPlayAudio(_zonesID);
|
||||
}
|
||||
|
||||
public void CalculateZoneWinnersList()
|
||||
@ -62,15 +57,9 @@ public void CalculateZoneWinnersList()
|
||||
for (int i = 0; i < zoneResultsList.Count; i++)
|
||||
{
|
||||
zoneResultsListTuple.Add(new Tuple<int, int>(zoneResultsList[i], i));
|
||||
//print($"Value: {zoneResultsListTuple[i]}");
|
||||
}
|
||||
zoneResultsListTuple.Sort((a, b) => b.Item1.CompareTo(a.Item1));
|
||||
|
||||
//print("1st winner is teamID: " + zoneResultsListTuple[0].Item2 + " - with score of: " + zoneResultsListTuple[0].Item1);
|
||||
//print("2nd winner is teamID: " + zoneResultsListTuple[1].Item2 + " - with score of: " + zoneResultsListTuple[1].Item1);
|
||||
//print("3rd winner is teamID: " + zoneResultsListTuple[2].Item2 + " - with score of: " + zoneResultsListTuple[2].Item1);
|
||||
//print("4th winner is teamID: " + zoneResultsListTuple[3].Item2 + " - with score of: " + zoneResultsListTuple[3].Item1);
|
||||
|
||||
sceneReference.UpdateScoresUI(0,zoneResultsListTuple[0].Item2, zoneResultsListTuple[0].Item1);
|
||||
sceneReference.UpdateScoresUI(1,zoneResultsListTuple[1].Item2, zoneResultsListTuple[1].Item1);
|
||||
sceneReference.UpdateScoresUI(2,zoneResultsListTuple[2].Item2, zoneResultsListTuple[2].Item1);
|
||||
@ -78,14 +67,18 @@ public void CalculateZoneWinnersList()
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
public void RpcPlayAudio()
|
||||
public void RpcPlayAudio(int _zonesID)
|
||||
{
|
||||
PlayAudio();
|
||||
PlayAudio(_zonesID);
|
||||
}
|
||||
|
||||
public void PlayAudio()
|
||||
public void PlayAudio(int _zonesID)
|
||||
{
|
||||
// only play score sound if this is our players zone
|
||||
if (sceneReference.playerPickupParty.teamID == _zonesID)
|
||||
{
|
||||
sceneReference.scoreSound.Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user