Advertisement
Zarius

Rust - CarRadio

Jun 10th, 2022
1,125
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 39.81 KB | None | 0 0
  1. using Newtonsoft.Json;
  2. using Newtonsoft.Json.Converters;
  3. using Oxide.Core;
  4. using Oxide.Core.Libraries.Covalence;
  5. using Rust.Instruments;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using UnityEngine;
  9. using static InstrumentKeyController;
  10.  
  11. namespace Oxide.Plugins
  12. {
  13.     [Info("Car Radio", "TCM420G", "1.0.3")]
  14.     [Description("Allows players to attach radios to vehicles")]
  15.     class CarRadio : CovalencePlugin
  16.     {
  17.         #region variables
  18.         private const string PERMISSION_ATTACHRADIO = "carradio.attachcarradio";
  19.         private const string PERMISSION_DETACHRADIO = "carradio.detachcarradio";
  20.         private const string PERMISSION_ATTACHRADIO_GLOBAL = "carradio.attachallcarradio";
  21.         private const string PERMISSION_DETACHRADIO_GLOBAL = "carradio.detachallcarradio";
  22.  
  23.         private const string I18N_MISSING_SIREN = "NoRadioForName";
  24.         private const string I18N_COULD_NOT_ATTACH = "CouldNotAttach";
  25.         private const string I18N_NOT_SUPPORTED = "NotSupported";
  26.         private const string I18N_ATTACHED = "Attached";
  27.         private const string I18N_ATTACHED_GLOBAL = "AttachedGlobal";
  28.         private const string I18N_DETACHED = "Detached";
  29.         private const string I18N_DETACHED_GLOBAL = "DetachedGlobal";
  30.         private const string I18N_NOT_A_VEHICLE = "NotAVehicle";
  31.         private const string I18N_RADIO = "Radios";
  32.         private const string I18N_PLAYERS_ONLY = "PlayersOnly";
  33.  
  34.         // Initial prefabs
  35.         private const string PREFAB_COCKPIT = "assets/content/vehicles/modularcar/module_entities/1module_cockpit.prefab";
  36.         private const string PREFAB_COCKPIT_ARMORED = "assets/content/vehicles/modularcar/module_entities/1module_cockpit_armored.prefab";
  37.         private const string PREFAB_COCKPIT_WITH_ENGINE = "assets/content/vehicles/modularcar/module_entities/1module_cockpit_with_engine.prefab";
  38.         private const string PREFAB_BUTTON = "assets/prefabs/deployable/playerioents/button/button.prefab";
  39.         private const string PREFAB_FLASHERLIGHT = null;
  40.         private const string PREFAB_SIRENLIGHT = null;
  41.         private const string PREFAB_RADIO = "assets/prefabs/voiceaudio/boombox/boombox.static.prefab";
  42.  
  43.         private const string PREFAB_SEDAN = "assets/content/vehicles/sedan_a/sedantest.entity.prefab";
  44.         private const string PREFAB_MINICOPTER = "assets/content/vehicles/minicopter/minicopter.entity.prefab";
  45.         private const string PREFAB_TRANSPORTHELI = "assets/content/vehicles/scrap heli carrier/scraptransporthelicopter.prefab";
  46.         private const string PREFAB_RHIB = "assets/content/vehicles/boats/rhib/rhib.prefab";
  47.         private const string PREFAB_ROWBOAT = "assets/content/vehicles/boats/rowboat/rowboat.prefab";
  48.         private const string PREFAB_WORKCART = "assets/content/vehicles/workcart/workcart.entity.prefab";
  49.         private const string PREFAB_WORKCART_ABOVEGROUND = "assets/content/vehicles/workcart/workcart_aboveground.entity.prefab";
  50.         private const string PREFAB_MAGNETCRANE = "assets/content/vehicles/crane_magnet/magnetcrane.entity.prefab";
  51.         private const string PREFAB_HORSE = "assets/rust.ai/nextai/testridablehorse.prefab";
  52.  
  53.         private const string KEY_MODULAR_CAR = "MODULAR_CAR";
  54.  
  55.         private const string DATAPATH_RADIO = "carradio/";
  56.  
  57.         // Preconfigured carradio
  58.         private static readonly Radio SIREN_DEFAULT = new Radio("Car-Radio",
  59.             new Dictionary<string, Attachment[]>
  60.             {
  61.                 [PREFAB_COCKPIT] = new Attachment[] {
  62.                     new Attachment(PREFAB_BUTTON, new Vector3(0.05f, 1.7f, 0.78f), new Vector3(210f, 0f, 0f)),
  63.                     new Attachment(PREFAB_RADIO, new Vector3(-0.00f, 1.0f, -0.9f), new Vector3(180f, 180f, 180f))
  64.                 },
  65.                 [PREFAB_COCKPIT_ARMORED] = new Attachment[] {
  66.                     new Attachment(PREFAB_BUTTON, new Vector3(0.05f, 1.7f, 0.78f), new Vector3(210f, 0f, 0f)),
  67.                     new Attachment(PREFAB_RADIO, new Vector3(-0.00f, 1.0f, -0.9f), new Vector3(180f, 180f, 180f))
  68.                 },
  69.                 [PREFAB_COCKPIT_WITH_ENGINE] = new Attachment[] {
  70.                     new Attachment(PREFAB_BUTTON, new Vector3(0.05f, 1.7f, 0.78f), new Vector3(210f, 0f, 0f)),
  71.                     new Attachment(PREFAB_RADIO, new Vector3(-0.00f, 1.0f, -0.9f), new Vector3(180f, 180f, 180f))
  72.                 }
  73.             },
  74.            new Dictionary<string, Attachment[]>
  75.             {
  76.                 [PREFAB_SEDAN] = new Attachment[] {
  77.                     new Attachment(PREFAB_BUTTON, new Vector3(0.0f, 2.05f, 1.9f), new Vector3(210f, 0f, 0f)),
  78.                         new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.55f, 1.7f), new Vector3(0f, 180f, 0f))
  79.                 },
  80.                 [PREFAB_MINICOPTER] = new Attachment[] {
  81.                     new Attachment(PREFAB_BUTTON, new Vector3(-0.2f, 2.0f, 0.91f), new Vector3(180f, 0f, 0f)),
  82.                     new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.700f, -0.1f), new Vector3(0f, 0f, 0f))
  83.                 },
  84.                 [PREFAB_TRANSPORTHELI] = new Attachment[] {
  85.                     new Attachment(PREFAB_BUTTON, new Vector3(-0.1f, 2.68f, 3.865f), new Vector3(205f, 0f, 0f)),
  86.                     new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.55f, 3.6f), new Vector3(0f, 180f, 0f))
  87.                 },
  88.                 [PREFAB_RHIB] = new Attachment[] {
  89.                     new Attachment(PREFAB_BUTTON, new Vector3(-0.0f, 3.4f, 0.60f), new Vector3(180f, 0f, 0f)),
  90.                     new Attachment(PREFAB_RADIO, new Vector3(0.0f, 2.83f, 0.62f), new Vector3(0f, 180f, 0f))
  91.                 },
  92.                 [PREFAB_ROWBOAT] = new Attachment[] {
  93.                     new Attachment(PREFAB_BUTTON, new Vector3(-1.7f, 0.5f, -1.8f), new Vector3(270f, 270f, 0f)),
  94.                     new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.8f, 2.18f), new Vector3(0f, 180f, 0f))
  95.                 },
  96.                 [PREFAB_WORKCART] = new Attachment[] {
  97.                     new Attachment(PREFAB_BUTTON, new Vector3(0.19f, 3.13f, 4.95f), new Vector3(235f, 0f, 0f)),
  98.                     new Attachment(PREFAB_RADIO, new Vector3(0.49f, 1.53f, 3.95f), new Vector3(0f, 180f, 0f))
  99.                 },
  100.                 [PREFAB_WORKCART_ABOVEGROUND] = new Attachment[] {
  101.                     new Attachment(PREFAB_BUTTON, new Vector3(0.19f, 3.13f, 4.95f), new Vector3(235f, 0f, 0f)),
  102.                     new Attachment(PREFAB_RADIO, new Vector3(0.49f, 1.53f, 3.95f), new Vector3(0f, 180f, 0f))
  103.                 },
  104.                 [PREFAB_MAGNETCRANE] = new Attachment[] {
  105.                     new Attachment(PREFAB_FLASHERLIGHT, new Vector3(-0.95f, 4.25f, 0.5f))
  106.                 }
  107.             }, new Tone(Notes.A, NoteType.Regular, 4, 1f), new Tone(Notes.D, NoteType.Regular, 5, 1f));
  108.         private static readonly Radio SIREN_SILENT = new Radio("test-radio",
  109.             new Dictionary<string, Attachment[]>
  110.             {
  111.                 [PREFAB_COCKPIT] = new Attachment[] {
  112.                     new Attachment(PREFAB_BUTTON, new Vector3(0.05f, 1.7f, 0.78f), new Vector3(210f, 0f, 0f)),
  113.                     new Attachment(PREFAB_RADIO, new Vector3(-0.00f, 1.0f, -0.9f), new Vector3(180f, 180f, 180f))
  114.                 },
  115.                 [PREFAB_COCKPIT_ARMORED] = new Attachment[] {
  116.                     new Attachment(PREFAB_BUTTON, new Vector3(0.05f, 1.7f, 0.78f), new Vector3(210f, 0f, 0f)),
  117.                     new Attachment(PREFAB_RADIO, new Vector3(-0.00f, 1.0f, -0.9f), new Vector3(180f, 180f, 180f))
  118.                 },
  119.                 [PREFAB_COCKPIT_WITH_ENGINE] = new Attachment[] {
  120.                     new Attachment(PREFAB_BUTTON, new Vector3(0.05f, 1.7f, 0.78f), new Vector3(210f, 0f, 0f)),
  121.                     new Attachment(PREFAB_RADIO, new Vector3(-0.00f, 1.0f, -0.9f), new Vector3(180f, 180f, 180f))
  122.                 }
  123.             },
  124.             new Dictionary<string, Attachment[]>
  125.           {
  126.                 [PREFAB_SEDAN] = new Attachment[] {
  127.                     new Attachment(PREFAB_BUTTON, new Vector3(0.0f, 2.05f, 1.9f), new Vector3(210f, 0f, 0f)),
  128.                         new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.55f, 3.6f), new Vector3(0f, 180f, 0f))
  129.                 },
  130.                 [PREFAB_MINICOPTER] = new Attachment[] {
  131.                     new Attachment(PREFAB_BUTTON, new Vector3(-0.1f, 2.0f, 1.0f), new Vector3(180f, 0f, 0f)),
  132.                     new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.700f, -0.1f), new Vector3(0f, 0f, 0f))
  133.                 },
  134.                 [PREFAB_TRANSPORTHELI] = new Attachment[] {
  135.                     new Attachment(PREFAB_BUTTON, new Vector3(-0.1f, 2.68f, 3.865f), new Vector3(205f, 0f, 0f)),
  136.                     new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.55f, 3.6f), new Vector3(0f, 180f, 0f))
  137.                 },
  138.                 [PREFAB_RHIB] = new Attachment[] {
  139.                     new Attachment(PREFAB_BUTTON, new Vector3(-0.0f, 3.4f, 0.60f), new Vector3(180f, 0f, 0f)),
  140.                     new Attachment(PREFAB_RADIO, new Vector3(0.0f, 2.83f, 0.62f), new Vector3(0f, 180f, 0f))
  141.                 },
  142.                 [PREFAB_ROWBOAT] = new Attachment[] {
  143.                     new Attachment(PREFAB_BUTTON, new Vector3(-1.7f, 0.5f, -1.8f), new Vector3(270f, 270f, 0f)),
  144.                     new Attachment(PREFAB_RADIO, new Vector3(0.0f, 0.8f, 2.18f), new Vector3(0f, 180f, 0f))
  145.                 },
  146.                 [PREFAB_WORKCART] = new Attachment[] {
  147.                     new Attachment(PREFAB_BUTTON, new Vector3(0.19f, 3.13f, 4.95f), new Vector3(235f, 0f, 0f))
  148.                 },
  149.                 [PREFAB_MAGNETCRANE] = new Attachment[] {
  150.                     new Attachment(PREFAB_FLASHERLIGHT, new Vector3(-0.95f, 4.25f, 0.5f))
  151.                 },
  152.                 [PREFAB_HORSE] = new Attachment[] {
  153.                     new Attachment(PREFAB_RADIO, new Vector3(0.0f, 1.7f, 1.2f), new Vector3(25f, 0f, 0f), "head")
  154.                 }
  155.             });
  156.         #endregion variables
  157.  
  158.         #region data
  159.         private class DataContainer
  160.         {
  161.             // Map BaseVehicle.net.ID -> RadioInfos
  162.             public Dictionary<uint, VehicleContainer> VehicleRadioMap = new Dictionary<uint, VehicleContainer>();
  163.         }
  164.  
  165.         private class VehicleContainer
  166.         {
  167.             public string RadioName = SIREN_DEFAULT.Name;
  168.             public RadioController.States State = RadioController.States.OFF;
  169.             public HashSet<uint> NetIDs = new HashSet<uint>();
  170.  
  171.             public VehicleContainer()
  172.             {
  173.             }
  174.  
  175.             public VehicleContainer(string aRadioName, RadioController.States aState, IEnumerable<uint> someNetIDs)
  176.             {
  177.                 RadioName = aRadioName;
  178.                 State = aState;
  179.                 NetIDs.UnionWith(someNetIDs);
  180.             }
  181.         }
  182.         #endregion data
  183.  
  184.         #region configuration
  185.  
  186.         private Configuration config;
  187.         private IDictionary<string, Radio> RadioDictionary { get; } = new Dictionary<string, Radio>();
  188.  
  189.         private class Configuration
  190.         {
  191.             [JsonProperty("MountNeeded")]
  192.             public bool MountNeeded = true;
  193.  
  194.             [JsonProperty("SoundEnabled")]
  195.             public bool SoundEnabled = true;
  196.  
  197.             [JsonProperty("RadioSpawnProbability")]
  198.             public Dictionary<string, float> RadioSpawnProbability = new Dictionary<string, float>
  199.             {
  200.                 [KEY_MODULAR_CAR] = 0f,
  201.                 [PREFAB_HORSE] = 0f,
  202.                 [PREFAB_MINICOPTER] = 0f,
  203.                 [PREFAB_SEDAN] = 0f,
  204.                 [PREFAB_TRANSPORTHELI] = 0f
  205.             };
  206.  
  207.             [JsonConverter(typeof(StringEnumConverter))]
  208.             [JsonProperty("DefaultState")]
  209.             public RadioController.States DefaultState = RadioController.States.OFF;
  210.  
  211.             public string ToJson() => JsonConvert.SerializeObject(this);
  212.  
  213.             public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
  214.         }
  215.  
  216.         private class Tone
  217.         {
  218.             public Tone(Notes aNote = Notes.A, NoteType aNoteType = NoteType.Regular, int anOctave = 4, float aDuration = 1f)
  219.             {
  220.                 Note = aNote;
  221.                 NoteType = aNoteType;
  222.                 Octave = anOctave;
  223.                 Duration = aDuration;
  224.             }
  225.  
  226.             [JsonConverter(typeof(StringEnumConverter))]
  227.             [JsonProperty("Note")]
  228.             public Notes Note;
  229.  
  230.             [JsonConverter(typeof(StringEnumConverter))]
  231.             [JsonProperty("NoteType")]
  232.             public NoteType NoteType;
  233.  
  234.             [JsonProperty("Octave")]
  235.             public int Octave;
  236.  
  237.             [JsonProperty("Duration")]
  238.             public float Duration;
  239.  
  240.             public string ToJson() => JsonConvert.SerializeObject(this);
  241.  
  242.             public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
  243.         }
  244.  
  245.         private class Radio
  246.         {
  247.             public Radio(string aName, Dictionary<string, Attachment[]> someModules, Dictionary<string, Attachment[]> someVehicles, params Tone[] someTones)
  248.             {
  249.                 Name = aName;
  250.                 Modules = someModules;
  251.                 Vehicles = someVehicles;
  252.                 Tones = someTones;
  253.             }
  254.  
  255.             [JsonProperty("Name")]
  256.             public string Name;
  257.  
  258.             [JsonProperty("Tones")]
  259.             public Tone[] Tones;
  260.  
  261.             [JsonProperty("Modules")]
  262.             public Dictionary<string, Attachment[]> Modules;
  263.  
  264.             [JsonProperty("Vehicles")]
  265.             public Dictionary<string, Attachment[]> Vehicles;
  266.  
  267.             public string ToJson() => JsonConvert.SerializeObject(this);
  268.  
  269.             public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
  270.         }
  271.  
  272.         private class Attachment
  273.         {
  274.             public Attachment(string aPrefab, Vector3 aPosition, Vector3 anAngle = new Vector3(), string aBone = null)
  275.             {
  276.                 Prefab = aPrefab;
  277.                 Position = aPosition;
  278.                 Angle = anAngle;
  279.                 Bone = aBone;
  280.             }
  281.  
  282.             [JsonProperty("Prefab")]
  283.             public string Prefab;
  284.  
  285.             [JsonProperty("Position")]
  286.             public Vector3 Position;
  287.  
  288.             [JsonProperty("Angle")]
  289.             public Vector3 Angle;
  290.  
  291.             [JsonProperty("Bone")]
  292.             public string Bone;
  293.  
  294.             public string ToJson() => JsonConvert.SerializeObject(this);
  295.  
  296.             public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
  297.         }
  298.  
  299.         protected override void LoadDefaultConfig()
  300.         {
  301.             config = new Configuration();
  302.             RadioDictionary.Clear();
  303.             RadioDictionary.Add(SIREN_DEFAULT.Name, SIREN_DEFAULT);
  304.             RadioDictionary.Add(SIREN_SILENT.Name, SIREN_SILENT);
  305.         }
  306.  
  307.         protected override void LoadConfig()
  308.         {
  309.             base.LoadConfig();
  310.             try
  311.             {
  312.                 config = Config.ReadObject<Configuration>();
  313.                 if (config == null)
  314.                 {
  315.                     throw new JsonException();
  316.                 }
  317.  
  318.                 try
  319.                 {
  320.                     foreach (string eachRadioFile in Interface.Oxide.DataFileSystem.GetFiles(DATAPATH_RADIO, "*.json"))
  321.                     {
  322.                         string theFilename = eachRadioFile.Basename(".json");
  323.                         try
  324.                         {
  325.                             Radio theRadio = Interface.Oxide.DataFileSystem.ReadObject<Radio>(DATAPATH_RADIO + theFilename);
  326.                             RadioDictionary.Add(theRadio.Name, theRadio);
  327.                         }
  328.                         catch
  329.                         {
  330.                             PrintWarning($"Radio file {theFilename}.json is invalid; ignoring");
  331.                         }
  332.                     }
  333.                 }
  334.                 catch
  335.                 {
  336.  
  337.                 }
  338.                 Puts("Loaded carradio: " + string.Join(", ", RadioDictionary.Keys));
  339.  
  340.                 if (RadioDictionary.IsEmpty())
  341.                 {
  342.                     PrintWarning("Configuration appears to be missing carradio; using defaults");
  343.                     RadioDictionary.Add(SIREN_DEFAULT.Name, SIREN_DEFAULT);
  344.                     RadioDictionary.Add(SIREN_SILENT.Name, SIREN_SILENT);
  345.                     SaveConfig();
  346.                 }
  347.  
  348.                 if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys))
  349.                 {
  350.                     PrintWarning("Configuration appears to be outdated; updating and saving");
  351.                     SaveConfig();
  352.                 }
  353.  
  354.             }
  355.             catch
  356.             {
  357.                 PrintWarning($"Configuration file {Name}.json is invalid; using defaults");
  358.                 LoadDefaultConfig();
  359.             }
  360.         }
  361.  
  362.         protected override void SaveConfig()
  363.         {
  364.             PrintWarning($"Configuration changes saved to {Name}.json");
  365.             Config.WriteObject(config, true);
  366.  
  367.             foreach (Radio eachRadio in RadioDictionary.Values)
  368.             {
  369.                 Interface.Oxide.DataFileSystem.WriteObject(DATAPATH_RADIO + eachRadio.Name, eachRadio);
  370.             }
  371.         }
  372.         #endregion configuration
  373.  
  374.         #region localization
  375.         protected override void LoadDefaultMessages()
  376.         {
  377.             lang.RegisterMessages(new Dictionary<string, string>
  378.             {
  379.                 [I18N_MISSING_SIREN] = "No radio was found for the given name (using {0} instead)",
  380.                 [I18N_COULD_NOT_ATTACH] = "Could not attach '{0}'",
  381.                 [I18N_ATTACHED] = "Attached radio '{0}'",
  382.                 [I18N_ATTACHED_GLOBAL] = "Attached radio '{0}' to all existing cars",
  383.                 [I18N_DETACHED] = "Detached radio",
  384.                 [I18N_DETACHED_GLOBAL] = "Detached all existing carradio",
  385.                 [I18N_NOT_A_VEHICLE] = "This entity is not a (supported) vehicle",
  386.                 [I18N_RADIO] = "Available carradio: {0}",
  387.                 [I18N_PLAYERS_ONLY] = "Command '{0}' can only be used by a player",
  388.                 [I18N_NOT_SUPPORTED] = "The radio '{0}' has no configuration for '{1}'"
  389.             }, this);
  390.         }
  391.         #endregion localization
  392.  
  393.         #region commands
  394.         [Command("attachradio"), Permission(PERMISSION_ATTACHRADIO)]
  395.         private void AttachCarRadios(IPlayer aPlayer, string aCommand, string[] someArgs)
  396.         {
  397.             if (aPlayer.IsServer)
  398.             {
  399.                 Message(aPlayer, I18N_PLAYERS_ONLY, aCommand);
  400.                 return;
  401.             }
  402.  
  403.             //BaseVehicle theVehicle = RaycastVehicle(aPlayer);
  404.             BasePlayer thePlayer = aPlayer.Object as BasePlayer;
  405.             BaseVehicle theVehicle = thePlayer?.GetMountedVehicle();
  406.             if (theVehicle)
  407.             {
  408.                 Radio theRadio = someArgs.Length > 0 ? FindRadioForName(someArgs[0], aPlayer) : RadioDictionary.Values.First();
  409.                 AttachRadios(theVehicle, theRadio, config.DefaultState, aPlayer);
  410.                 Message(aPlayer, I18N_ATTACHED, theRadio.Name);
  411.             }
  412.         }
  413.  
  414.         [Command("removeradio"), Permission(PERMISSION_DETACHRADIO)]
  415.         private void DetachCarRadios(IPlayer aPlayer, string aCommand, string[] someArgs)
  416.         {
  417.             if (aPlayer.IsServer)
  418.             {
  419.                 Message(aPlayer, I18N_PLAYERS_ONLY, aCommand);
  420.                 return;
  421.             }
  422.  
  423.             BaseVehicle theVehicle = RaycastVehicle(aPlayer);
  424.             if (theVehicle && DetachRadios(theVehicle))
  425.             {
  426.                 Message(aPlayer, I18N_DETACHED);
  427.             }
  428.         }
  429.  
  430.         [Command("attachallcarradio"), Permission(PERMISSION_ATTACHRADIO_GLOBAL)]
  431.         private void AttachAllCarRadios(IPlayer aPlayer, string aCommand, string[] someArgs)
  432.         {
  433.             Radio theRadio = someArgs.Length > 0 ? FindRadioForName(someArgs[0], aPlayer) : RadioDictionary.Values.First();
  434.             foreach (BaseVehicle eachVehicle in BaseNetworkable.serverEntities.OfType<BaseVehicle>())
  435.             {
  436.                 AttachRadios(eachVehicle, theRadio, config.DefaultState, aPlayer);
  437.             }
  438.             Message(aPlayer, I18N_ATTACHED_GLOBAL, theRadio.Name);
  439.         }
  440.  
  441.         [Command("detachallcarradio"), Permission(PERMISSION_DETACHRADIO_GLOBAL)]
  442.         private void DetachAllCarRadios(IPlayer aPlayer, string aCommand, string[] someArgs)
  443.         {
  444.             foreach (BaseVehicle eachVehicle in BaseNetworkable.serverEntities.OfType<BaseVehicle>())
  445.             {
  446.                 DetachRadios(eachVehicle);
  447.             }
  448.             Message(aPlayer, I18N_DETACHED_GLOBAL);
  449.         }
  450.  
  451.        
  452.  
  453.         [Command("togglecarradio")]
  454.         private void ToggleRadios(IPlayer aPlayer, string aCommand, string[] someArgs)
  455.         {
  456.             if (aPlayer.IsServer)
  457.             {
  458.                 Message(aPlayer, I18N_PLAYERS_ONLY, aCommand);
  459.                 return;
  460.             }
  461.  
  462.             BasePlayer thePlayer = aPlayer.Object as BasePlayer;
  463.             BaseVehicle theVehicle = thePlayer?.GetMountedVehicle();
  464.             if (theVehicle)
  465.             {
  466.                 theVehicle.GetComponent<RadioController>()?.ChangeState();
  467.             }
  468.             else if (!config.MountNeeded)
  469.             {
  470.                 RaycastVehicle(aPlayer)?.GetComponent<RadioController>()?.ChangeState(); ;
  471.             }
  472.         }
  473.         #endregion commands
  474.  
  475.         #region hooks
  476.         private void Unload()
  477.         {
  478.             OnServerSave();
  479.  
  480.             foreach (BaseVehicle eachVehicle in BaseNetworkable.serverEntities.OfType<BaseVehicle>())
  481.             {
  482.                 DetachRadios(eachVehicle);
  483.             }
  484.         }
  485.  
  486.         private void OnServerSave()
  487.         {
  488.             DataContainer thePersistentData = new DataContainer();
  489.             foreach (BaseVehicle eachCar in BaseNetworkable.serverEntities.OfType<BaseVehicle>())
  490.             {
  491.                 RadioController theController = eachCar.GetComponent<RadioController>();
  492.                 thePersistentData.VehicleRadioMap.Add(eachCar.net.ID, theController ? new VehicleContainer(theController.Radio.Name, theController.State, theController.NetIDs) : null);
  493.             }
  494.             Interface.Oxide.DataFileSystem.WriteObject(Name, thePersistentData);
  495.         }
  496.  
  497.         private void OnServerInitialized(bool anInitialFlag)
  498.         {
  499.             bool theSpawnRandomlyFlag = config.RadioSpawnProbability.Any(entry => entry.Value > 0f);
  500.             if (!theSpawnRandomlyFlag)
  501.             {
  502.                 Unsubscribe("OnEntitySpawned");
  503.             }
  504.  
  505.             // Reattach on server restart
  506.             DataContainer thePersistentData = Interface.Oxide.DataFileSystem.ReadObject<DataContainer>(Name);
  507.             foreach (BaseVehicle eachVehicle in BaseNetworkable.serverEntities.OfType<BaseVehicle>())
  508.             {
  509.                 VehicleContainer theContainer;
  510.                 if (thePersistentData.VehicleRadioMap.TryGetValue(eachVehicle.net.ID, out theContainer))
  511.                 {
  512.                     if (theContainer != null)
  513.                     {
  514.                         Radio theRadio;
  515.                         if (RadioDictionary.TryGetValue(theContainer.RadioName, out theRadio))
  516.                         {
  517.                             CreateRadioController(eachVehicle, theRadio, theContainer.NetIDs);
  518.                             AttachRadios(eachVehicle, theRadio, theContainer.State);
  519.                         }
  520.                         else
  521.                         {
  522.                             CreateRadioController(eachVehicle, null, theContainer.NetIDs);
  523.                             DetachRadios(eachVehicle);
  524.                             PrintWarning($"Missing radio for name \"{theContainer.RadioName}\". Ignoring...");
  525.                         }
  526.                     }
  527.                 }
  528.                 else if (theSpawnRandomlyFlag)
  529.                 {
  530.                     RadioController theController = eachVehicle.GetComponent<RadioController>();
  531.                     if (!theController)
  532.                     {
  533.                         float theProbability;
  534.                         if (config.RadioSpawnProbability.TryGetValue(eachVehicle is ModularCar ? KEY_MODULAR_CAR : eachVehicle.PrefabName, out theProbability) && Core.Random.Range(0f, 1f) < theProbability)
  535.                         {
  536.                             AttachRadios(eachVehicle, RadioDictionary.Values.First(), config.DefaultState);
  537.                         }
  538.                     }
  539.                 }
  540.             }
  541.         }
  542.  
  543.         private object OnButtonPress(PressButton aButton, BasePlayer aPlayer)
  544.         {
  545.             BaseVehicle theVehicle = aButton.GetComponentInParent<BaseVehicle>()?.VehicleParent();
  546.             // theVehicle = theVehicle ? theVehicle : aButton.GetComponentInParent<BaseVehicle>();
  547.             theVehicle = aPlayer?.GetMountedVehicle();
  548.             if (theVehicle)
  549.             {
  550.                 RadioController theController = theVehicle.GetComponent<RadioController>();
  551.  
  552.                 if (theController)
  553.                 {
  554.                     if ((config.MountNeeded && aPlayer.GetMountedVehicle() != theVehicle) || !theController.NetIDs.Contains(aButton.net.ID))
  555.                     {
  556.                         return false;
  557.                     }
  558.                     theController.ChangeState();
  559.                 }
  560.             }
  561.             return null;
  562.         }
  563.  
  564.         private void OnEntitySpawned(BaseVehicle aVehicle)
  565.         {
  566.             RadioController theController = aVehicle.GetComponent<RadioController>();
  567.             if (!theController)
  568.             {
  569.                 float theProbability;
  570.                 if (config.RadioSpawnProbability.TryGetValue(aVehicle is ModularCar ? KEY_MODULAR_CAR : aVehicle.PrefabName, out theProbability) && Core.Random.Range(0f, 1f) < theProbability)
  571.                 {
  572.                     AttachRadios(aVehicle, RadioDictionary.Values.First(), config.DefaultState);
  573.                 }
  574.             }
  575.         }
  576.         #endregion hooks
  577.  
  578.         #region methods
  579.         /// <summary>
  580.         /// Tries to attach the given radio to the vehicle, replacing any existing radio.
  581.         /// </summary>
  582.         /// <param name="aVehicle">The vehicle.</param>
  583.         /// <param name="aRadio">The radio.</param>
  584.         /// <param name="anInitialState">The initial radio state.</param>
  585.         /// <param name="aPlayer">The calling player.</param>
  586.         private void AttachRadios(BaseVehicle aVehicle, Radio aRadio, RadioController.States anInitialState, IPlayer aPlayer = null)
  587.         {
  588.             DetachRadios(aVehicle);
  589.             RadioController theController = CreateRadioController(aVehicle, aRadio);
  590.             if (aVehicle as ModularCar)
  591.             {
  592.                 if (aRadio.Modules == null)
  593.                 {
  594.                     Message(aPlayer, I18N_NOT_SUPPORTED, aRadio.Name, KEY_MODULAR_CAR);
  595.                     DetachRadios(aVehicle);
  596.                     return;
  597.                 }
  598.                 foreach (BaseVehicleModule eachModule in aVehicle.GetComponentsInChildren<BaseVehicleModule>())
  599.                 {
  600.                     SpawnAttachments(aRadio.Modules, aPlayer, theController, eachModule);
  601.                 }
  602.             }
  603.             else if (!SpawnAttachments(aRadio.Vehicles, aPlayer, theController, aVehicle))
  604.             {
  605.                 Message(aPlayer, I18N_NOT_SUPPORTED, aRadio.Name, aVehicle.PrefabName);
  606.                 DetachRadios(aVehicle);
  607.                 return;
  608.             }
  609.             theController.SetState(anInitialState);
  610.         }
  611.  
  612.         /// <summary>
  613.         /// Spawns the attachments for the given dictionary for the given parent entity.
  614.         /// </summary>
  615.         /// <param name="someAttachments">The dictionary.</param>
  616.         /// <param name="aPlayer">The calling player.</param>
  617.         /// <param name="theController">The RadioController of the Parent.</param>
  618.         /// <param name="aParent">The Parent.</param>
  619.         /// <returns>True, if the parent has an entry in the dictionary with at least one Attachment.</returns>
  620.         private bool SpawnAttachments(IDictionary<string, Attachment[]> someAttachments, IPlayer aPlayer, RadioController theController, BaseEntity aParent)
  621.         {
  622.             if (someAttachments == null)
  623.             {
  624.                 return false;
  625.             }
  626.  
  627.             Attachment[] theAttachments;
  628.             if (someAttachments.TryGetValue(aParent.PrefabName, out theAttachments))
  629.             {
  630.                 foreach (Attachment eachAttachment in theAttachments)
  631.                 {
  632.                     BaseEntity theNewEntity = AttachEntity(aParent, eachAttachment.Prefab, eachAttachment.Position, eachAttachment.Angle, eachAttachment.Bone);
  633.                     if (theNewEntity)
  634.                     {
  635.                         theController.NetIDs.Add(theNewEntity.net.ID);
  636.                     }
  637.                     else if (aPlayer != null)
  638.                     {
  639.                         Message(aPlayer, I18N_COULD_NOT_ATTACH, eachAttachment.Prefab);
  640.                     }
  641.                 }
  642.                 return !theAttachments.IsEmpty();
  643.             }
  644.             return false;
  645.         }
  646.  
  647.         /// <summary>
  648.         /// Creates or replaces the RadioController of the given vehicle.
  649.         /// </summary>
  650.         /// <param name="aVehicle">The vehicle.</param>
  651.         /// <param name="aRadio">The Radio.</param>
  652.         /// <param name="someNetIDs">Already existing radio entities.</param>
  653.         /// <returns>The newly created RadioController.</returns>
  654.         private RadioController CreateRadioController(BaseVehicle aVehicle, Radio aRadio, IEnumerable<uint> someNetIDs = null)
  655.         {
  656.             RadioController theController = aVehicle.GetComponent<RadioController>();
  657.             if (theController)
  658.             {
  659.                 UnityEngine.Object.DestroyImmediate(theController);
  660.             }
  661.             theController = aVehicle.gameObject.AddComponent<RadioController>();
  662.             theController.Config = config;
  663.             theController.Radio = aRadio;
  664.             if (someNetIDs != null)
  665.             {
  666.                 theController.NetIDs.UnionWith(someNetIDs);
  667.             }
  668.             return theController;
  669.         }
  670.  
  671.         /// <summary>
  672.         /// Detaches the radio from a vehicle and removes all corresponding entities.
  673.         /// </summary>
  674.         /// <param name="aVehicle"> The vehicle.</param>
  675.         /// <returns>True, if a radio was removed.</returns>
  676.         private bool DetachRadios(BaseVehicle aVehicle)
  677.         {
  678.             RadioController theController = aVehicle.GetComponent<RadioController>();
  679.             if (theController)
  680.             {
  681.                 foreach (BaseEntity eachEntity in aVehicle.GetComponentsInChildren<BaseEntity>())
  682.                 {
  683.                     if (theController.NetIDs.Contains(eachEntity.net.ID))
  684.                     {
  685.                         Destroy(eachEntity);
  686.                     }
  687.                 }
  688.                 UnityEngine.Object.DestroyImmediate(theController);
  689.                 return true;
  690.             }
  691.             return false;
  692.         }
  693.  
  694.         /// <summary>
  695.         /// Destroys the entity.
  696.         /// </summary>
  697.         /// <param name="anEntity">The entity.</param>
  698.         private static void Destroy(BaseEntity anEntity)
  699.         {
  700.             if (!anEntity.IsDestroyed)
  701.             {
  702.                 anEntity.Kill();
  703.             }
  704.         }
  705.  
  706.         /// <summary>
  707.         /// Attaches the prefab entity at the given local position and angles to the parent.
  708.         /// </summary>
  709.         /// <param name="aParent">The parent.</param>
  710.         /// <param name="aPrefab">The prefab for the new entity.</param>
  711.         /// <param name="aPosition">The local position.</param>
  712.         /// <param name="anAngle">The local angles.</param>
  713.         /// <returns></returns>
  714.         private BaseEntity AttachEntity(BaseEntity aParent, string aPrefab, Vector3 aPosition, Vector3 anAngle = new Vector3(), string aBone = null)
  715.         {
  716.             BaseEntity theNewEntity = GameManager.server.CreateEntity(aPrefab, aParent.transform.position);
  717. Puts($"Attaching {aPrefab} at loction {aPosition}");
  718.  
  719.             if (!theNewEntity)
  720.             {
  721.                 return null;
  722.             }
  723.  
  724.             theNewEntity.Spawn();
  725.             Transform theBone = aParent.FindBone(aBone);
  726.             if (theBone == null && aBone != null)
  727.             {
  728.                 PrintWarning($"No bone found for name '{aBone}'");
  729.                 PrintWarning("Valid bone names: " + string.Join(", ", aParent.GetBones().Select(eachBone => eachBone.name)));
  730.             }
  731.  
  732.             if (theBone != null && theBone != aParent.transform)
  733.             {
  734.                 theNewEntity.SetParent(aParent, theBone.name);
  735.                 theNewEntity.transform.localPosition = theBone.InverseTransformPoint(aParent.transform.TransformPoint(aPosition));
  736.                 theNewEntity.transform.localRotation = Quaternion.Inverse(theBone.rotation) * (aParent.transform.rotation * Quaternion.Euler(anAngle));
  737.             }
  738.             else
  739.             {
  740.                 theNewEntity.transform.localPosition = aPosition;
  741.                 theNewEntity.transform.localEulerAngles = anAngle;
  742.                 theNewEntity.SetParent(aParent);
  743.             }
  744.             //Puts(theNewEntity.ShortPrefabName + ": (" + theNewEntity.GetComponents<Component>().Length + ") " + string.Join(", ", theNewEntity.GetComponents<Component>().Select(eachComp => eachComp.GetType().Name)));
  745.             UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent<DestroyOnGroundMissing>());
  746.             UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent<GroundWatch>());
  747.             UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent<BoxCollider>());
  748.             UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent<InstrumentKeyController>());
  749.             theNewEntity.OwnerID = 0;
  750.             BaseCombatEntity theCombatEntity = theNewEntity as BaseCombatEntity;
  751.             if (theCombatEntity)
  752.             {
  753.                 theCombatEntity.pickup.enabled = false;
  754.             }
  755.             PressButton theButton = theNewEntity as PressButton;
  756.             if (theButton)
  757.             {
  758.                 theButton.pressDuration = 0.2f;
  759.             }
  760.  
  761.             theNewEntity.EnableSaving(true);
  762.             theNewEntity.SendNetworkUpdateImmediate();
  763.             return theNewEntity;
  764.         }
  765.  
  766.         /// <summary>
  767.         /// Toggles the IOEntity.
  768.         /// </summary>
  769.         /// <param name="anIOEntity">The IOEntity.</param>
  770.         /// <param name="theEnabledFlag">The new state.</param>
  771.         private static void ToogleRadios(IOEntity anIOEntity, bool theEnabledFlag)
  772.         {
  773.             anIOEntity.UpdateHasPower(theEnabledFlag ? anIOEntity.ConsumptionAmount() : 0, 0);
  774.             anIOEntity.SetFlag(BaseEntity.Flags.On, theEnabledFlag);
  775.         }
  776.         #endregion methods
  777.  
  778.         #region helpers
  779.         private BaseVehicle RaycastVehicle(IPlayer aPlayer)
  780.         {
  781.             RaycastHit theHit;
  782.             if (!Physics.Raycast((aPlayer.Object as BasePlayer).eyes.HeadRay(), out theHit, 5f))
  783.             {
  784.                 return null;
  785.             }
  786.  
  787.             BaseVehicle theVehicle = theHit.GetEntity()?.GetComponentInParent<BaseVehicle>();
  788.             if (!theVehicle)
  789.             {
  790.                 Message(aPlayer, I18N_NOT_A_VEHICLE);
  791.             }
  792.             return theVehicle;
  793.         }
  794.  
  795.         private Radio FindRadioForName(string aName, IPlayer aPlayer)
  796.         {
  797.             Radio theRadio;
  798.             if (!RadioDictionary.TryGetValue(aName, out theRadio))
  799.             {
  800.                 theRadio = RadioDictionary.Values.First();
  801.                 Message(aPlayer, I18N_MISSING_SIREN, theRadio.Name);
  802.             }
  803.             return theRadio;
  804.         }
  805.  
  806.         private string GetText(string aKey, string aPlayerId = null, params object[] someArgs) => string.Format(lang.GetMessage(aKey, this, aPlayerId), someArgs);
  807.  
  808.         private void Message(IPlayer aPlayer, string anI18nKey, params object[] someArgs)
  809.         {
  810.             if (aPlayer.IsConnected)
  811.             {
  812.                 string theText = GetText(anI18nKey, aPlayer.Id, someArgs);
  813.                 aPlayer.Reply(theText != anI18nKey ? theText : anI18nKey);
  814.             }
  815.         }
  816.  
  817.         private void Message(BasePlayer aPlayer, string anI18nKey, params object[] someArgs)
  818.         {
  819.             if (aPlayer.IsConnected)
  820.             {
  821.                 string theText = GetText(anI18nKey, aPlayer.UserIDString, someArgs);
  822.                 aPlayer.ChatMessage(theText != anI18nKey ? theText : anI18nKey);
  823.             }
  824.         }
  825.         #endregion helpers
  826.  
  827.         #region controllers
  828.         private class RadioController : FacepunchBehaviour
  829.         {
  830.             public enum States
  831.             {
  832.                 OFF,
  833.                 ON,
  834.                 LIGHTS_ONLY
  835.             }
  836.  
  837.             private BaseVehicle vehicle;
  838.             private InstrumentTool trumpet;
  839.             public Configuration Config { get; set; }
  840.             public States State { get; private set; }
  841.             public Radio Radio { get; set; }
  842.             public ISet<uint> NetIDs { get; } = new HashSet<uint>();
  843.  
  844.             public States ChangeState()
  845.             {
  846.                 SetState(State >= States.LIGHTS_ONLY ? States.OFF : State + 1);
  847.                 return State;
  848.             }
  849.  
  850.             public void SetState(States aState)
  851.             {
  852.                 State = aState;
  853.                 if ((!Config.SoundEnabled || Radio?.Tones?.Length < 1 || !GetTrumpet()) && State == States.ON)
  854.                 {
  855.                     State++;
  856.                 }
  857.                 RefreshRadioState();
  858.             }
  859.  
  860.             public void RefreshRadioState()
  861.             {
  862.                 if (State == States.ON)
  863.                 {
  864.                     PlayTone(0);
  865.                 }
  866.                 bool theLightsOnFlag = State > States.OFF;
  867.                 foreach (IOEntity eachEntity in GetVehicle().GetComponentsInChildren<IOEntity>())
  868.                 {
  869.                     if (NetIDs.Contains(eachEntity.net.ID) && !(eachEntity is PressButton))
  870.                     {
  871.                         ToogleRadios(eachEntity, theLightsOnFlag);
  872.                     }
  873.                 }
  874.             }
  875.  
  876.             private InstrumentTool GetTrumpet()
  877.             {
  878.                 if (trumpet == null || trumpet.IsDestroyed)
  879.                 {
  880.                     trumpet = GetVehicle().GetComponentInChildren<InstrumentTool>();
  881.                 }
  882.                 return trumpet;
  883.             }
  884.  
  885.             private BaseVehicle GetVehicle()
  886.             {
  887.                 if (vehicle == null)
  888.                 {
  889.                     vehicle = GetComponentInParent<BaseVehicle>();
  890.                 }
  891.                 return vehicle;
  892.             }
  893.  
  894.             private void PlayTone(int anIndex)
  895.             {
  896.                 if (State != States.ON || !GetTrumpet())
  897.                 {
  898.                     return;
  899.                 }
  900.                 if (anIndex >= Radio.Tones.Length)
  901.                 {
  902.                     anIndex = 0;
  903.                 }
  904.                 Tone theTone = Radio.Tones[anIndex];
  905.                 GetTrumpet().ClientRPC(null, "Client_PlayNote", (int)theTone.Note, (int)theTone.NoteType, theTone.Octave, 1f);
  906.                 Invoke(() => GetTrumpet().ClientRPC(null, "Client_StopNote", (int)theTone.Note, (int)theTone.NoteType, theTone.Octave), theTone.Duration);
  907.                 Invoke(() => PlayTone(++anIndex), theTone.Duration);
  908.             }
  909.         }
  910.         #endregion controllers
  911.     }
  912. }
  913.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement