Just curious if the attack helicopter will be supported.
Attack Helicopter?
Mod 1 car not working at the moment....
+1 for attack helicopter support please
i made a patch for the newer vehicles and fixed the problem with the modular vehicles where it blocks mounting if you try attaching it to a vehicle module in the 2nd module slot
but im not sure when it will be accepted so i guess il just drop it here for now if anyone wants it.
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.68")]
[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<string, Attachment[]>
{
[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<string, Attachment[]>
{
[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<string, Attachment[]>
{
//Erased Dictionary to remove bulk since it was a duplicate of the above
},
new Dictionary<string, Attachment[]>
{
//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<ulong, VehicleContainer> VehicleRadioMap = new Dictionary<ulong, VehicleContainer>();
}
private class VehicleContainer
{
public string RadioName = SIREN_DEFAULT.Name;
public RadioController.States State = RadioController.States.OFF;
public HashSet<ulong> NetIDs = new HashSet<ulong>();
public VehicleContainer()
{
}
public VehicleContainer(string aRadioName, RadioController.States aState, IEnumerable<ulong> someNetIDs)
{
RadioName = aRadioName;
State = aState;
NetIDs.UnionWith(someNetIDs);
}
}
#endregion data
#region configuration
private Configuration config;
private IDictionary<string, Radio> RadioDictionary { get; } = new Dictionary<string, Radio>();
private class Configuration
{
[JsonProperty("MountNeeded")]
public bool MountNeeded = true;
[JsonProperty("SoundEnabled")]
public bool SoundEnabled = true;
[JsonProperty("RadioSpawnProbability")]
public Dictionary<string, float> RadioSpawnProbability = new Dictionary<string, float>
{
[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<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(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<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
}
private class Radio
{
public Radio(string aName, Dictionary<string, Attachment[]> someModules, Dictionary<string, Attachment[]> 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<string, Attachment[]> Modules;
[JsonProperty("Vehicles")]
public Dictionary<string, Attachment[]> Vehicles;
public string ToJson() => JsonConvert.SerializeObject(this);
public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(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<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(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<Configuration>();
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<Radio>(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<string, string>
{
[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<BaseVehicle>())
{
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<BaseVehicle>())
{
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<RadioController>()?.ChangeState();
}
else if (!config.MountNeeded)
{
RaycastVehicle(aPlayer)?.GetComponent<RadioController>()?.ChangeState(); ;
}
}
#endregion commands
#region hooks
private void Unload()
{
OnServerSave();
foreach (BaseVehicle eachVehicle in BaseNetworkable.serverEntities.OfType<BaseVehicle>())
{
DetachRadios(eachVehicle);
}
}
private void OnServerSave()
{
DataContainer thePersistentData = new DataContainer();
foreach (BaseVehicle eachCar in BaseNetworkable.serverEntities.OfType<BaseVehicle>())
{
RadioController theController = eachCar.GetComponent<RadioController>();
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<DataContainer>(Name);
foreach (BaseVehicle eachVehicle in BaseNetworkable.serverEntities.OfType<BaseVehicle>())
{
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<RadioController>();
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<BaseVehicle>()?.VehicleParent();
theVehicle = theVehicle ? theVehicle : aButton.GetComponentInParent<BaseVehicle>();
theVehicle = aPlayer?.GetMountedVehicle();
if (theVehicle)
{
RadioController theController = theVehicle.GetComponent<RadioController>();
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<RadioController>();
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
/// <summary>
/// Tries to attach the given radio to the vehicle, replacing any existing radio.
/// </summary>
/// <param name="aVehicle">The vehicle.</param>
/// <param name="aRadio">The radio.</param>
/// <param name="anInitialState">The initial radio state.</param>
/// <param name="aPlayer">The calling player.</param>
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<BaseVehicleModule>())
{
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);
}
/// <summary>
/// Spawns the attachments for the given dictionary for the given parent entity.
/// </summary>
/// <param name="someAttachments">The dictionary.</param>
/// <param name="aPlayer">The calling player.</param>
/// <param name="theController">The RadioController of the Parent.</param>
/// <param name="aParent">The Parent.</param>
/// <returns>True, if the parent has an entry in the dictionary with at least one Attachment.</returns>
private bool SpawnAttachments(IDictionary<string, Attachment[]> 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;
}
/// <summary>
/// Creates or replaces the RadioController of the given vehicle.
/// </summary>
/// <param name="aVehicle">The vehicle.</param>
/// <param name="aRadio">The Radio.</param>
/// <param name="someNetIDs">Already existing radio entities.</param>
/// <returns>The newly created RadioController.</returns>
private RadioController CreateRadioController(BaseVehicle aVehicle, Radio aRadio, IEnumerable<ulong> someNetIDs = null)
{
RadioController theController = aVehicle.GetComponent<RadioController>();
if (theController)
{
UnityEngine.Object.DestroyImmediate(theController);
}
theController = aVehicle.gameObject.AddComponent<RadioController>();
theController.Config = config;
theController.Radio = aRadio;
if (someNetIDs != null)
{
theController.NetIDs.UnionWith(someNetIDs);
}
return theController;
}
/// <summary>
/// Detaches the radio from a vehicle and removes all corresponding entities.
/// </summary>
/// <param name="aVehicle"> The vehicle.</param>
/// <returns>True, if a radio was removed.</returns>
private bool DetachRadios(BaseVehicle aVehicle)
{
RadioController theController = aVehicle.GetComponent<RadioController>();
if (theController)
{
foreach (BaseEntity eachEntity in aVehicle.GetComponentsInChildren<BaseEntity>())
{
if (theController.NetIDs.Contains(eachEntity.net.ID.Value))
{
Destroy(eachEntity);
}
}
UnityEngine.Object.DestroyImmediate(theController);
return true;
}
return false;
}
/// <summary>
/// Destroys the entity.
/// </summary>
/// <param name="anEntity">The entity.</param>
private static void Destroy(BaseEntity anEntity)
{
if (!anEntity.IsDestroyed)
{
anEntity.Kill();
}
}
/// <summary>
/// Attaches the prefab entity at the given local position and angles to the parent.
/// </summary>
/// <param name="aParent">The parent.</param>
/// <param name="aPrefab">The prefab for the new entity.</param>
/// <param name="aPosition">The local position.</param>
/// <param name="anAngle">The local angles.</param>
/// <returns></returns>
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<Component>().Length + ") " + string.Join(", ", theNewEntity.GetComponents<Component>().Select(eachComp => eachComp.GetType().Name)));
UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent<DestroyOnGroundMissing>());
UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent<GroundWatch>());
UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent<BoxCollider>());
UnityEngine.Object.DestroyImmediate(theNewEntity.GetComponent<InstrumentKeyController>());
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;
}
/// <summary>
/// Toggles the IOEntity.
/// </summary>
/// <param name="anIOEntity">The IOEntity.</param>
/// <param name="theEnabledFlag">The new state.</param>
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<BaseVehicle>();
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<ulong> NetIDs { get; } = new HashSet<ulong>();
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<IOEntity>())
{
//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<InstrumentTool>();
}
return trumpet;
}
private BaseVehicle GetVehicle()
{
if (vehicle == null)
{
vehicle = GetComponentInParent<BaseVehicle>();
}
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
}
} thanks man.
it's loaded fine, let's see how is it working.
csaszykjthanks man.
it's loaded fine, let's see how is it working.
i did remove the push button functionality at line 901 but its fairly easy to re-enable if you refer to the text in line 900, but personally i think they work better without the button cause you can toggle the radio from outside the vehicle now