C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using Network;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using ProtoBuf;
using UnityEngine;
namespace Oxide.Plugins {
[Info("Sound Library Api", "supreme", "1.1.0")]
[Description("Api for storing / playing player recorded sounds")]
public class SoundLibraryApi : RustPlugin {
#region Class Fields
[PluginReference]
private Plugin HumanNpc, ZoneManager;
private static SoundLibraryApi _pluginInstance;
private PluginConfig _pluginConfig;
private PluginData _pluginData;
private readonly Hash<ulong, PlayerSpeaker> _playerSpeakers = new Hash<ulong, PlayerSpeaker>();
private readonly Hash<uint, PlayerSpeaker> _botSpeakers = new Hash<uint, PlayerSpeaker>();
private ItemDefinition _balaclava;
private ItemDefinition _hoodie;
private ItemDefinition _pants;
private ItemDefinition _gloves;
private ItemDefinition _boots;
private const string UsePermission = "soundlibraryapi.use";
private const string AudioMessagesPermission = "soundlibraryapi.audio";
private const string SpeakerPrefab = "assets/prefabs/player/player.prefab";
private enum HookActionType : byte {
OnUseNPC = 0, OnEnterNPC = 1, OnLeaveNPC = 2, OnHitNPC = 3
}
private enum ZoneHookType : byte {
OnEnterZone = 0, OnExitZone = 1
}
#endregion
#region Hooks
private void Init() {
_pluginInstance = this;
}
private void OnServerInitialized() {
LoadData();
permission.RegisterPermission(UsePermission, this);
permission.RegisterPermission(AudioMessagesPermission, this);
_balaclava = ItemManager.FindItemDefinition("mask.balaclava");
_hoodie = ItemManager.FindItemDefinition("hoodie");
_pants = ItemManager.FindItemDefinition("pants");
_gloves = ItemManager.FindItemDefinition("burlap.gloves");
_boots = ItemManager.FindItemDefinition("shoes.boots");
cmd.AddConsoleCommand("audio.message", this, nameof(ConsoleAudioCommand));
if (_pluginConfig.ScheduledMessageInterval == 0f) {
return;
}
timer.Every(_pluginConfig.ScheduledMessageInterval, () => {
SendSoundToAll(_pluginConfig.ScheduledMessageFile.GetRandom());
}
);
}
private void Unload() {
SaveData();
foreach (PlayerSpeaker speaker in _playerSpeakers.Values.ToList()) {
speaker.Destroy();
}
_pluginInstance = null;
}
// There's an issue where the npc speaker can pick up the player's voice data while talking
private object OnPlayerVoice(BasePlayer player, byte[] data) {
{
NetWrite write = Net.sv.StartWrite();
write.PacketID(Message.Type.VoiceData);
write.UInt32(player.net.ID);
write.BytesWithSize(data);
NetWrite netWrite = write;
SendInfo sendInfo = new SendInfo(BaseNetworkable.GetConnectionsWithin(player.transform.position, 100f).Where(c => c.userid != player.userID).ToList()) {
priority = Priority.Immediate
}
;
netWrite.Send(sendInfo);
}
if (player.activeTelephone) {
player.activeTelephone.OnReceivedVoiceFromUser(data);
}
return true;
}
private void OnPlayerDisconnected(BasePlayer player, string reason) {
_playerSpeakers[player.userID]?.Destroy();
}
//TODO: Remove CanNetwork hook and implement a limitedNetworking & SendAsSnapshot eventually
private object CanNetworkTo(BasePlayer speaker, BasePlayer target) {
if (_botSpeakers.ContainsKey(speaker.net.ID)) {
if (!_playerSpeakers.ContainsKey(target.userID)) {
return false;
}
}
return null;
}
private object OnEntityTakeDamage(BasePlayer player, HitInfo info) {
if (_botSpeakers.ContainsKey(player.net.ID)) {
return true;
}
return null;
}
private void OnPlayerCorpseSpawned(BasePlayer player, NPCPlayerCorpse corpse) {
if (!_botSpeakers.ContainsKey(player.net.ID)) {
return;
}
foreach (ItemContainer container in corpse.containers) {
container.Clear();
}
corpse.containers = null;
ItemManager.DoRemoves();
NextTick(() => {
corpse.Kill();
}
);
}
private void OnPlayerConnected(BasePlayer player) {
if (!player) {
return;
}
if (_pluginConfig.WelcomeMessage) {
timer.Once(5f, () => {
SendSoundToPlayer(player.userID, _pluginConfig.WelcomeMessageFile.GetRandom());
}
);
}
}
private void OnEntitySpawned(BaseHelicopter helicopter) {
if (_pluginConfig.ChopperMessage) {
SendSoundToAll(_pluginConfig.ChopperMessageFile.GetRandom());
}
}
private void OnEntitySpawned(CargoShip ship) {
if (_pluginConfig.CargoshipMessage) {
SendSoundToAll(_pluginConfig.CargoshipMessageFile.GetRandom());
}
}
private void OnEntitySpawned(CargoPlane plane) {
if (_pluginConfig.AirdropMessage) {
SendSoundToAll(_pluginConfig.AirdropMessageFile.GetRandom());
}
}
private void OnEntitySpawned(CH47HelicopterAIController ch47) {
if (_pluginConfig.ChinookMessage) {
SendSoundToAll(_pluginConfig.ChinookMessageFile.GetRandom());
}
}
private void OnCrateHack(HackableLockedCrate crate) {
if (_pluginConfig.CrateHackMessage) {
SendSoundToAll(_pluginConfig.CrateHackMessageFile.GetRandom());
}
}
#endregion
#region HumanNPC Hooks
private void OnUseNPC(BasePlayer npc, BasePlayer player) {
if (player.userID.IsSteamId()) {
SendSoundToPlayer(player.userID, _pluginData.NpcsData[npc.userID]?.Filenames[HookActionType.OnUseNPC]?.GetRandom());
}
}
private void OnEnterNPC(BasePlayer npc, BasePlayer player) {
if (player.userID.IsSteamId()) {
SendSoundToPlayer(player.userID, _pluginData.NpcsData[npc.userID]?.Filenames[HookActionType.OnEnterNPC]?.GetRandom());
}
}
private void OnLeaveNPC(BasePlayer npc, BasePlayer player) {
if (player.userID.IsSteamId()) {
SendSoundToPlayer(player.userID, _pluginData.NpcsData[npc.userID]?.Filenames[HookActionType.OnLeaveNPC]?.GetRandom());
}
}
private void OnHitNPC(BasePlayer npc, HitInfo info) {
BasePlayer player = info.InitiatorPlayer;
if (player && !player.IsNpc && player.userID.IsSteamId()) {
SendSoundToPlayer(info.InitiatorPlayer.userID, _pluginData.NpcsData[npc.userID]?.Filenames[HookActionType.OnHitNPC]?.GetRandom());
}
}
#endregion
#region Zone Manager Hooks
private void OnEnterZone(string zoneId, BasePlayer player) {
SendSoundToPlayer(player.userID, _pluginData.ZonesData[zoneId]?.Filenames[ZoneHookType.OnEnterZone]?.GetRandom());
}
private void OnExitZone(string zoneId, BasePlayer player) {
SendSoundToPlayer(player.userID, _pluginData.ZonesData[zoneId]?.Filenames[ZoneHookType.OnExitZone]?.GetRandom());
}
#endregion
#region Audio Speaker
private class PlayerSpeaker {
private BasePlayer Target {
get;
}
private BasePlayer Speaker {
get;
set;
}
private readonly List<SoundDataV2> _queuedSoundsV2 = new List<SoundDataV2>();
private Coroutine QueuedRoutine {
get;
set;
}
private uint NetId {
get;
set;
}
private long StartTime {
get;
set;
}
private long LastTick {
get;
set;
}
public PlayerSpeaker(BasePlayer target) {
Target = target;
CreateSpeaker(target);
}
private void CreateSpeaker(BasePlayer target) {
Speaker = (BasePlayer) GameManager.server.CreateEntity(SpeakerPrefab, Vector3.down * 5 + Vector3.back * _pluginInstance._pluginConfig.SpeakerDistance);
Speaker.Spawn();
Speaker.RemovePlayerRigidbody();
Speaker.DisablePlayerCollider();
Speaker.SetParent(target);
NetId = Speaker.net.ID;
Speaker.inventory.containerMain.Clear();
Speaker.inventory.containerBelt.Clear();
Speaker.inventory.containerWear.Clear();
ItemManager.DoRemoves();
_pluginInstance.NextTick(() => {
Speaker.inventory.containerWear.AddItem(_pluginInstance._balaclava, 1, 1110601275);
Speaker.inventory.containerWear.AddItem(_pluginInstance._hoodie, 1, 883710255);
Speaker.inventory.containerWear.AddItem(_pluginInstance._pants, 1, 883709785);
Speaker.inventory.containerWear.AddItem(_pluginInstance._gloves, 1, 883710591);
Speaker.inventory.containerWear.AddItem(_pluginInstance._boots, 1, 883709405);
}
);
_pluginInstance._botSpeakers[NetId] = this;
}
public void QueueSoundV2(SoundDataV2 data) {
_queuedSoundsV2.Add(data);
if (QueuedRoutine == null) {
QueuedRoutine = InvokeHandler.Instance.StartCoroutine(RunSpeakerV2());
}
}
public void LiveSound(byte[] data) {
SendSound(Target, Speaker.net.ID, data);
}
private IEnumerator RunSpeakerV2() {
yield return new WaitForSeconds(.5f);
while (_queuedSoundsV2.Count != 0) {
SoundDataV2 soundData = _queuedSoundsV2[0];
_queuedSoundsV2.RemoveAt(0);
LastTick = 0;
StartTime = DateTime.Now.Ticks;
for (int index = 0; index < soundData.Data.Count;) {
while (!WaitForNextSend() && index < soundData.Data.Count) {
SoundLibraryPacket data = soundData.Data[index];
SendSound(Target, Speaker.net.ID, data.SteamVoicePacket);
LastTick = data.TicksSinceStart;
index++;
}
yield return new WaitWhile(WaitForNextSend);
}
yield return new WaitForSeconds(1f);
}
Destroy();
}
private bool WaitForNextSend() {
return DateTime.Now.Ticks - StartTime < LastTick;
}
private void SendSound(BasePlayer player, uint netId, byte[] data) {
if (data.Length == 0) {
return;
}
NetWrite write = Net.sv.StartWrite();
write.PacketID(Message.Type.VoiceData);
write.UInt32(netId);
write.BytesWithSize(data);
write.Send(new SendInfo(player.Connection) {
priority = Priority.Immediate,
}
);
}
public void Destroy() {
if (QueuedRoutine != null) {
InvokeHandler.Instance.StopCoroutine(QueuedRoutine);
}
Speaker.inventory.containerWear.Clear();
Speaker.Kill();
_pluginInstance?._playerSpeakers.Remove(Target.userID);
_pluginInstance?._botSpeakers.Remove(NetId);
}
}
private PlayerSpeaker GetPlayerSpeaker(BasePlayer player) {
PlayerSpeaker speaker = _playerSpeakers[player.userID];
//Added not null check in order to not queue up the sound files multiple times while entering/leaving zone, placeholder code until I implement a different method for the zones
if (speaker != null) {
return null;
}
speaker = new PlayerSpeaker(player);
_playerSpeakers[player.userID] = speaker;
return speaker;
}
private void SendLiveAudio(BasePlayer player, byte[] data) {
PlayerSpeaker speaker = GetPlayerSpeaker(player);
speaker.LiveSound(data);
}
private void SendLiveAudio(List<BasePlayer> players, byte[] data) {
InvokeHandler.Instance.StartCoroutine(HandleLiveAudio(players, data));
}
private IEnumerator HandleLiveAudio(List<BasePlayer> players, byte[] data) {
foreach (BasePlayer player in players) {
SendLiveAudio(player, data);
yield return null;
}
}
private void SendLoadedAudioV2(BasePlayer player, SoundDataV2 soundData) {
PlayerSpeaker speaker = GetPlayerSpeaker(player);
if (speaker == null) {
return;
}
speaker.QueueSoundV2(soundData);
}
private void SendLoadedAudio(IEnumerable<BasePlayer> players, SoundDataV2 soundData) {
InvokeHandler.Instance.StartCoroutine(HandleCreateSpeaker(players, soundData));
}
private IEnumerator HandleCreateSpeaker(IEnumerable<BasePlayer> players, SoundDataV2 soundData) {
foreach (BasePlayer player in players) {
SendLoadedAudioV2(player, soundData);
yield return null;
}
}
#endregion
#region Commands
[ChatCommand("sl")]
private void SoundLibraryChatCommand(BasePlayer player, string command, string[] args) {
if (!permission.UserHasPermission(player.UserIDString, UsePermission)) {
player.ChatMessage(Lang(LangKeys.NoPermission));
return;
}
if (args.Length == 0) {
player.ChatMessage(Lang(LangKeys.HelpText));
return;
}
switch (args[0].ToLower()) {
case "play" : {
HandlePlay(player, args);
break;
}
case "stop" : {
HandleStop(player, args);
break;
}
case "npc" : {
HandleNpc(player, args);
break;
}
case "zone" : {
HandleZone(player, args);
break;
}
default : {
player.ChatMessage(Lang(LangKeys.HelpText));
break;
}
}
}
[ChatCommand("audio")]
private void AudioCommand(BasePlayer player, string command, string[] args) {
if (!permission.UserHasPermission(player.UserIDString, AudioMessagesPermission)) {
player.ChatMessage(Lang(LangKeys.NoPermission));
return;
}
if (args.Length == 0) {
player.ChatMessage(Lang(LangKeys.AudioMessagesHelpText));
return;
}
switch (args[0].ToLower()) {
case "help" : {
player.ChatMessage(Lang(LangKeys.AudioMessagesHelpText));
break;
}
case "send" : {
if (args.Length < 3) {
player.ChatMessage(Lang(LangKeys.AudioMessagesHelpText));
return;
}
BasePlayer target = FindPlayer(args[1]);
if (!target) {
player.ChatMessage(Lang(LangKeys.InvalidTarget));
}
string fileName = args[2];
if (fileName == null) {
player.ChatMessage(Lang(LangKeys.NullFileName));
}
SendSoundToPlayer(target.userID, fileName);
player.ChatMessage(Lang(LangKeys.PlayingAudioMessageText, null, fileName));
break;
}
case "sendall" : {
if (args.Length < 2) {
player.ChatMessage(Lang(LangKeys.AudioMessagesHelpText));
return;
}
string fileName = args[1];
if (fileName == null) {
player.ChatMessage(Lang(LangKeys.NullFileName));
}
SendSoundToAll(fileName);
player.ChatMessage(Lang(LangKeys.PlayingAudioMessageText, null, fileName));
break;
}
case "sendlooped" : {
if (args.Length < 4) {
player.ChatMessage(Lang(LangKeys.AudioMessagesHelpText));
return;
}
BasePlayer target = FindPlayer(args[1]);
if (!target) {
player.ChatMessage(Lang(LangKeys.InvalidTarget));
}
string fileName = args[2];
if (fileName == null) {
player.ChatMessage(Lang(LangKeys.NullFileName));
}
int loopTimes = Convert.ToInt32(args[3]);
SendSoundToPlayerLooped(target.userID, fileName, loopTimes);
player.ChatMessage(Lang(LangKeys.PlayingAudioMessageText, null, fileName));
break;
}
case "sendalllooped" : {
if (args.Length < 3) {
player.ChatMessage(Lang(LangKeys.AudioMessagesHelpText));
return;
}
string fileName = args[1];
if (fileName == null) {
player.ChatMessage(Lang(LangKeys.NullFileName));
}
int loopTimes = Convert.ToInt32(args[2]);
SendSoundToAllLooped(fileName, loopTimes);
player.ChatMessage(Lang(LangKeys.PlayingAudioMessageText, null, fileName));
break;
}
case "files" : {
string[] files = Interface.Oxide.DataFileSystem.GetFiles("SoundLibraryApi");
StringBuilder stringBuilder = new StringBuilder();
foreach (string file in files) {
if (file.Contains(".json")) {
continue;
}
stringBuilder.AppendLine(file.Split('\\').Last().Replace(".data", string.Empty));
}
player.ChatMessage(Lang(LangKeys.AvailableFileNames, null, stringBuilder));
break;
}
}
}
private void ConsoleAudioCommand(ConsoleSystem.Arg arg) {
if (!arg.HasArgs()) {
arg.ReplyWith(Lang(LangKeys.AudioMessagesHelpText));
return;
}
switch (arg.Args[0].ToLower()) {
case "help" : {
arg.ReplyWith(Lang(LangKeys.AudioMessagesHelpText));
break;
}
case "send" : {
if (arg.Args.Length < 3) {
arg.ReplyWith(Lang(LangKeys.AudioMessagesHelpText));
return;
}
BasePlayer target = FindPlayer(arg.Args[1]);
if (!target) {
arg.ReplyWith(Lang(LangKeys.InvalidTarget));
return;
}
string fileName = arg.Args[2];
if (fileName == null) {
arg.ReplyWith(Lang(LangKeys.NullFileName));
return;
}
SendSoundToPlayer(target.userID, fileName);
arg.ReplyWith(Lang(LangKeys.PlayingAudioMessageText, null, fileName));
break;
}
case "sendall" : {
if (arg.Args.Length < 2) {
arg.ReplyWith(Lang(LangKeys.AudioMessagesHelpText));
return;
}
string fileName = arg.Args[1];
if (fileName == null) {
arg.ReplyWith(Lang(LangKeys.NullFileName));
return;
}
SendSoundToAll(fileName);
arg.ReplyWith(Lang(LangKeys.PlayingAudioMessageText, null, fileName));
break;
}
case "sendlooped" : {
if (arg.Args.Length < 4) {
arg.ReplyWith(Lang(LangKeys.AudioMessagesHelpText));
return;
}
BasePlayer target = FindPlayer(arg.Args[1]);
if (!target) {
arg.ReplyWith(Lang(LangKeys.InvalidTarget));
}
string fileName = arg.Args[2];
if (fileName == null) {
arg.ReplyWith(Lang(LangKeys.NullFileName));
}
int loopTimes = Convert.ToInt32(arg.Args[3]);
SendSoundToPlayerLooped(target.userID, fileName, loopTimes);
arg.ReplyWith(Lang(LangKeys.PlayingAudioMessageText, null, fileName));
break;
}
case "sendalllooped" : {
if (arg.Args.Length < 3) {
arg.ReplyWith(Lang(LangKeys.AudioMessagesHelpText));
return;
}
string fileName = arg.Args[1];
if (fileName == null) {
arg.ReplyWith(Lang(LangKeys.NullFileName));
}
int loopTimes = Convert.ToInt32(arg.Args[2]);
SendSoundToAllLooped(fileName, loopTimes);
arg.ReplyWith(Lang(LangKeys.PlayingAudioMessageText, null, fileName));
break;
}
case "files" : {
string[] files = Interface.Oxide.DataFileSystem.GetFiles("SoundLibraryApi");
StringBuilder stringBuilder = new StringBuilder();
foreach (string file in files) {
if (file.Contains("NpcData")) {
continue;
}
stringBuilder.AppendLine(file.Split('\\').Last().Replace(".json", string.Empty));
}
arg.ReplyWith(Lang(LangKeys.AvailableFileNames, null, stringBuilder));
break;
}
}
}
#endregion
#region Play Handler
private void HandlePlay(BasePlayer player, string[] args) {
if (args.Length < 2) {
player.ChatMessage(Lang(LangKeys.PlayInvalidSyntax));
return;
}
string name = string.Join(" ", args.Skip(1).ToArray());
SoundDataV2 v2 = SoundDataV2.Load(name);
if (v2 != null) {
SendLoadedAudioV2(player, v2);
player.ChatMessage(Lang(LangKeys.PlayingText, null, name));
return;
}
player.ChatMessage(Lang(LangKeys.NoSoundFileFound, player, name));
}
private void HandleStop(BasePlayer player, string[] args) {
GetPlayerSpeaker(player)?.Destroy();
}
private void HandleNpc(BasePlayer player, string[] args) {
BasePlayer npc = FindNpc(player);
if (!npc) {
player.ChatMessage(Lang(LangKeys.NpcNotFound));
}
else {
if (args.Length < 4) {
player.ChatMessage(Lang(LangKeys.HelpText));
return;
}
switch (args[1].ToLower()) {
case "add" : {
string action = args[2];
HookActionType hookActionType;
if (!Enum.TryParse(action, true, out hookActionType)) {
player.ChatMessage(Lang(LangKeys.InvalidHookActionType, null, action));
return;
}
string fileName = args[3];
if (fileName == null) {
player.ChatMessage(Lang(LangKeys.NullFileName));
return;
}
Hash<ulong, NpcData> npcData = _pluginData.NpcsData;
if (npcData.ContainsKey(npc.userID)) {
if (npcData[npc.userID].Filenames.ContainsKey(hookActionType)) {
if (npcData[npc.userID].Filenames[hookActionType].Contains(fileName)) {
player.ChatMessage(Lang(LangKeys.FileNameAlreadyAdded, null, fileName));
return;
}
npcData[npc.userID].Filenames[hookActionType].Add(fileName);
}
else {
npcData[npc.userID].Filenames.Add(hookActionType, new List<string> {
fileName
}
);
}
}
else {
npcData.Add(npc.userID, new NpcData {
Filenames = new Hash<HookActionType, List<string>> {
[hookActionType]
= new List<string> {
fileName
}
}
}
);
}
player.ChatMessage(Lang(LangKeys.NpcSoundAdded, null, fileName, hookActionType));
SaveData();
break;
}
case "remove" : {
string action = args[2];
HookActionType hookActionType;
if (!Enum.TryParse(action, true, out hookActionType)) {
player.ChatMessage(Lang(LangKeys.InvalidHookActionType, null, action));
return;
}
string fileName = args[3];
if (fileName == null) {
player.ChatMessage(Lang(LangKeys.NullFileName));
return;
}
Hash<ulong, NpcData> npcData = _pluginData.NpcsData;
if (npcData.ContainsKey(npc.userID)) {
if (npcData[npc.userID].Filenames.ContainsKey(hookActionType)) {
if (npcData[npc.userID].Filenames[hookActionType].Contains(fileName)) {
npcData[npc.userID].Filenames[hookActionType].Remove(fileName);
}
}
}
player.ChatMessage(Lang(LangKeys.NpcSoundRemoved, null, fileName, hookActionType));
SaveData();
break;
}
}
}
}
private void HandleZone(BasePlayer player, string[] args) {
if (args.Length < 5) {
player.ChatMessage(Lang(LangKeys.HelpText));
return;
}
string zoneId = args[4];
if (CheckZoneId(zoneId) == null) {
player.ChatMessage(Lang(LangKeys.ZoneNotFound));
return;
}
switch (args[1].ToLower()) {
case "add" : {
string hookType = args[2];
ZoneHookType zoneHookType;
if (!Enum.TryParse(hookType, true, out zoneHookType)) {
player.ChatMessage(Lang(LangKeys.InvalidZoneHookType, null, hookType));
return;
}
string fileName = args[3];
if (fileName == null) {
player.ChatMessage(Lang(LangKeys.NullFileName));
return;
}
Hash<string, ZoneData> zoneData = _pluginData.ZonesData;
if (zoneData.ContainsKey(zoneId)) {
if (zoneData[zoneId].Filenames.ContainsKey(zoneHookType)) {
if (zoneData[zoneId].Filenames[zoneHookType].Contains(fileName)) {
player.ChatMessage(Lang(LangKeys.FileNameAlreadyAdded, null, fileName));
return;
}
zoneData[zoneId].Filenames[zoneHookType].Add(fileName);
}
else {
zoneData[zoneId].Filenames.Add(zoneHookType, new List<string> {
fileName
}
);
}
}
else {
zoneData.Add(zoneId, new ZoneData {
Filenames = new Hash<ZoneHookType, List<string>> {
[zoneHookType]
= new List<string> {
fileName
}
}
}
);
}
player.ChatMessage(Lang(LangKeys.ZoneSoundAdded, null, fileName, zoneHookType, zoneId));
SaveData();
break;
}
case "remove" : {
string hookType = args[2];
ZoneHookType zoneHookType;
if (!Enum.TryParse(hookType, true, out zoneHookType)) {
player.ChatMessage(Lang(LangKeys.InvalidZoneHookType, null, hookType));
return;
}
string fileName = args[3];
if (fileName == null) {
player.ChatMessage(Lang(LangKeys.NullFileName));
return;
}
Hash<string, ZoneData> zoneData = _pluginData.ZonesData;
if (zoneData.ContainsKey(zoneId)) {
if (zoneData[zoneId].Filenames.ContainsKey(zoneHookType)) {
if (zoneData[zoneId].Filenames[zoneHookType].Contains(fileName)) {
zoneData[zoneId].Filenames[zoneHookType].Remove(fileName);
}
}
}
player.ChatMessage(Lang(LangKeys.ZoneSoundRemoved, null, fileName, zoneId));
SaveData();
break;
}
}
}
#endregion
#region Configuration
private class PluginConfig {
[DefaultValue(true)]
[JsonProperty(PropertyName = "Enable welcome message")]
public bool WelcomeMessage {
get;
set;
}
[JsonProperty(PropertyName = "Welcome message file name")]
public List<string> WelcomeMessageFile {
get;
set;
}
[DefaultValue(true)]
[JsonProperty(PropertyName = "Enable Chopper event message")]
public bool ChopperMessage {
get;
set;
}
[JsonProperty(PropertyName = "Chopper event message file name")]
public List<string> ChopperMessageFile {
get;
set;
}
[DefaultValue(true)]
[JsonProperty(PropertyName = "Enable Airdrop event message")]
public bool AirdropMessage {
get;
set;
}
[JsonProperty(PropertyName = "Airdrop event message file name")]
public List<string> AirdropMessageFile {
get;
set;
}
[DefaultValue(true)]
[JsonProperty(PropertyName = "Enable Cargoship event message")]
public bool CargoshipMessage {
get;
set;
}
[JsonProperty(PropertyName = "Cargoship event message file name")]
public List<string> CargoshipMessageFile {
get;
set;
}
[DefaultValue(true)]
[JsonProperty(PropertyName = "Enable Chinook event message")]
public bool ChinookMessage {
get;
set;
}
[JsonProperty(PropertyName = "Chinook event message file name")]
public List<string> ChinookMessageFile {
get;
set;
}
[DefaultValue(true)]
[JsonProperty(PropertyName = "Enable Crate Hack event message")]
public bool CrateHackMessage {
get;
set;
}
[JsonProperty(PropertyName = "Crate Hack event message file name")]
public List<string> CrateHackMessageFile {
get;
set;
}
[DefaultValue(900f)]
[JsonProperty(PropertyName = "Scheduled message interval (seconds)")]
public float ScheduledMessageInterval {
get;
set;
}
[JsonProperty(PropertyName = "Scheduled message file name")]
public List<string> ScheduledMessageFile {
get;
set;
}
[DefaultValue(5f)]
[JsonProperty(PropertyName = "Speaker Distance (The higher the number, the further away is the speaker resulting in a quieter voice)")]
public float SpeakerDistance {
get;
set;
}
}
protected override void LoadDefaultConfig() {
PrintWarning("Loading Default Config");
}
protected override void LoadConfig() {
base.LoadConfig();
Config.Settings.DefaultValueHandling = DefaultValueHandling.Populate;
_pluginConfig = AdditionalConfig(Config.ReadObject<PluginConfig>());
Config.WriteObject(_pluginConfig);
}
private PluginConfig AdditionalConfig(PluginConfig pluginConfig) {
pluginConfig.WelcomeMessageFile = pluginConfig.WelcomeMessageFile ?? new List<string> {
"filename1", "filename2", "filename3"
}
;
pluginConfig.ChopperMessageFile = pluginConfig.ChopperMessageFile ?? new List<string> {
"filename1", "filename2", "filename3"
}
;
pluginConfig.AirdropMessageFile = pluginConfig.AirdropMessageFile ?? new List<string> {
"filename1", "filename2", "filename3"
}
;
pluginConfig.CargoshipMessageFile = pluginConfig.CargoshipMessageFile ?? new List<string> {
"filename1", "filename2", "filename3"
}
;
pluginConfig.ChinookMessageFile = pluginConfig.ChinookMessageFile ?? new List<string> {
"filename1", "filename2", "filename3"
}
;
pluginConfig.CrateHackMessageFile = pluginConfig.CrateHackMessageFile ?? new List<string> {
"filename1", "filename2", "filename3"
}
;
pluginConfig.ScheduledMessageFile = pluginConfig.ScheduledMessageFile ?? new List<string> {
"filename1", "filename2", "filename3"
}
;
return pluginConfig;
}
#endregion
#region Data Handler
private void SaveData() {
Interface.Oxide.DataFileSystem.WriteObject($"{Name}/Data", _pluginData);
}
private void LoadData() {
_pluginData = Interface.Oxide.DataFileSystem.ReadObject<PluginData>($"{Name}/Data");
if (_pluginData == null) {
_pluginData = new PluginData();
}
}
private class PluginData {
[JsonProperty(PropertyName = "Npc Data")]
public Hash<ulong, NpcData> NpcsData {
get;
set;
}
= new Hash<ulong, NpcData>();
[JsonProperty(PropertyName = "Zone Data")]
public Hash<string, ZoneData> ZonesData {
get;
set;
}
= new Hash<string, ZoneData>();
}
private class NpcData {
[JsonProperty(PropertyName = "Action type of the npc & Filenames that are played")]
public Hash<HookActionType, List<string>> Filenames {
get;
set;
}
}
private class ZoneData {
[JsonProperty(PropertyName = "Zone hook type & Filenames that are played")]
public Hash<ZoneHookType, List<string>> Filenames {
get;
set;
}
}
#endregion
#region Language
private class LangKeys {
public const string NoPermission = nameof(NoPermission);
public const string HelpText = nameof(HelpText);
public const string AudioMessagesHelpText = nameof(AudioMessagesHelpText);
public const string InvalidAddSyntax = nameof(InvalidAddSyntax);
public const string AddAlreadyExists = nameof(AddAlreadyExists);
public const string PlayInvalidSyntax = nameof(PlayInvalidSyntax);
public const string NoSoundFileFound = nameof(NoSoundFileFound);
public const string PlayingText = nameof(PlayingText);
public const string PlayingAudioMessageText = nameof(PlayingAudioMessageText);
public const string InvalidTarget = nameof(InvalidTarget);
public const string NullFileName = nameof(NullFileName);
public const string FileNameNotFound = nameof(FileNameNotFound);
public const string AvailableFileNames = nameof(AvailableFileNames);
public const string NpcNotFound = nameof(NpcNotFound);
public const string InvalidHookActionType = nameof(InvalidHookActionType);
public const string InvalidZoneHookType = nameof(InvalidZoneHookType);
public const string NpcSoundAdded = nameof(NpcSoundAdded);
public const string NpcSoundRemoved = nameof(NpcSoundRemoved);
public const string ZoneSoundAdded = nameof(ZoneSoundAdded);
public const string ZoneSoundRemoved = nameof(ZoneSoundRemoved);
public const string ZoneNotFound = nameof(ZoneNotFound);
public const string FileNameAlreadyAdded = nameof(FileNameAlreadyAdded);
}
protected override void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string, string> {
[LangKeys.NoPermission]
= "<color=#e3e3e3>You do not have permission to use this command</color>", [LangKeys.InvalidAddSyntax] = "<color=#e3e3e3>Invalid Syntax. Ex: <color=#de8732>/sl add soundname</color></color>", [LangKeys.AddAlreadyExists] = "<color=#e3e3e3>This sound already exists. </color>" + "<color=#e3e3e3>Please add <color=#de8732>overwrite</color> to your arguments to allow overwriting an existing sound.</color>" + "<color=#e3e3e3>Ex: <color=#de8732>sl add {{0}} overwrite</color></color>", [LangKeys.PlayInvalidSyntax] = "<color=#e3e3e3>Invalid Syntax. Ex: <color=#de8732>/sl play soundname</color> - to play the sound with the given name.</color>", [LangKeys.NoSoundFileFound] = "<color=#e3e3e3>No sound file found with name '{0}'</color>", [LangKeys.HelpText] = "<color=#e3e3e3>Allows players to record sound that can be played back to a player\n" + "<color=#de8732>/sl npc add hookActionType fileName</color> - to add one or more filenames to a human npc (hook action types are: <color=#de8732>OnUseNPC/OnEnterNPC/OnLeaveNPC/OnHitNPC</color>)\n" + "<color=#de8732>/sl npc remove hookActionType fileName</color> - to remove a filename from a human npc (hook action types are: <color=#de8732>OnUseNPC/OnEnterNPC/OnLeaveNPC/OnHitNPC</color>)\n" + "<color=#de8732>/sl zone add zoneHookType fileName zoneId</color> - to add one or more filenames to a zone (zone hook types are: <color=#de8732>OnEnterZone/OnExitZone</color>)\n" + "<color=#de8732>/sl zone remove zoneHookType fileName zoneId</color> - to remove a filename from a zone (zone hook types are: <color=#de8732>OnEnterZone/OnExitZone</color>)\n" + "<color=#de8732>/sl cancel</color> - to cancel your recording and not save.\n" + "<color=#de8732>/sl play</color> - to player your current recording back to you.\n" + "<color=#de8732>/sl</color> - to view this help text again.</color>", [LangKeys.AudioMessagesHelpText] = "<color=#e3e3e3><color=#66B2FF>/audio send playerName audioName</color> - Sends an audio message to a player</color>\n" + "<color=#e3e3e3><color=#66B2FF>/audio sendall audioName</color> - Sends an audio message to everyone on the server\n" + "<color=#e3e3e3><color=#66B2FF>/audio sendlooped playerName audioName 2 (loop times amount)</color> - Sends a looped audio message to a player\n" + "<color=#e3e3e3><color=#66B2FF>/audio sendalllooped audioName 2 (loop times amount)</color> - Sends a looped audio message to everyone on the server\n" + "<color=#e3e3e3><color=#66B2FF>/audio files</color> - Shows a list of all audio files on the server\n" + "<color=#e3e3e3><color=#66B2FF>/sl</color> - Shows the help page for the Sound Library API\n" + "<color=#e3e3e3><color=#66B2FF>/audio help</color> - Shows these help messages", [LangKeys.PlayingText] = "<color=#e3e3e3>Playing the file with the name of <color=#de8732>{0}</color></color>", [LangKeys.PlayingAudioMessageText] = "<color=#e3e3e3>Playing the file with the name of <color=#66B2FF>{0}</color></color>", [LangKeys.InvalidTarget] = "<color=#e3e3e3>Invalid target, try using his steamid/name</color>", [LangKeys.NullFileName] = "<color=#e3e3e3>This file name is null, use /audio files</color>", [LangKeys.AvailableFileNames] = "<color=#e3e3e3>Available files:\n<color=#66B2FF>{0}</color></color>", [LangKeys.FileNameNotFound] = "<color=#e3e3e3><color=#66B2FF>{0}</color> does not exist, use <color=#66B2FF>/audio files</color></color>", [LangKeys.NpcNotFound] = "<color=#e3e3e3>Couldn't find any human npc, make sure you have created one using <color=#de8732>/npc_add</color> and you are looking at it!</color>", [LangKeys.InvalidHookActionType] = "<color=#e3e3e3><color=#de8732>{0}</color> is invalid, list of hook action types:<color=#de8732>\nOnUseNPC\nOnEnterNPC\nOnLeaveNPC\nOnHitNPC</color></color>", [LangKeys.NpcSoundAdded] = "<color=#e3e3e3><color=#de8732>{0}</color> was added and will play when <color=#66B2FF>{1}</color> is triggered</color>", [LangKeys.NpcSoundRemoved] = "<color=#e3e3e3><color=#de8732>{0}</color> was removed from this NPC!</color>", [LangKeys.InvalidZoneHookType] = "<color=#e3e3e3><color=#de8732>{0}</color> is invalid, list of zone hook types:<color=#de8732>\nOnEnterZone\nOnExitZone</color></color>", [LangKeys.ZoneSoundAdded] = "<color=#e3e3e3><color=#de8732>{0}</color> was added and will play when <color=#66B2FF>{1}</color> is triggered (Zone ID: {2})</color>", [LangKeys.ZoneSoundRemoved] = "<color=#e3e3e3><color=#de8732>{0}</color> was removed from this Zone ({1})!</color>", [LangKeys.ZoneNotFound] = "<color=#e3e3e3>Zone not found!</color>", [LangKeys.FileNameAlreadyAdded] = "<color=#e3e3e3><color=#de8732>{0}</color> was already added!</color>"
}
, this);
}
private string Lang(string key, BasePlayer player = null, params object[] args) {
try {
return string.Format(lang.GetMessage(key, this, player?.UserIDString), args);
}
catch (Exception ex) {
PrintError($"Lang Key '{key}' threw exception\n:{ex}");
throw;
}
}
#endregion
#region API
private void SendSoundToPlayer(ulong playerId, string name) {
if (string.IsNullOrEmpty(name)) {
return;
}
BasePlayer player = BasePlayer.FindAwakeOrSleeping(playerId.ToString());
if (!player || !player.IsConnected) {
return;
}
SoundDataV2 data = SoundDataV2.Load(name);
if (data == null) {
return;
}
SendLoadedAudioV2(player, data);
player.ChatMessage(Lang(LangKeys.PlayingText, null, name));
}
private void SendSoundToPlayers(List<BasePlayer> players, string name) {
if (string.IsNullOrEmpty(name)) {
return;
}
SoundDataV2 data = SoundDataV2.Load(name);
if (data == null) {
return;
}
SendLoadedAudio(players, data);
}
private void SendSoundToAll(string name) {
if (string.IsNullOrEmpty(name)) {
return;
}
SoundDataV2 da
// ... [Code preview truncated due to length. Full source in attached file] ...