using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Oxide.Core; using Oxide.Core.Libraries.Covalence; using Rust.Instruments; using System.Collections.Generic; using System.Linq; using UnityEngine; using static InstrumentKeyController; namespace Oxide.Plugins { [Info("Car Radio", "TCM420G", "1.0.70")] [Description("Allows players to attach radios to vehicles")] class CarRadio : CovalencePlugin { #region variables private const string PERMISSION_ATTACHRADIO = "carradio.attachcarradio"; private const string PERMISSION_DETACHRADIO = "carradio.detachcarradio"; private const string PERMISSION_ATTACHRADIO_GLOBAL = "carradio.attachallcarradio"; private const string PERMISSION_DETACHRADIO_GLOBAL = "carradio.detachallcarradio"; private const string I18N_MISSING_SIREN = "NoRadioForName"; private const string I18N_COULD_NOT_ATTACH = "CouldNotAttach"; private const string I18N_NOT_SUPPORTED = "NotSupported"; private const string I18N_ATTACHED = "Attached"; private const string I18N_ATTACHED_GLOBAL = "AttachedGlobal"; private const string I18N_DETACHED = "Detached"; private const string I18N_DETACHED_GLOBAL = "DetachedGlobal"; private const string I18N_NOT_A_VEHICLE = "NotAVehicle"; private const string I18N_RADIO = "Radios"; private const string I18N_PLAYERS_ONLY = "PlayersOnly"; // Initial prefabs private const string PREFAB_COCKPIT = "assets/content/vehicles/modularcar/module_entities/1module_cockpit.prefab"; private const string PREFAB_COCKPIT_ARMORED = "assets/content/vehicles/modularcar/module_entities/1module_cockpit_armored.prefab"; private const string PREFAB_COCKPIT_WITH_ENGINE = "assets/content/vehicles/modularcar/module_entities/1module_cockpit_with_engine.prefab"; // private const string PREFAB_BUTTON = "assets/prefabs/deployable/playerioents/button/button.prefab"; private const string PREFAB_FLASHERLIGHT = null; private const string PREFAB_SIRENLIGHT = null; private const string PREFAB_SPOTLIGHT = null; private const string PREFAB_RADIO = "assets/prefabs/voiceaudio/boombox/boombox.static.prefab"; // Vehicles private const string PREFAB_KAYAK = "assets/content/vehicles/boats/kayak/kayak.prefab"; private const string PREFAB_TUGBOAT = "assets/content/vehicles/boats/tugboat/tugboat.prefab"; private const string PREFAB_ROWBOAT = "assets/content/vehicles/boats/rowboat/rowboat.prefab"; private const string PREFAB_RHIB = "assets/content/vehicles/boats/rhib/rhib.prefab"; private const string PREFAB_SEDAN = "assets/content/vehicles/sedan_a/sedantest.entity.prefab"; private const string PREFAB_SEDANRAIL = "assets/content/vehicles/sedan_a/sedanrail.entity.prefab"; private const string PREFAB_MINICOPTER = "assets/content/vehicles/minicopter/minicopter.entity.prefab"; private const string PREFAB_ATTACKHELI = "assets/content/vehicles/attackhelicopter/attackhelicopter.entity.prefab"; private const string PREFAB_TRANSPORTHELI = "assets/content/vehicles/scrap heli carrier/scraptransporthelicopter.prefab"; private const string PREFAB_CHINOOK = "assets/prefabs/npc/ch47/ch47.entity.prefab"; private const string PREFAB_MAGNETCRANE = "assets/content/vehicles/crane_magnet/magnetcrane.entity.prefab"; private const string PREFAB_SUBMARINESOLO = "assets/content/vehicles/submarine/submarinesolo.entity.prefab"; private const string PREFAB_SUBMARINEDUO = "assets/content/vehicles/submarine/submarineduo.entity.prefab"; private const string PREFAB_SNOWMOBILE = "assets/content/vehicles/snowmobiles/snowmobile.prefab"; private const string PREFAB_SNOWMOBILETOMAHA = "assets/content/vehicles/snowmobiles/tomahasnowmobile.prefab"; // Train Engine private const string PREFAB_WORKCART = "assets/content/vehicles/trains/workcart/workcart.entity.prefab"; private const string PREFAB_TRAINENGINE = "assets/content/vehicles/trains/workcart/workcart_aboveground.entity.prefab"; private const string PREFAB_TRAINENGINE_COVERED = "assets/content/vehicles/trains/workcart/workcart_aboveground2.entity.prefab"; private const string PREFAB_TRAINENGINE_LOCOMOTIVE = "assets/content/vehicles/trains/locomotive/locomotive.entity.prefab"; // Other Vehicles private const string PREFAB_HORSE = "assets/rust.ai/nextai/testridablehorse.prefab"; private const string PREFAB_HOTAIRBALLOON = "assets/prefabs/deployable/hot air balloon/hotairballoon.prefab"; private const string KEY_MODULAR_CAR = "MODULAR_CAR"; private const string DATAPATH_RADIO = "carradio/"; // Preconfigured carradio private static readonly Radio SIREN_DEFAULT = new Radio("Car-Radio", new Dictionary { [PREFAB_COCKPIT] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.05f, 1.85f, 0.62f), new Vector3(200f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(-0.00f, 0.58f, 0.2f), new Vector3(330f, 180f, 0f)) }, [PREFAB_COCKPIT_ARMORED] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.05f, 1.85f, 0.62f), new Vector3(200f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(-0.00f, 0.58f, 0.2f), new Vector3(330f, 180f, 0f)) }, [PREFAB_COCKPIT_WITH_ENGINE] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.05f, 1.85f, 0.62f), new Vector3(200f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(-0.00f, 0.58f, 0.2f), new Vector3(330f, 180f, 0f)) } }, new Dictionary { [PREFAB_KAYAK] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(-1.0f, 0.2f, 1.5f), new Vector3(270f, 270f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(-0.1f, 0.25f, -0.7f), new Vector3(0f, 0f, 0f)) }, [PREFAB_ROWBOAT] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(-1.7f, 0.5f, -1.8f), new Vector3(270f, 270f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.8f, 2.18f), new Vector3(0f, 180f, 0f)) }, [PREFAB_TUGBOAT] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(-2.05f, 6.8f, 3.1f), new Vector3(230f, 320f, 270f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 8.0f, 0.7f), new Vector3(0f, 0f, 0f)), // Cockpit Radio new Attachment(PREFAB_RADIO, new Vector3(0.0f, 2.0f, 5.3f), new Vector3(0f, 180f, 0f)), // Lower Deck Radio new Attachment(PREFAB_RADIO, new Vector3(0.0f, 2.75f, -10.5f), new Vector3(0f, 0f, 0f)) // Outer Deck Radio }, [PREFAB_RHIB] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.5f, 2.275f, 1.55f), new Vector3(251f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 2.83f, 0.62f), new Vector3(0f, 180f, 0f)) }, [PREFAB_SEDAN] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.0f, 2.07f, 1.98f), new Vector3(210f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.53f, 1.25f), new Vector3(315f, 180f, 0f)) }, [PREFAB_SEDANRAIL] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.0f, 2.3f, 1.22f), new Vector3(210f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.76f, 0.48f), new Vector3(315f, 180f, 0f)) }, [PREFAB_MINICOPTER] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.0f, 2.01f, 1.03f), new Vector3(180f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 1.4f, 0.0f), new Vector3(0f, 0f, 0f)) }, [PREFAB_ATTACKHELI] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.0f, 0.35f, 0.55f), new Vector3(0f, 180f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 2.15f, -0.45f), new Vector3(0f, 0f, 0f)) }, [PREFAB_TRANSPORTHELI] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(-0.1f, 2.68f, 3.865f), new Vector3(205f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 2.51f, 1.53f), new Vector3(90f, 0f, 0f)) }, [PREFAB_CHINOOK] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.0f, 1.9f, 8.0f), new Vector3(270f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 3.5f, -1.6f), new Vector3(100f, 180f, 0f)), // Back Radio new Attachment(PREFAB_RADIO, new Vector3(0.0f, 3.4f, 6.35f), new Vector3(90f, 0f, 0f)) // Cockpit Radio }, [PREFAB_MAGNETCRANE] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(1.08f, 3.98f, -2.15f), new Vector3(233f, 170f, 0f), "Top"), new Attachment(PREFAB_RADIO, new Vector3(0.95f, 3.5f, 0.0f), new Vector3(0f, 180f, 0f), "Top") }, [PREFAB_SUBMARINESOLO] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(-0.27f, 2.55f, -0.27f), new Vector3(180f, 225f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.85f, 0.45f), new Vector3(0f, 180f, 0f)) }, [PREFAB_SUBMARINEDUO] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.275f, 2.5f, 0.76f), new Vector3(172f, 90f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 1.25f, -1.48f), new Vector3(0f, 0f, 0f)) }, [PREFAB_SNOWMOBILE] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.0f, 1.8f, 0.9f), new Vector3(210f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.9f, -1.1f), new Vector3(0f, 0f, 0f)) }, [PREFAB_SNOWMOBILETOMAHA] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.0f, 1.21f, 0.88f), new Vector3(240f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.5f, -1.25f), new Vector3(0f, 0f, 0f)) }, [PREFAB_WORKCART] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.19f, 3.13f, 4.95f), new Vector3(235f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.7f, 2.6f, 0.0f), new Vector3(0f, 180f, 0f)) }, [PREFAB_TRAINENGINE] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.19f, 3.13f, 4.95f), new Vector3(235f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.7f, 2.6f, 0.0f), new Vector3(0f, 180f, 0f)) }, [PREFAB_TRAINENGINE_COVERED] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.19f, 3.13f, 4.95f), new Vector3(235f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.7f, 2.6f, 0.0f), new Vector3(0f, 180f, 0f)) }, [PREFAB_TRAINENGINE_LOCOMOTIVE] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(-0.1f, 4.3f, 5.35f), new Vector3(200f, 270f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(1.1f, 3.5f, 3.34f), new Vector3(0f, 0f, 0f)), // Cockpit Radio new Attachment(PREFAB_RADIO, new Vector3(0.0f, 1.55f, -9.4f), new Vector3(0f, 180f, 0f)) // Back Radio }, [PREFAB_HORSE] = new Attachment[] { new Attachment(PREFAB_RADIO, new Vector3(0.0f, 1.55f, -0.8f), new Vector3(260f, 0f, 180f)) }, [PREFAB_HOTAIRBALLOON] = new Attachment[] { // new Attachment(PREFAB_BUTTON, new Vector3(0.0f, 2.01f, 1.03f), new Vector3(180f, 0f, 0f)), new Attachment(PREFAB_RADIO, new Vector3(0.0f, 1.4f, 0.0f), new Vector3(0f, 0f, 0f)) } }, new Tone(Notes.A, NoteType.Regular, 4, 1f), new Tone(Notes.D, NoteType.Regular, 5, 1f)); private static readonly Radio SIREN_SILENT = new Radio("test-radio", new Dictionary { //Erased Dictionary to remove bulk since it was a duplicate of the above }, new Dictionary { //Erased Dictionary to remove bulk since it was a duplicate of the above }); #endregion variables #region data private class DataContainer { // Map BaseVehicle.net.ID -> RadioInfos public Dictionary VehicleRadioMap = new Dictionary(); } private class VehicleContainer { public string RadioName = SIREN_DEFAULT.Name; public RadioController.States State = RadioController.States.OFF; public HashSet NetIDs = new HashSet(); public VehicleContainer() { } public VehicleContainer(string aRadioName, RadioController.States aState, IEnumerable someNetIDs) { RadioName = aRadioName; State = aState; NetIDs.UnionWith(someNetIDs); } } #endregion data #region configuration private Configuration config; private IDictionary RadioDictionary { get; } = new Dictionary(); private class Configuration { [JsonProperty("MountNeeded")] public bool MountNeeded = true; [JsonProperty("SoundEnabled")] public bool SoundEnabled = true; [JsonProperty("RadioSpawnProbability")] public Dictionary RadioSpawnProbability = new Dictionary { [KEY_MODULAR_CAR] = 0f, [PREFAB_HORSE] = 0f, [PREFAB_MINICOPTER] = 0f, [PREFAB_SEDAN] = 0f, [PREFAB_TRANSPORTHELI] = 0f }; [JsonConverter(typeof(StringEnumConverter))] [JsonProperty("DefaultState")] public RadioController.States DefaultState = RadioController.States.OFF; public string ToJson() => JsonConvert.SerializeObject(this); public Dictionary ToDictionary() => JsonConvert.DeserializeObject>(ToJson()); } private class Tone { public Tone(Notes aNote = Notes.A, NoteType aNoteType = NoteType.Regular, int anOctave = 4, float aDuration = 1f) { Note = aNote; NoteType = aNoteType; Octave = anOctave; Duration = aDuration; } [JsonConverter(typeof(StringEnumConverter))] [JsonProperty("Note")] public Notes Note; [JsonConverter(typeof(StringEnumConverter))] [JsonProperty("NoteType")] public NoteType NoteType; [JsonProperty("Octave")] public int Octave; [JsonProperty("Duration")] public float Duration; public string ToJson() => JsonConvert.SerializeObject(this); public Dictionary ToDictionary() => JsonConvert.DeserializeObject>(ToJson()); } private class Radio { public Radio(string aName, Dictionary someModules, Dictionary someVehicles, params Tone[] someTones) { Name = aName; Modules = someModules; Vehicles = someVehicles; Tones = someTones; } [JsonProperty("Name")] public string Name; [JsonProperty("Tones")] public Tone[] Tones; [JsonProperty("Modules")] public Dictionary Modules; [JsonProperty("Vehicles")] public Dictionary Vehicles; public string ToJson() => JsonConvert.SerializeObject(this); public Dictionary ToDictionary() => JsonConvert.DeserializeObject>(ToJson()); } private class Attachment { public Attachment(string aPrefab, Vector3 aPosition, Vector3 anAngle = new Vector3(), string aBone = null) { Prefab = aPrefab; Position = aPosition; Angle = anAngle; Bone = aBone; } [JsonProperty("Prefab")] public string Prefab; [JsonProperty("Position")] public Vector3 Position; [JsonProperty("Angle")] public Vector3 Angle; [JsonProperty("Bone")] public string Bone; public string ToJson() => JsonConvert.SerializeObject(this); public Dictionary ToDictionary() => JsonConvert.DeserializeObject>(ToJson()); } protected override void LoadDefaultConfig() { config = new Configuration(); RadioDictionary.Clear(); RadioDictionary.Add(SIREN_DEFAULT.Name, SIREN_DEFAULT); RadioDictionary.Add(SIREN_SILENT.Name, SIREN_SILENT); } protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) { throw new JsonException(); } try { foreach (string eachRadioFile in Interface.Oxide.DataFileSystem.GetFiles(DATAPATH_RADIO, "*.json")) { string theFilename = eachRadioFile.Basename(".json"); try { Radio theRadio = Interface.Oxide.DataFileSystem.ReadObject(DATAPATH_RADIO + theFilename); RadioDictionary.Add(theRadio.Name, theRadio); } catch { PrintWarning($"Radio file {theFilename}.json is invalid; ignoring"); } } } catch { } Puts("Loaded carradio: " + string.Join(", ", RadioDictionary.Keys)); if (RadioDictionary.IsEmpty()) { PrintWarning("Configuration appears to be missing carradio; using defaults"); RadioDictionary.Add(SIREN_DEFAULT.Name, SIREN_DEFAULT); RadioDictionary.Add(SIREN_SILENT.Name, SIREN_SILENT); SaveConfig(); } if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys)) { PrintWarning("Configuration appears to be outdated; updating and saving"); SaveConfig(); } } catch { PrintWarning($"Configuration file {Name}.json is invalid; using defaults"); LoadDefaultConfig(); } } protected override void SaveConfig() { PrintWarning($"Configuration changes saved to {Name}.json"); Config.WriteObject(config, true); foreach (Radio eachRadio in RadioDictionary.Values) { Interface.Oxide.DataFileSystem.WriteObject(DATAPATH_RADIO + eachRadio.Name, eachRadio); } } #endregion configuration #region localization protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary { [I18N_MISSING_SIREN] = "No radio was found for the given name (using {0} instead)", [I18N_COULD_NOT_ATTACH] = "Could not attach '{0}'", [I18N_ATTACHED] = "Attached radio '{0}'", [I18N_ATTACHED_GLOBAL] = "Attached radio '{0}' to all existing cars", [I18N_DETACHED] = "Detached radio", [I18N_DETACHED_GLOBAL] = "Detached all existing carradio", [I18N_NOT_A_VEHICLE] = "This entity is not a (supported) vehicle", [I18N_RADIO] = "Available carradio: {0}", [I18N_PLAYERS_ONLY] = "Command '{0}' can only be used by a player", [I18N_NOT_SUPPORTED] = "The radio '{0}' has no configuration for '{1}'" }, this); } #endregion localization #region commands [Command("attachradio"), Permission(PERMISSION_ATTACHRADIO)] private void AttachCarRadios(IPlayer aPlayer, string aCommand, string[] someArgs) { if (aPlayer.IsServer) { Message(aPlayer, I18N_PLAYERS_ONLY, aCommand); return; } BaseVehicle theVehicle = RaycastVehicle(aPlayer); if (theVehicle) { Radio theRadio = someArgs.Length > 0 ? FindRadioForName(someArgs[0], aPlayer) : RadioDictionary.Values.First(); AttachRadios(theVehicle, theRadio, config.DefaultState, aPlayer); Message(aPlayer, I18N_ATTACHED, theRadio.Name); } } [Command("removeradio"), Permission(PERMISSION_DETACHRADIO)] private void DetachCarRadios(IPlayer aPlayer, string aCommand, string[] someArgs) { if (aPlayer.IsServer) { Message(aPlayer, I18N_PLAYERS_ONLY, aCommand); return; } BaseVehicle theVehicle = RaycastVehicle(aPlayer); if (theVehicle && DetachRadios(theVehicle)) { Message(aPlayer, I18N_DETACHED); } } [Command("attachallcarradio"), Permission(PERMISSION_ATTACHRADIO_GLOBAL)] private void AttachAllCarRadios(IPlayer aPlayer, string aCommand, string[] someArgs) { Radio theRadio = someArgs.Length > 0 ? FindRadioForName(someArgs[0], aPlayer) : RadioDictionary.Values.First(); foreach (BaseVehicle eachVehicle in BaseNetworkable.serverEntities.OfType()) { AttachRadios(eachVehicle, theRadio, config.DefaultState, aPlayer); } Message(aPlayer, I18N_ATTACHED_GLOBAL, theRadio.Name); } [Command("detachallcarradio"), Permission(PERMISSION_DETACHRADIO_GLOBAL)] private void DetachAllCarRadios(IPlayer aPlayer, string aCommand, string[] someArgs) { foreach (BaseVehicle eachVehicle in BaseNetworkable.serverEntities.OfType()) { DetachRadios(eachVehicle); } Message(aPlayer, I18N_DETACHED_GLOBAL); } [Command("togglecarradio")] private void ToggleRadios(IPlayer aPlayer, string aCommand, string[] someArgs) { if (aPlayer.IsServer) { Message(aPlayer, I18N_PLAYERS_ONLY, aCommand); return; } BasePlayer thePlayer = aPlayer.Object as BasePlayer; BaseVehicle theVehicle = thePlayer?.GetMountedVehicle(); if (theVehicle) { theVehicle.GetComponent()?.ChangeState(); } else if (!config.MountNeeded) { RaycastVehicle(aPlayer)?.GetComponent()?.ChangeState(); ; } } #endregion commands #region hooks private void Unload() { OnServerSave(); foreach (BaseVehicle eachVehicle in BaseNetworkable.serverEntities.OfType()) { DetachRadios(eachVehicle); } } private void OnServerSave() { DataContainer thePersistentData = new DataContainer(); foreach (BaseVehicle eachCar in BaseNetworkable.serverEntities.OfType()) { RadioController theController = eachCar.GetComponent(); thePersistentData.VehicleRadioMap.Add(eachCar.net.ID.Value, theController ? new VehicleContainer(theController.Radio.Name, theController.State, theController.NetIDs) : null); } Interface.Oxide.DataFileSystem.WriteObject(Name, thePersistentData); } private void OnServerInitialized(bool anInitialFlag) { bool theSpawnRandomlyFlag = config.RadioSpawnProbability.Any(entry => entry.Value > 0f); if (!theSpawnRandomlyFlag) { Unsubscribe("OnEntitySpawned"); } // Reattach on server restart DataContainer thePersistentData = Interface.Oxide.DataFileSystem.ReadObject(Name); foreach (BaseVehicle eachVehicle in BaseNetworkable.serverEntities.OfType()) { VehicleContainer theContainer; if (thePersistentData.VehicleRadioMap.TryGetValue(eachVehicle.net.ID.Value, out theContainer)) { if (theContainer != null) { Radio theRadio; if (RadioDictionary.TryGetValue(theContainer.RadioName, out theRadio)) { CreateRadioController(eachVehicle, theRadio, theContainer.NetIDs); AttachRadios(eachVehicle, theRadio, theContainer.State); } else { CreateRadioController(eachVehicle, null, theContainer.NetIDs); DetachRadios(eachVehicle); PrintWarning($"Missing radio for name \"{theContainer.RadioName}\". Ignoring..."); } } } else if (theSpawnRandomlyFlag) { RadioController theController = eachVehicle.GetComponent(); if (!theController) { float theProbability; if (config.RadioSpawnProbability.TryGetValue(eachVehicle is ModularCar ? KEY_MODULAR_CAR : eachVehicle.PrefabName, out theProbability) && Core.Random.Range(0f, 1f) < theProbability) { AttachRadios(eachVehicle, RadioDictionary.Values.First(), config.DefaultState); } } } } } /* private object OnButtonPress(PressButton aButton, BasePlayer aPlayer) { BaseVehicle theVehicle = aButton.GetComponentInParent()?.VehicleParent(); theVehicle = theVehicle ? theVehicle : aButton.GetComponentInParent(); theVehicle = aPlayer?.GetMountedVehicle(); if (theVehicle) { RadioController theController = theVehicle.GetComponent(); if (theController) { if ((config.MountNeeded && aPlayer.GetMountedVehicle() != theVehicle) || !theController.NetIDs.Contains(aButton.net.ID.Value)) { return false; } theController.ChangeState(); } } return null; } */ private void OnEntitySpawned(BaseVehicle aVehicle) { RadioController theController = aVehicle.GetComponent(); if (!theController) { float theProbability; if (config.RadioSpawnProbability.TryGetValue(aVehicle is ModularCar ? KEY_MODULAR_CAR : aVehicle.PrefabName, out theProbability) && Core.Random.Range(0f, 1f) < theProbability) { AttachRadios(aVehicle, RadioDictionary.Values.First(), config.DefaultState); } } } #endregion hooks #region methods /// /// Tries to attach the given radio to the vehicle, replacing any existing radio. /// /// The vehicle. /// The radio. /// The initial radio state. /// The calling player. private void AttachRadios(BaseVehicle aVehicle, Radio aRadio, RadioController.States anInitialState, IPlayer aPlayer = null) { DetachRadios(aVehicle); RadioController theController = CreateRadioController(aVehicle, aRadio); if (aVehicle as ModularCar) { if (aRadio.Modules == null) { Message(aPlayer, I18N_NOT_SUPPORTED, aRadio.Name, KEY_MODULAR_CAR); DetachRadios(aVehicle); return; } foreach (BaseVehicleModule eachModule in aVehicle.GetComponentsInChildren()) { SpawnAttachments(aRadio.Modules, aPlayer, theController, eachModule); } } else if (!SpawnAttachments(aRadio.Vehicles, aPlayer, theController, aVehicle)) { Message(aPlayer, I18N_NOT_SUPPORTED, aRadio.Name, aVehicle.PrefabName); DetachRadios(aVehicle); return; } theController.SetState(anInitialState); } /// /// Spawns the attachments for the given dictionary for the given parent entity. /// /// The dictionary. /// The calling player. /// The RadioController of the Parent. /// The Parent. /// True, if the parent has an entry in the dictionary with at least one Attachment. private bool SpawnAttachments(IDictionary someAttachments, IPlayer aPlayer, RadioController theController, BaseEntity aParent) { if (someAttachments == null) { return false; } Attachment[] theAttachments; if (someAttachments.TryGetValue(aParent.PrefabName, out theAttachments)) { foreach (Attachment eachAttachment in theAttachments) { BaseEntity theNewEntity = AttachEntity(aParent, eachAttachment.Prefab, eachAttachment.Position, eachAttachment.Angle, eachAttachment.Bone); if (theNewEntity) { theController.NetIDs.Add(theNewEntity.net.ID.Value); } else if (aPlayer != null) { Message(aPlayer, I18N_COULD_NOT_ATTACH, eachAttachment.Prefab); } } return !theAttachments.IsEmpty(); } return false; } /// /// Creates or replaces the RadioController of the given vehicle. /// /// The vehicle. /// The Radio. /// Already existing radio entities. /// The newly created RadioController. private RadioController CreateRadioController(BaseVehicle aVehicle, Radio aRadio, IEnumerable someNetIDs = null) { RadioController theController = aVehicle.GetComponent(); if (theController) { UnityEngine.Object.DestroyImmediate(theController); } theController = aVehicle.gameObject.AddComponent(); theController.Config = config; theController.Radio = aRadio; if (someNetIDs != null) { theController.NetIDs.UnionWith(someNetIDs); } return theController; } /// /// Detaches the radio from a vehicle and removes all corresponding entities. /// /// The vehicle. /// True, if a radio was removed. private bool DetachRadios(BaseVehicle aVehicle) { RadioController theController = aVehicle.GetComponent(); if (theController) { foreach (BaseEntity eachEntity in aVehicle.GetComponentsInChildren()) { if (theController.NetIDs.Contains(eachEntity.net.ID.Value)) { Destroy(eachEntity); } } UnityEngine.Object.DestroyImmediate(theController); return true; } return false; } /// /// Destroys the entity. /// /// The entity. private static void Destroy(BaseEntity anEntity) { if (!anEntity.IsDestroyed) { anEntity.Kill(); } } /// /// Attaches the prefab entity at the given local position and angles to the parent. /// /// The parent. /// The prefab for the new entity. /// The local position. /// The local angles. /// private BaseEntity AttachEntity(BaseEntity aParent, string aPrefab, Vector3 aPosition, Vector3 anAngle = new Vector3(), string aBone = null) { BaseEntity theNewEntity = GameManager.server.CreateEntity(aPrefab, aParent.transform.position); if (!theNewEntity) { return null; } theNewEntity.Spawn(); Transform theBone = aParent.FindBone(aBone); if (theBone == null && aBone != null) { PrintWarning($"No bone found for name '{aBone}'"); PrintWarning("Valid bone names: " + string.Join(", ", aParent.GetBones().Select(eachBone => eachBone.name))); } if (theBone != null && theBone != aParent.transform) { theNewEntity.SetParent(aParent, theBone.name); theNewEntity.transform.localPosition = theBone.InverseTransformPoint(aParent.transform.TransformPoint(aPosition)); theNewEntity.transform.localRotation = Quaternion.Inverse(theBone.rotation) * (aParent.transform.rotation * Quaternion.Euler(anAngle)); } else { theNewEntity.transform.localPosition = aPosition; theNewEntity.transform.localEulerAngles = anAngle; theNewEntity.SetParent(aParent); } //Puts(theNewEntity.ShortPrefabName + ": (" + theNewEntity.GetComponents().Length + ") " + string.Join(", ", theNewEntity.GetComponents().Select(eachComp => eachComp.GetType().Name))); UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent()); UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent()); UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent()); UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent()); theNewEntity.OwnerID = 0; BaseCombatEntity theCombatEntity = theNewEntity as BaseCombatEntity; if (theCombatEntity) { theCombatEntity.pickup.enabled = false; } PressButton theButton = theNewEntity as PressButton; if (theButton) { theButton.pressDuration = 0.2f; } theNewEntity.EnableSaving(true); theNewEntity.SendNetworkUpdateImmediate(); return theNewEntity; } /// /// Toggles the IOEntity. /// /// The IOEntity. /// The new state. private static void ToogleRadios(IOEntity anIOEntity, bool theEnabledFlag) { anIOEntity.UpdateHasPower(theEnabledFlag ? anIOEntity.ConsumptionAmount() : 0, 0); anIOEntity.SetFlag(BaseEntity.Flags.On, theEnabledFlag); } #endregion methods #region helpers private BaseVehicle RaycastVehicle(IPlayer aPlayer) { RaycastHit theHit; if (!Physics.Raycast((aPlayer.Object as BasePlayer).eyes.HeadRay(), out theHit, 5f)) { return null; } BaseVehicle theVehicle = theHit.GetEntity()?.GetComponentInParent(); if (!theVehicle) { Message(aPlayer, I18N_NOT_A_VEHICLE); } return theVehicle; } private Radio FindRadioForName(string aName, IPlayer aPlayer) { Radio theRadio; if (!RadioDictionary.TryGetValue(aName, out theRadio)) { theRadio = RadioDictionary.Values.First(); Message(aPlayer, I18N_MISSING_SIREN, theRadio.Name); } return theRadio; } private string GetText(string aKey, string aPlayerId = null, params object[] someArgs) => string.Format(lang.GetMessage(aKey, this, aPlayerId), someArgs); private void Message(IPlayer aPlayer, string anI18nKey, params object[] someArgs) { if (aPlayer.IsConnected) { string theText = GetText(anI18nKey, aPlayer.Id, someArgs); aPlayer.Reply(theText != anI18nKey ? theText : anI18nKey); } } private void Message(BasePlayer aPlayer, string anI18nKey, params object[] someArgs) { if (aPlayer.IsConnected) { string theText = GetText(anI18nKey, aPlayer.UserIDString, someArgs); aPlayer.ChatMessage(theText != anI18nKey ? theText : anI18nKey); } } #endregion helpers #region controllers private class RadioController : FacepunchBehaviour { public enum States { OFF, ON, LIGHTS_ONLY } private BaseVehicle vehicle; private InstrumentTool trumpet; public Configuration Config { get; set; } public States State { get; private set; } public Radio Radio { get; set; } public ISet NetIDs { get; } = new HashSet(); public States ChangeState() { SetState(State >= States.LIGHTS_ONLY ? States.OFF : State + 1); return State; } public void SetState(States aState) { State = aState; if ((!Config.SoundEnabled || Radio?.Tones?.Length < 1 || !GetTrumpet()) && State == States.ON) { State++; } RefreshRadioState(); } public void RefreshRadioState() { if (State == States.ON) { PlayTone(0); } bool theLightsOnFlag = State > States.OFF; foreach (IOEntity eachEntity in GetVehicle().GetComponentsInChildren()) { //if (NetIDs.Contains(eachEntity.net.ID.Value) && !(eachEntity is PressButton)) -- Original Code for button functionality if (eachEntity is PressButton) { ToogleRadios(eachEntity, theLightsOnFlag); } } } private InstrumentTool GetTrumpet() { if (trumpet == null || trumpet.IsDestroyed) { trumpet = GetVehicle().GetComponentInChildren(); } return trumpet; } private BaseVehicle GetVehicle() { if (vehicle == null) { vehicle = GetComponentInParent(); } return vehicle; } private void PlayTone(int anIndex) { if (State != States.ON || !GetTrumpet()) { return; } if (anIndex >= Radio.Tones.Length) { anIndex = 0; } Tone theTone = Radio.Tones[anIndex]; GetTrumpet().ClientRPC(null, "Client_PlayNote", (int)theTone.Note, (int)theTone.NoteType, theTone.Octave, 1f); Invoke(() => GetTrumpet().ClientRPC(null, "Client_StopNote", (int)theTone.Note, (int)theTone.NoteType, theTone.Octave), theTone.Duration); Invoke(() => PlayTone(++anIndex), theTone.Duration); } } #endregion controllers } }