C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Oxide.Core.Libraries.Covalence;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
// TODO: Fix weapons floating when spectating
namespace Oxide.Plugins
{
[Info("Spectate", "Wulf/lukespragg", "0.4.3")]
[Description("Allows only players with permission to spectate")]
public class Spectate : CovalencePlugin
{
#region Localization
private new void LoadDefaultMessages()
{
// English
lang.RegisterMessages(new Dictionary<string, string>
{
["CommandSpectate"] = "spectate",
["NotAllowed"] = "You are not allowed to use the '{0}' command",
["NoValidTargets"] = "No valid spectate targets",
["PlayersOnly"] = "Command '{0}' can only be used by a player",
["SpectateSelf"] = "You cannot spectate yourself",
["SpectateStart"] = "Started spectating {0}",
["SpectateStop"] = "Stopped spectating {0}",
["TargetIsSpectating"] = "{0} is currently spectating another player"
}, this);
// French
lang.RegisterMessages(new Dictionary<string, string>
{
["CommandSpectate"] = "spectre",
["NotAllowed"] = "Vous n’êtes pas autorisé à utiliser la commande « {0} »",
["NoValidTargets"] = "Non valides spectate cibles",
["PlayersOnly"] = "Commande « {0} » seulement peut être utilisée que par un joueur",
["SpectateSelf"] = "Vous ne pouvez pas vous-même spectate",
["SpectateStart"] = "Commencé spectature {0}",
["SpectateStop"] = "Cessé de spectature {0}",
["TargetIsSpectating"] = "{0} est spectature actuellement un autre joueur"
}, this, "fr");
// German
lang.RegisterMessages(new Dictionary<string, string>
{
["CommandSpectate"] = "gespenst",
["NotAllowed"] = "Sie sind nicht berechtigt, verwenden Sie den Befehl '{0}'",
["NoValidTargets"] = "Zuschauen Sie keine gültige Ziele",
["PlayersOnly"] = "Befehl '{0}' kann nur von einem Spieler verwendet werden",
["SpectateSelf"] = "Sie können nicht selbst als Zuschauer",
["SpectateStart"] = "Begann zuschauen {0}",
["SpectateStop"] = "Nicht mehr zuschauen {0}",
["TargetIsSpectating"] = "{0} ist derzeit ein anderer Spieler zuschauen"
}, this, "de");
// Russian
lang.RegisterMessages(new Dictionary<string, string>
{
["CommandSpectate"] = "Призрак",
["NotAllowed"] = "Нельзя использовать команду «{0}»",
["NoValidTargets"] = "Нет допустимых spectate целей",
["PlayersOnly"] = "Команда «{0}» может использоваться только игрок",
["SpectateSelf"] = "Вы не можете spectate себя",
["SpectateStart"] = "Начал spectating {0}",
["SpectateStop"] = "Остановлен spectating {0}",
["TargetIsSpectating"] = "{0} в настоящее время spectating другой игрок"
}, this, "ru");
// Spanish
lang.RegisterMessages(new Dictionary<string, string>
{
["CommandSpectate"] = "espectador",
["NotAllowed"] = "No se permite utilizar el comando '{0}'",
["NoValidTargets"] = "No válido espectador objetivos",
["PlayersOnly"] = "Comando '{0}' solo puede ser usado por un jugador",
["SpectateSelf"] = "Usted no puede sí mismo espectador",
["SpectateStart"] = "Comenzó a observar {0}",
["SpectateStop"] = "Dejado de ver {0}",
["TargetIsSpectating"] = "{0} está actualmente tenemos otro jugador"
}, this, "es");
}
#endregion Localization
#region Initialization
private readonly Dictionary<string, Vector3> lastPositions = new Dictionary<string, Vector3>();
private readonly Dictionary<string, string> spectating = new Dictionary<string, string>();
private const string permUse = "spectate.use";
private void Init()
{
permission.RegisterPermission(permUse, this);
AddLocalizedCommand("CommandSpectate", "SpectateCommand");
}
#endregion Initialization
#region Chat Command
private void SpectateCommand(IPlayer player, string command, string[] args)
{
if (player.IsServer)
{
Message(player, "PlayersOnly", command);
return;
}
if (!player.HasPermission(permUse))
{
Message(player, "NotAllowed", command);
return;
}
BasePlayer basePlayer = player.Object as BasePlayer;
if (basePlayer == null)
{
return;
}
if (!basePlayer.IsSpectating())
{
BasePlayer target = BasePlayer.Find(string.Join(" ", args.Select(v => v.ToString()).ToArray()));
if (target == null || target.IsDead())
{
Message(player, "NoValidTargets");
return;
}
if (ReferenceEquals(target, basePlayer))
{
Message(player, "SpectateSelf");
return;
}
if (target.IsSpectating())
{
Message(player, "TargetIsSpectating", target.displayName);
return;
}
// Store current location before spectating
if (!lastPositions.ContainsKey(player.Id))
{
lastPositions.Add(player.Id, basePlayer.transform.position);
}
else
{
lastPositions[player.Id] = basePlayer.transform.position;
}
// Prep player for spectate mode
HeldEntity heldEntity = basePlayer.GetActiveItem()?.GetHeldEntity() as HeldEntity;
heldEntity?.SetHeld(false);
// Put player in spectate mode
basePlayer.SetPlayerFlag(BasePlayer.PlayerFlags.Spectating, true);
basePlayer.gameObject.SetLayerRecursive(10);
basePlayer.CancelInvoke("MetabolismUpdate");
basePlayer.CancelInvoke("InventoryUpdate");
basePlayer.ClearEntityQueue();
basePlayer.SendEntitySnapshot(target);
basePlayer.gameObject.Identity();
basePlayer.SetParent(target);
basePlayer.SetPlayerFlag(BasePlayer.PlayerFlags.ThirdPersonViewmode, true);
player.Command("camoffset 0,1.3,0");
// Notify player and store target name
Message(player, "SpectateStart", target.displayName);
if (!spectating.ContainsKey(player.Id))
{
spectating.Add(player.Id, target.displayName);
}
else
{
spectating[player.Id] = target.displayName;
}
}
else
{
// Restore player to normal mode
player.Command("camoffset", "0,1,0");
basePlayer.SetParent(null);
basePlayer.SetPlayerFlag(BasePlayer.PlayerFlags.Spectating, false);
basePlayer.SetPlayerFlag(BasePlayer.PlayerFlags.ThirdPersonViewmode, false);
basePlayer.gameObject.SetLayerRecursive(17);
basePlayer.metabolism.Reset();
basePlayer.InvokeRepeating("InventoryUpdate", 1f, 0.1f * UnityEngine.Random.Range(0.99f, 1.01f));
// Restore player to previous state
basePlayer.StartSleeping();
HeldEntity heldEntity = basePlayer.GetActiveItem()?.GetHeldEntity() as HeldEntity;
heldEntity?.SetHeld(true);
// Teleport to original location after spectating
if (lastPositions.ContainsKey(player.Id))
{
Vector3 lastPosition = lastPositions[player.Id];
player.Teleport(lastPosition.x, lastPosition.y + 0.3f, lastPosition.z);
lastPositions.Remove(player.Id);
}
// Notify player and clear target name
if (spectating.ContainsKey(player.Id))
{
Message(player, "SpectateStop", spectating[player.Id]);
spectating.Remove(player.Id);
}
else
{
Message(player, "SpectateStop", "?");
}
}
}
#endregion Chat Command
#region Game Hooks
private void OnUserConnected(IPlayer player) => ResetSpectate(player);
private void OnUserDisconnected(IPlayer player) => ResetSpectate(player);
// Reset player's camera offset and stored states
private void ResetSpectate(IPlayer player)
{
player.Command("camoffset 0,1,0");
if (spectating.ContainsKey(player.Id))
{
spectating.Remove(player.Id);
}
if (lastPositions.ContainsKey(player.Id))
{
lastPositions.Remove(player.Id);
}
}
#endregion Game Hooks
#region Helpers
private string Lang(string key, string id = null, params object[] args)
{
return string.Format(lang.GetMessage(key, this, id), args);
}
private void AddLocalizedCommand(string key, string command)
{
foreach (string language in lang.GetLanguages(this))
{
Dictionary<string, string> messages = lang.GetMessages(language, this);
foreach (KeyValuePair<string, string> message in messages.Where(m => m.Key.Equals(key)))
{
if (!string.IsNullOrEmpty(message.Value))
{
AddCovalenceCommand(message.Value, command);
}
}
}
}
private void Message(IPlayer player, string key, params object[] args)
{
player.Reply(Lang(key, player.Id, args));
}
#endregion Helpers
}
}