C#:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("CargoShipRadiation", "emp77", "1.2.1")]
[Description("Adds configurable numeric radiation to cargo ships")]
public class CargoShipRadiation : RustPlugin
{
private const string CargoShipPrefab = "assets/content/vehicles/boats/cargoship/cargoshiptest.prefab";
private const string TriggerName = "CargoShipRadiationTrigger";
private const int PlayerMask = 131072;
private const float DefaultRadiationLevel = 20f;
// Preserve the existing zone's dimensions in cargo ship local coordinates.
private static readonly Vector3 RadiationZonePosition = new Vector3(0f, 18f, 10f);
private static readonly Vector3 RadiationZoneSize = new Vector3(40f, 40f, 150f);
private readonly Dictionary<CargoShip, GameObject> _triggers = new Dictionary<CargoShip, GameObject>();
private Configuration _config = new Configuration();
#region Configuration
private class Configuration
{
[JsonProperty("Radiation Level")]
public float RadiationLevel = DefaultRadiationLevel;
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
JObject json = Config.ReadObject<JObject>();
if (json == null)
throw new JsonSerializationException("The configuration must be a JSON object.");
JToken level = json["Radiation Level"];
if (level != null && level.Type != JTokenType.Integer && level.Type != JTokenType.Float)
throw new JsonSerializationException("Radiation Level must be a JSON number.");
_config = json.ToObject<Configuration>();
float radiation = _config.RadiationLevel;
if (float.IsNaN(radiation) || float.IsInfinity(radiation) || radiation < 0f)
throw new JsonSerializationException("Radiation Level must be a finite, non-negative float.");
}
catch (Exception exception)
{
PrintError($"Cannot load radiation configuration: {exception.Message} Fix the config and reload the plugin.");
throw; // Keep the invalid file intact instead of silently replacing it.
}
SaveConfig(); // Write only the current configuration fields.
}
protected override void LoadDefaultConfig() => _config = new Configuration();
protected override void SaveConfig() => Config.WriteObject(_config);
#endregion
private void OnServerInitialized()
{
// One scan covers ships already present when the plugin loads/reloads.
foreach (BaseNetworkable networkable in BaseNetworkable.serverEntities)
TryAttach(networkable as CargoShip);
}
private void OnEntitySpawned(BaseNetworkable networkable)
{
TryAttach(networkable as CargoShip);
}
private void OnEntityKill(BaseNetworkable networkable)
{
CargoShip ship = networkable as CargoShip;
GameObject triggerObject;
if (ship != null && _triggers.TryGetValue(ship, out triggerObject))
{
_triggers.Remove(ship);
DestroyTrigger(triggerObject);
}
}
private void Unload()
{
foreach (GameObject triggerObject in _triggers.Values)
DestroyTrigger(triggerObject);
_triggers.Clear();
}
private void TryAttach(CargoShip ship)
{
if (ship == null || ship.IsDestroyed || !ship.isServer || ship.PrefabName != CargoShipPrefab)
return;
GameObject existing;
if (_triggers.TryGetValue(ship, out existing) && existing != null)
return;
// Version 1.1.0 left its GameObject behind on unload. Remove only this
// plugin's named direct children, including any old duplicate triggers.
for (int i = ship.transform.childCount - 1; i >= 0; i--)
{
Transform child = ship.transform.GetChild(i);
if (child.name == TriggerName && child.gameObject != ship.radiation &&
child.GetComponent<TriggerRadiation>() != null)
DestroyTrigger(child.gameObject);
}
GameObject triggerObject = new GameObject(TriggerName);
try
{
// Configure before activation so physics never observes a default tier.
triggerObject.SetActive(false);
triggerObject.layer = 18; // Trigger layer, as in the original plugin.
// A direct child follows translation/rotation without a timer or a
// network entity. Keep it OUTSIDE ship.radiation: UpdateRadiation()
// overwrites all TriggerRadiation overrides below that vanilla object.
triggerObject.transform.SetParent(ship.transform, false);
triggerObject.transform.localPosition = RadiationZonePosition;
triggerObject.transform.localRotation = Quaternion.identity;
triggerObject.transform.localScale = Vector3.one;
BoxCollider box = triggerObject.AddComponent<BoxCollider>();
box.isTrigger = true;
box.size = RadiationZoneSize;
box.center = Vector3.zero;
TriggerRadiation trigger = triggerObject.AddComponent<TriggerRadiation>();
trigger.InterestLayers = PlayerMask;
// Rust ignores overrides <= 0 and falls back to the tier. NONE is
// required for a configured zero to mean zero, rather than LOW.
trigger.radiationTier = Radiation.Tier.NONE;
trigger.RadiationAmountOverride = _config.RadiationLevel;
trigger.falloff = 0.1f; // Preserve native edge falloff and armor protection.
trigger.enabled = true;
_triggers[ship] = triggerObject;
triggerObject.SetActive(true);
}
catch
{
_triggers.Remove(ship);
DestroyTrigger(triggerObject);
throw;
}
}
private static void DestroyTrigger(GameObject triggerObject)
{
if (triggerObject == null)
return;
// TriggerBase.OnDisable removes occupants from their entity trigger lists
// immediately; Destroy then disposes the whole object and both components.
triggerObject.SetActive(false);
UnityEngine.Object.Destroy(triggerObject);
}
}
}