C#:
using System;
using System.Collections.Generic;
using UnityEngine;
using Oxide.Core.Plugins;
using System.Linq;
using Oxide.Core;
namespace Oxide.Plugins {
[Info("CargoTrainEvent", "Sempai#3239", VERSION)]
[Description("Just like Cargo Ships - but with trains")]
public class CargoTrainEvent : RustPlugin {
private static CargoTrainEvent Instance;
private static Timer EventTimer;
[PluginReference]
private Plugin Kits, FuelManager, TruePVE, DeathNotes, GUIAnnouncements;
#region CONST & STATIC
public const string VERSION = "1.0.11";
public const ulong INTERNAL_OWNERID = 1337422;
public const string PREFAB_DRIVER = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_cargo_turret_any.prefab";
public const string PREFAB_SCIENTIST = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_cargo_turret_any.prefab";
public const string PREFAB_TRAIN = "assets/content/vehicles/workcart/workcart.entity.prefab";
public const string PREFAB_CRATE_LOCKED = "assets/prefabs/deployable/chinooklockedcrate/codelockedhackablecrate.prefab";
public const string PREFAB_CRATE_ELITE = "assets/bundled/prefabs/radtown/crate_elite.prefab";
public const string PREFAB_CRATE_BRADLEY = "assets/prefabs/npc/m2bradley/bradley_crate.prefab";
public const string PREFAB_CRATE_HELI = "assets/prefabs/npc/patrol helicopter/heli_crate.prefab";
public const string PREFAB_CRATE_MARKER = "assets/prefabs/tools/map/cratemarker.prefab";
public const string PREFAB_GENERIC_MARKER = "assets/prefabs/tools/map/genericradiusmarker.prefab";
public const string PREFAB_RUG = "assets/prefabs/deployable/rug/rug.deployed.prefab";
public const string PREFAB_SIRENLIGHT = "assets/prefabs/deployable/playerioents/lights/sirenlight/electric.sirenlight.deployed.prefab";
public const string PREFAB_COUNTER = "assets/prefabs/deployable/playerioents/counter/counter.prefab";
public const string PREFAB_ALARM = "assets/prefabs/deployable/playerioents/alarms/audioalarm.prefab";
public const string PREFAB_CCTV = "assets/prefabs/deployable/cctvcamera/cctv_deployed.prefab";
public const string FX_C4_EXPLOSION = "assets/prefabs/tools/c4/effects/c4_explosion.prefab";
public const string FX_EXPLOSION_01 = "assets/bundled/prefabs/fx/explosions/explosion_01.prefab";
public const string FX_EXPLOSION_02 = "assets/bundled/prefabs/fx/explosions/explosion_02.prefab";
public const string FX_EXPLOSION_03 = "assets/bundled/prefabs/fx/explosions/explosion_03.prefab";
public const ulong SKIN_SPLAT1 = 2451067899;
public const int ITEM_SNOWBALL = -363689972;
public const int ITEM_SUIT_HEAVY = -1772746857;
public const int ITEM_LOWGRADE = -946369541;
#endregion
#region HOOKS
private void Init() {
Instance = null;
}
private void OnServerInitialized() {
PrintWarning("\n-----------------------------\n" + " Author - Sempai#3239\n" + " VK - https://vk.com/rustnastroika/n" + " Forum - https://whiteplugins.ru/n" + " Discord - https://discord.gg/5DPTsRmd3G/n" + "-----------------------------");
Instance = this;
lang.RegisterMessages(LangMessages, this);
permission.RegisterPermission(PERM_ADMIN, this);
LoadConfigData();
ProcessConfigData();
ConfigValues = new Dictionary<string, InteractiveConfigValue>();
GenerateAllInteractiveConfigValues();
SpecialTrain.TrainDriverNetIDToSpecialTrain = new Dictionary<uint, SpecialTrain>();
SpecialTrain.TrainNetIDToSpecialTrain = new Dictionary<uint, SpecialTrain>();
SpecialTrain.TrainNetIDToTrainEngine = new Dictionary<uint, TrainEngine>();
SpecialTrain.WorkcartEntityNetIDToSpecialTrain = new Dictionary<uint, SpecialTrain>();
SpecialTrain.PlatformTriggerToTrain = new Dictionary<TriggerBase, SpecialTrain>();
SpecialTrain.UserIDToSpecialTrains = new Dictionary<ulong, List<SpecialTrain>>();
//pre-cleanup
foreach (var entity in BaseNetworkable.FindObjectsOfType<BaseEntity>().Where(e => e.OwnerID == INTERNAL_OWNERID)) {
entity.Kill(BaseNetworkable.DestroyMode.None);
}
foreach (var train in UnityEngine.Object.FindObjectsOfType<TrainEngine>()) {
OnEntitySpawned(train);
}
//broken from December 2021 forced wipe
//FuelManagerCheck();
TruePVECheck();
DeathNotesCheck();
ScheduleTrainEventAtRandom();
}
private void OnBookmarkControlStarted(ComputerStation station, BasePlayer player, string text, CCTV_RC cctv) {
if (cctv == null) return;
if (cctv.OwnerID != INTERNAL_OWNERID) return;
var group = Network.Net.sv.visibility.GetGroup(cctv.transform.position);
player.net.SwitchSecondaryGroup(group);
Interface.CallHook("OnBookmarkControlStartedTrainCCTV", player, text, cctv);
}
private void OnEntitySpawned(TrainEngine train) {
if (Instance == null) return;
SpecialTrain.TrainNetIDToTrainEngine.Add(train.net.ID, train);
train.SetFlag(TrainEngine.Flag_AltColor, false);
train.SendNetworkUpdateImmediate();
}
private void OnCrateHack(HackableLockedCrate crate) {
if (Instance == null) return;
if (crate.OwnerID != INTERNAL_OWNERID) return;
if (!SpecialTrain.WorkcartEntityNetIDToSpecialTrain.ContainsKey(crate.net.ID)) return;
SpecialTrain.WorkcartEntityNetIDToSpecialTrain[crate.net.ID].OnCrateHack(crate);
Interface.CallHook("OnTrainCrateHack", SpecialTrain.WorkcartEntityNetIDToSpecialTrain[crate.net.ID].Train, crate);
}
object OnCounterModeToggle(PowerCounter counter, BasePlayer player, bool wants) {
if (Instance == null) return null;
if (counter.OwnerID != INTERNAL_OWNERID) return null;
return false;
}
private object CanBeWounded(BasePlayer player) {
if (Instance == null) return null;
if (!SpecialTrain.TrainDriverNetIDToSpecialTrain.ContainsKey(player.net.ID)) {
return null;
}
//drivers can't be wounded
return false;
}
private object OnTurretTarget(AutoTurret turret, ScientistNPC target) {
if (Instance == null) return null;
if (turret == null) return null;
if (turret.net == null) return null;
if (target == null) return null;
if (target.net == null) return null;
if (SpecialTrain.WorkcartEntityNetIDToSpecialTrain.ContainsKey(target.net.ID)) {
return false;
}
return null;
}
private object CanBradleyApcTarget(BradleyAPC bradley, ScientistNPC target) {
if (Instance == null) return null;
if (bradley == null) return null;
if (target == null) return null;
if (target.net == null) return null;
if (bradley.net == null) return null;
if (SpecialTrain.WorkcartEntityNetIDToSpecialTrain.ContainsKey(target.net.ID)) {
return false;
}
return null;
}
private object CanHelicopterTarget(PatrolHelicopterAI heli, BasePlayer target) {
if (Instance == null) return null;
if (heli == null) return null;
if (target == null) return null;
if (target.net == null) return null;
if (SpecialTrain.WorkcartEntityNetIDToSpecialTrain.ContainsKey(target.net.ID)) {
return false;
}
if (SpecialTrain.TrainDriverNetIDToSpecialTrain.ContainsKey(target.net.ID)) {
return false;
}
return null;
}
private object OnNpcTarget(BaseNpc attacker, BasePlayer target) {
if (Instance == null) return null;
if (attacker == null) return null;
if (target == null) return null;
if (target.net == null) return null;
if (attacker.net == null) return null;
//Drivers will never be targets nor target anything and we can skip a lot of logic
if (SpecialTrain.TrainDriverNetIDToSpecialTrain.ContainsKey(target.net.ID)) {
return true;
}
if (SpecialTrain.TrainDriverNetIDToSpecialTrain.ContainsKey(attacker.net.ID)) {
return true;
}
//attacker and target might or might not belong to the train
bool attackerIsSpecial = SpecialTrain.WorkcartEntityNetIDToSpecialTrain.ContainsKey(attacker.net.ID) || SpecialTrain.TrainDriverNetIDToSpecialTrain.ContainsKey(attacker.net.ID);
bool targetIsSpecial = SpecialTrain.WorkcartEntityNetIDToSpecialTrain.ContainsKey(target.net.ID) || SpecialTrain.TrainDriverNetIDToSpecialTrain.ContainsKey(target.net.ID);
if (attackerIsSpecial && targetIsSpecial) {
//specials don't target specials
return true;
}
//none of them are special? outside of the scope of the plugin, then
if (!(attackerIsSpecial || targetIsSpecial)) {
return null;
}
bool targetIsAlivePlayer = false;
var alivePlayer = target as BasePlayer;
if (alivePlayer != null) {
targetIsAlivePlayer = !alivePlayer.IsNpc;
}
//at this point, the attacker can be special, the target can be special or the target is aliveplayer
//second, who is the target? a tunnel dweller? then don't targe
if (targetIsAlivePlayer) {
//default rules
return null;
}
else if (attackerIsSpecial) {
//is the target a tunnel dweller?
//not a special target, so let's see...if (!targetIsSpecial)
{
if (target.PrefabName.Contains("tunneldweller")) {
//let the targetting through
if (configData.ScientistsTargetTunnelDwellers) {
//default targetting rules
return null;
}
else {
return true;
}
}
else //nope it's not a tunnel dweller, so default targetting rules apply
{
return null;
}
}
else //special attacker AND special target, so don't let that happen
{
return true;
}
}
else //not a special attacker
{
if (targetIsSpecial) {
//something non special is trying to target something special.//don't let it through
return true;
}
}
//anything else: default targetting rules apply
return null;
}
private void OnEntityKill(BaseEntity entity) {
if (Instance == null) return;
if (entity == null) return;
if (entity.net == null) return;
if (!SpecialTrain.WorkcartEntityNetIDToSpecialTrain.ContainsKey(entity.net.ID)) return;
//remove the entity from your stuff
SpecialTrain.WorkcartEntityNetIDToSpecialTrain[entity.net.ID].RemoveWorkcartEntity(entity.net.ID);
}
private void OnEntityKill(TrainEngine train) {
if (Instance == null) return;
SpecialTrain.TrainNetIDToTrainEngine.Remove(train.net.ID);
if (IsTrainSpecial(train.net.ID)) {
MakeTrainNormal(train.net.ID);
}
}
private void Unload() {
if (Instance == null) return;
foreach (var specialTrain in SpecialTrain.TrainNetIDToSpecialTrain.Values.ToList()) {
UnityEngine.Object.DestroyImmediate(specialTrain);
}
SpecialTrain.TrainDriverNetIDToSpecialTrain = null;
SpecialTrain.TrainNetIDToSpecialTrain = null;
SpecialTrain.TrainNetIDToTrainEngine = null;
SpecialTrain.WorkcartEntityNetIDToSpecialTrain = null;
SpecialTrain.PlatformTriggerToTrain = null;
SpecialTrain.UserIDToSpecialTrains = null;
if (EventTimer != null) {
EventTimer.Destroy();
EventTimer = null;
}
ConfigValues = null;
Instance = null;
}
private void OnPlayerTruePVECleanup(BasePlayer player) {
if (TruePVEIsLoaded) {
if (SpecialTrain.UserIDToSpecialTrains.ContainsKey(player.userID)) {
foreach (var train in SpecialTrain.UserIDToSpecialTrains[player.userID].ToList()) {
if (train != null) {
if (train.PlayersInTruePVEBubble.ContainsKey(player.userID)) {
train.PlayersInTruePVEBubble.Remove(player.userID);
}
}
}
SpecialTrain.UserIDToSpecialTrains.Remove(player.userID);
}
}
}
private object OnDeathNotice(Dictionary<string, object> dic, string msg) {
if (dic == null) return null;
if (!dic.ContainsKey("HitInfo")) {
return null;
}
var hit = dic["HitInfo"] as HitInfo;
if (hit.InitiatorPlayer == null) {
return null;
}
var victim = hit.HitEntity as BasePlayer;
if (victim == null) {
return null;
}
if (hit.InitiatorPlayer.displayName == Instance.configData.TrainDriverName) {
return false;
}
if (victim.displayName == Instance.configData.TrainDriverName) {
return false;
}
return null;
}
private object OnPlayerDeath(BasePlayer player, HitInfo info) {
if (Instance == null) return null;
OnPlayerTruePVECleanup(player);
//ignore non-drivers here
if (!SpecialTrain.TrainDriverNetIDToSpecialTrain.ContainsKey(player.net.ID)) {
return null;
}
return SpecialTrain.TrainDriverNetIDToSpecialTrain[player.net.ID].OnTrainDriverKilled(info);
}
private void OnPlayerDisconnect(BasePlayer player) {
if (Instance == null) return;
OnPlayerTruePVECleanup(player);
}
private object OnEntityTakeDamage(BaseCombatEntity combatEntity, HitInfo info) {
if (Instance == null) return null;
if (combatEntity == null) return null;
if (combatEntity.net == null) return null;
if (info == null) return null;
if (SpecialTrain.WorkcartEntityNetIDToSpecialTrain.ContainsKey(combatEntity.net.ID)) {
return SpecialTrain.WorkcartEntityNetIDToSpecialTrain[combatEntity.net.ID].OnTrainEntityTakeDamage(combatEntity, info);
}
if (SpecialTrain.TrainDriverNetIDToSpecialTrain.ContainsKey(combatEntity.net.ID)) {
return SpecialTrain.TrainDriverNetIDToSpecialTrain[combatEntity.net.ID].OnTrainEntityTakeDamage(combatEntity, info);
}
//don't let them damage splats
if (combatEntity.PrefabName == PREFAB_RUG && combatEntity.skinID == SKIN_SPLAT1) {
return true;
}
return null;
}
private object OnEntityTakeDamage(TrainCar train, HitInfo info) {
if (Instance == null) return null;
if (SpecialTrain.TrainNetIDToSpecialTrain.ContainsKey(train.net.ID)) {
return SpecialTrain.TrainNetIDToSpecialTrain[train.net.ID].OnTrainEntityTakeDamage(train, info);
}
return null;
}
//broken from the December 2021 forced wipe
/*
private object OnFuelAbstract(EntityFuelSystem fuelSystem, object returnOnSuccess)
{
if (Instance == null) return null;
if (fuelSystem.owner.OwnerID != INTERNAL_OWNERID) return null;
if (!SpecialTrain.TrainNetIDToSpecialTrain.ContainsKey(fuelSystem.owner.net.ID))
{
return null;
}
//no infinite fuel if the driver is dead
if (!SpecialTrain.TrainNetIDToSpecialTrain[fuelSystem.owner.net.ID].DriverIsAlive)
{
return null;
}
return returnOnSuccess;
}
private object OnFuelCheck(EntityFuelSystem fuelSystem) => OnFuelAbstract(fuelSystem, true);
private object CanUseFuel(EntityFuelSystem fuelSystem, StorageContainer container, float seconds, float fuelUsedPerSecond) => OnFuelCheck(fuelSystem);
object OnFuelAmountCheck(EntityFuelSystem fuelSystem, Item item) => OnFuelAbstract(fuelSystem, 1337);
*/
#endregion
#region PERMISSION
public const string PERM_ADMIN = "cargotrainevent.admin";
#endregion
#region LANG
public const string MSG_FUEL_MANAGER_PRESENT = "MSG_FUEL_MANAGER_PRESENT";
public const string MSG_FUEL_MANAGER_NOT_PRESENT = "MSG_FUEL_MANAGER_NOT_PRESENT";
public const string MSG_CHAT_PREFIX = "MSG_CHAT_PREFIX";
public const string MSG_EVENT_SCHEDULED_IN = "MSG_EVENT_SCHEDULED_IN";
public const string MSG_EVENT_SCHEDULE_DISABLED = "MSG_EVENT_SCHEDULE_DISABLED";
public const string MSG_EVENT_STARTED = "MSG_EVENT_STARTED";
public const string MSG_EVENT_UPDATE_POSITION = "MSG_EVENT_UPDATE_POSITION";
public const string MSG_EVENT_OVER = "MSG_EVENT_OVER";
public const string MSG_EVENT_DRIVER_DEAD = "MSG_EVENT_DRIVER_DEAD";
public const string MSG_EVENT_CRATE_BEING_HACKED = "MSG_EVENT_CRATE_BEING_HACKED";
public const string MSG_EVENT_SELF_DESTRUCT_INITIATED = "MSG_EVENT_SELF_DESTRUCT_INITIATED";
public const string MSG_FORMAT_TIME_BOTH = "MSG_FORMAT_TIME_BOTH";
public const string MSG_FORMAT_TIME_SEC = "MSG_FORMAT_TIME_SEC";
public const string MSG_FORMAT_TIME_MIN = "MSG_FORMAT_TIME_MIN";
public const string MSG_AVAILABLE_CAMERAS = "MSG_AVAILABLE_CAMERAS";
public const string MSG_AVAILABLE_CAMERAS2 = "MSG_AVAILABLE_CAMERAS2";
public const string MSG_VALUE_HAS_BEEN_SET = "MSG_VALUE_HAS_BEEN_SET";
private const string MSG_CFG_DEFAULT = "MSG_CFG_DEFAULT";
private const string MSG_CFG_RUNDOWN_FORMAT = "MSG_CFG_RUNDOWN_FORMAT";
private const string MSG_CFG_DETAILS_FORMAT = "MSG_CFG_DETAILS_FORMAT";
private const string MSG_CFG_NO_SETTING_FOUND = "MSG_CFG_NO_SETTING_FOUND";
private Dictionary<string, string> LangMessages = new Dictionary<string, string> {
[MSG_FUEL_MANAGER_PRESENT]
= "Fuel Manager v.{0} loaded; handing over fuel handling", [MSG_FUEL_MANAGER_NOT_PRESENT] = "Fuel Manager not loaded, using default handling", [MSG_CHAT_PREFIX] = "<color=red>[Cargo Train Event]</color>", [MSG_EVENT_SCHEDULE_DISABLED] = "Not scheduling the next Cargo Train Event - the built-in event timer is disabled.You will need to run the command manually.", [MSG_EVENT_SCHEDULED_IN] = "The next Cargo Train Event has been scheduled to run {0} seconds (= {1} minutes) from now.", [MSG_EVENT_STARTED] = "Cargo Workcart inbound at <color=yellow>{0}</color>!", [MSG_EVENT_UPDATE_POSITION] = "Cargo Workcart is currently at <color=yellow>{0}</color>!", [MSG_EVENT_OVER] = "The Cargo Workcart event is over!", [MSG_EVENT_DRIVER_DEAD] = "The original driver of the Cargo Workcart has been taken out!", [MSG_EVENT_CRATE_BEING_HACKED] = "Somebody started hacking the locked crate on the Cargo Workcart!", [MSG_EVENT_SELF_DESTRUCT_INITIATED] = "The Cargo Workcart will self-destruct in {0}!", [MSG_FORMAT_TIME_BOTH] = "{0} m {1} s", [MSG_FORMAT_TIME_SEC] = "{0} s", [MSG_FORMAT_TIME_MIN] = "{0} m", [MSG_AVAILABLE_CAMERAS] = "The following CCTV identifiers have been assigned:{0}", [MSG_AVAILABLE_CAMERAS2] = "Event CCTVS: ", [MSG_VALUE_HAS_BEEN_SET] = "Config value <color=yellow>{0}</color> has been set to {1} by an admin.", [MSG_CFG_DEFAULT] = "Here's the current settings.Type <color=yellow>/te_cfg settingName</color> with no parameters to see the description, the current value and what the accepted arguments are.Type <color=yellow>/te_cfg settingName acceptedValue</color> to change the setting.", [MSG_CFG_RUNDOWN_FORMAT] = "/te_cfg <color=yellow>{0}</color> (currently: {1})", [MSG_CFG_DETAILS_FORMAT] = "<color=green>{0}</color>:\n{1} ({2})\nThis value is currently set to: {3}\n", [MSG_CFG_NO_SETTING_FOUND] = "No setting with that name has been found.Type /te_cfg to get a rundown.",
}
;
private static object ReusableObject;
private static bool ReusableBool;
private static string ReusableString;
private static float ReusableFloat;
private static bool ReusableBool2;
private static string ReusableString2;
private static float ReusableFloat2;
private static int ReusableInt2;
private static ulong ReusableUlong2;
[PluginReference]
private Plugin Notify;
private void TellMessage(BasePlayer player, string message, bool alsoPrintWarning = false) {
ReusableString2 = $"{MSG(MSG_CHAT_PREFIX)} {message}";
if (player == null) {
if (Instance.configData.UseChatMessages) {
Instance.PrintToChat(ReusableString2);
}
if (Instance.configData.UseNotifyPlugin) {
foreach (var playah in BasePlayer.activePlayerList) {
if (!playah.IsConnected) {
continue;
}
Notify?.Call("SendNotify", playah, (int)0, ReusableString2);
}
}
if (Instance.configData.UseGuiAnnouncements) {
GUIAnnouncements?.Call("CreateAnnouncement", ReusableString2, "Grey", "White", null, 0f, false, false, null, false);
}
if (alsoPrintWarning) {
Instance.PrintWarning(Instance.StripTags(ReusableString2));
}
}
else {
if (Instance.configData.UseChatMessages) {
player.ChatMessage(ReusableString2);
}
if (Instance.configData.UseNotifyPlugin) {
Notify?.Call("SendNotify", player, (int)0, ReusableString2);
}
if (Instance.configData.UseGuiAnnouncements) {
GUIAnnouncements?.Call("CreateAnnouncement", ReusableString2, "0 0 0 0", "1 1 0 1", player, 0f, false, false, null, false);
}
if (alsoPrintWarning) {
Instance.PrintWarning(Instance.StripTags($"{player.displayName} has been told: {ReusableString2}"));
}
}
}
private static string MSG(string msg, string userID = null, params object[] args) {
if (args == null) {
return Instance.lang.GetMessage(msg, Instance, userID);
}
else {
return string.Format(Instance.lang.GetMessage(msg, Instance, userID), args);
}
}
#endregion
#region CONFIG
public class ColorCode {
public string hexValue;
public UnityEngine.Color rustValue;
public string rustString;
public ColorCode(string hex) {
hexValue = hex.ToUpper();
if (hex.StartsWith("#")) {
hex = hex.Substring(1);
}
if (!hexValue.StartsWith("#")) {
hexValue = "#" + hexValue;
}
//extract the R, G, B
float r=255F, g=0, b=0;
try {
r = (float)short.Parse(hex.Substring(0, 2), System.Globalization.NumberStyles.HexNumber) / 255;
g = (float)short.Parse(hex.Substring(2, 2), System.Globalization.NumberStyles.HexNumber) / 255;
b = (float)short.Parse(hex.Substring(4, 2), System.Globalization.NumberStyles.HexNumber) / 255;
}
catch {
Instance.PrintError($"ERROR: {hexValue} doesn't appear to be a valid colour! Defaulting to red.");
r = 255F;
g = 0;
b = 0;
}
rustValue = new UnityEngine.Color(r, g, b);
rustString = $"{r} {g} {b}";
}
}
public class InteractiveConfigValue {
private Func<object> getter;
private Action<object> setter;
private Func<object, bool> validator;
public Func<object, string> formatter;
private Func<object, object> parser;
public Type valueType;
private double lowerLimit;
private double upperLimit;
//name also identifies it in the dic
public string name;
public string description;
public InteractiveConfigValue(string name, string description, Func<object> getter, Action<object> setter, Type validatorType = null, double lowerLimit = double.NegativeInfinity, double upperLimit = double.PositiveInfinity) {
this.getter = getter;
this.setter = setter;
this.name = name;
this.description = description;
this.lowerLimit = lowerLimit;
this.upperLimit = upperLimit;
formatter = FormatDefault;
if (validatorType == null || validatorType == typeof(bool)) {
validator = ValidateBool;
parser = ParseBool;
formatter = FormatBool;
}
else if (validatorType == typeof(float)) {
validator = ValidateFloat;
parser = ParseFloat;
}
else if (validatorType == typeof(int)) {
validator = ValidateInt;
parser = ParseInt;
}
else if (validatorType == typeof(ulong)) {
validator = ValidateUlong;
parser = ParseUlong;
}
else if (validatorType == typeof(string)) {
validator = ValidateString;
parser = ParseString;
formatter = FormatString;
}
valueType = validatorType;
}
public object GetSet {
get {
return getter();
}
set {
//validate first
if (validator(value)) {
ReusableObject = parser(value);
setter(ReusableObject);
//tell the players
Instance.TellMessage(null, MSG(MSG_VALUE_HAS_BEEN_SET, null, name, formatter(ReusableObject)));
Instance.SaveConfigData();
}
else {
ReusableString = upperLimit != double.PositiveInfinity || lowerLimit != double.NegativeInfinity ? $"The value for {name} is either too low or too high.Try {FormatNumericLimits()}" : $"Incorrect value for {name}.You need to enter a {FormatNumericLimits()}";
Instance.TellMessage(null, $"{ReusableString} \nThe value remains as {formatter(getter())}.");
}
}
}
public string FormatAcceptable() {
return valueType == typeof(bool) ? "logical values (<color=green>true</color> or <color=red>false</color>)" : valueType == typeof(string) ? FormatStrings() : FormatNumericLimits();
}
private string FormatNumericLimits() {
ReusableString2 = valueType == typeof(float) ? "<color=green>fractions (like 1.2345)</color>" : "<color=purple>integers (like 12345)</color>";
return $"{ReusableString2} between <color=red>{lowerLimit.ToString("0.00")}</color> and <color=blue>{upperLimit.ToString("0.00")}";
}
private string FormatStrings() {
return "strings (like ThisIsAString) - if they have a space, in quotes (like \"This Is A String\")";
}
private string FormatString(object value) {
return $"<color=yellow>{value?.ToString() ?? "NULL"}</color>";
}
private string FormatBool(object value) {
return value.Equals(true) ? "<color=green>true</color>" : "<color=red>false</color>";
}
private object ParseBool(object value) {
ReusableString2 = value.ToString().ToLower();
ReusableBool2 = !(ReusableString2.Contains("f") || ReusableString2.Contains("0") || ReusableString2.Contains("no"));
return ReusableBool2;
}
private bool ValidateBool(object value) {
//whatever it is, it can be always treated as bool
return true;
}
private object ParseFloat(object value) {
//it's already in ReusableFloat! neat, huh.//since we're using it, it must've been validated, so it's still there eh.return ReusableFloat2;
}
private object ParseString(object value) {
return ReusableString;
}
private object ParseInt(object value) {
return ReusableInt2;
}
private object ParseUlong(object value) {
return ReusableUlong2;
}
private bool ValidateFloat(object value) {
if (float.TryParse(value.ToString(), out ReusableFloat2)) {
return (ReusableFloat2 >= lowerLimit && ReusableFloat2 <= upperLimit);
}
else return false;
}
private bool ValidateInt(object value) {
if (int.TryParse(value.ToString(), out ReusableInt2)) {
return (ReusableInt2 >= lowerLimit && ReusableInt2 <= upperLimit);
}
else return false;
}
private bool ValidateUlong(object value) {
if (ulong.TryParse(value.ToString(), out ReusableUlong2)) {
return (ReusableUlong2 >= lowerLimit && ReusableUlong2 <= upperLimit);
}
else return false;
}
private bool ValidateString(object value) {
ReusableString = value.ToString();
if (ReusableString.ToLower() == "null") {
ReusableString = null;
}
return true;
}
private string FormatDefault(object value) {
return $"<color=#00FFFF>{value.ToString()}</color>";
}
}
private static Dictionary<string, InteractiveConfigValue> ConfigValues = null;
private static void AddInteractiveConfigValue(string name, string description, Func<object> getter, Action<object> setter, Type validator = null, double lowerLimit = double.NegativeInfinity, double upperLimit = double.PositiveInfinity) {
ConfigValues.Add(name, new InteractiveConfigValue(name, description, getter, setter, validator, lowerLimit, upperLimit));
}
private static void GenerateAllInteractiveConfigValues() {
AddInteractiveConfigValue("EnableRandomEvent", $"If true, enable random Cargo Train Event with random timers based on your config.If false, you will have to run the console command \"trainevent\" manually", () => Instance.configData.EnableRandomEvent, val => {
Instance.configData.EnableRandomEvent = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("EventDuration", $"How long the Cargo Train Event is going to last before initiating self-destruction timer (in seconds)", () => Instance.configData.EventDuration, val => {
Instance.configData.EventDuration = (float)val;
}
, typeof(float), 1F, 10000F);
AddInteractiveConfigValue("EventRandomTimerMin", $"Minimum time to wait before the next Cargo Train Event (in seconds)", () => Instance.configData.EventRandomTimerMin, val => {
Instance.configData.EventRandomTimerMin = (float)val;
}
, typeof(float), 1F, 10000F);
AddInteractiveConfigValue("EventRandomTimerMax", $"Maximum time to wait before the next Cargo Train Event (in seconds)", () => Instance.configData.EventRandomTimerMax, val => {
Instance.configData.EventRandomTimerMax = (float)val;
}
, typeof(float), 1F, 10000F);
AddInteractiveConfigValue("EventTrainAltitudeMin", $"Only idle carts with Y-position greater than this value (higher altitude) will be considered for the event.Use for no lower bound.", () => Instance.configData.EventTrainAltitudeMin, val => {
Instance.configData.EventTrainAltitudeMin = (float)val;
}
, typeof(float), -10000F, 10000F);
AddInteractiveConfigValue("EventTrainAltitudeMax", $"Only idle carts with Y-position lesser than this value (lower altitude) will be considered for the event.Use large values for no upper bound.", () => Instance.configData.EventTrainAltitudeMax, val => {
Instance.configData.EventTrainAltitudeMax = (float)val;
}
, typeof(float), -10000F, 10000F);
AddInteractiveConfigValue("SelfDestructTimer", $"The length of the self-destruction timer (in seconds)", () => Instance.configData.SelfDestructTimer, val => {
Instance.configData.SelfDestructTimer = (float)val;
}
, typeof(float), 1F, 10000F);
AddInteractiveConfigValue("SelfDestructDamageRadius", $"The radius of the self-destruction explosion (in meters)", () => Instance.configData.SelfDestructDamageRadius, val => {
Instance.configData.SelfDestructDamageRadius = (float)val;
}
, typeof(float), 1F, 10000F);
AddInteractiveConfigValue("SelfDestructDamageAmount", $"The amount of damage taken by entities within explosion radius (in HP)", () => Instance.configData.SelfDestructDamageAmount, val => {
Instance.configData.SelfDestructDamageAmount = (float)val;
}
, typeof(float), 1F, 10000F);
AddInteractiveConfigValue("HackingAddsSeconds", $"When a locked crate on the Train is hacked and the train is not self-destructing yet, postpone the self-destruct timer by this many seconds - or 0 to disable.900 seconds (15 minut) is the normal duration of the crate.", () => Instance.configData.HackingAddsSeconds, val => {
Instance.configData.HackingAddsSeconds = (float)val;
}
, typeof(float), 0F, 10000);
AddInteractiveConfigValue("WhenAttackedStopFor", $"When the train (or any entity belonging it) is attacked, it will brake and wait for this amount of seconds before continuing along the track", () => Instance.configData.WhenAttackedStopFor, val => {
Instance.configData.WhenAttackedStopFor = (float)val;
}
, typeof(float), 0F, 3600F);
AddInteractiveConfigValue("DontStopOnTunnelDwellerAttack", $"If true, tunnel dwellers shooting at the Cargo Train won't make the NPC Driver stop", () => Instance.configData.DontStopOnTunnelDwellerAttack, val => {
Instance.configData.DontStopOnTunnelDwellerAttack = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("ScientistsTargetTunnelDwellers", $"If true, scientists will actively target and shoot Tunnel Dwellers in their range", () => Instance.configData.ScientistsTargetTunnelDwellers, val => {
Instance.configData.ScientistsTargetTunnelDwellers = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("TrainTopSpeed", $"The top speed of the Train (in meters/second), Rust default is 12", () => Instance.configData.TrainTopSpeed, val => {
Instance.configData.TrainTopSpeed = (float)val;
}
, typeof(float), 1F, 60F);
AddInteractiveConfigValue("DeadManSwitchStop", $"If true, when the Train Driver NPC is killed, the train comes to a stop", () => Instance.configData.DeadManSwitchStop, val => {
Instance.configData.DeadManSwitchStop = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("CandidateHasToBeAtStation", $"If true, when deciding which idle train to man for the event, only consider trains flagged by Rust as \"At station\".", () => Instance.configData.CandidateHasToBeAtStation, val => {
Instance.configData.CandidateHasToBeAtStation = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("TrainDriverKit", $"The name of the custom kit for the Train Driver - or NULL to restore default (Heavy Scientist suit)", () => Instance.configData.TrainDriverKit, val => {
Instance.configData.TrainDriverKit = (string)val;
}
, typeof(string));
AddInteractiveConfigValue("TrainScientistKit", $"The name of the custom kit for the Scientists - or NULL for no custom kit (so default blue hazmat + LR)", () => Instance.configData.TrainScientistKit, val => {
Instance.configData.TrainScientistKit = (string)val;
}
, typeof(string));
/*
AddInteractiveConfigValue("TrainDriverName", $"The display name of the Train Driver - or NULL for no name", () => Instance.configData.TrainDriverName, val => { Instance.configData.TrainDriverName = (string)val; }, typeof(string));
*/
AddInteractiveConfigValue("RadiationEnable", $"If true, the train will have a radiation bubble around it at all times, according to the RadiationTier and RadiationRadius", () => Instance.configData.RadiationEnable, val => {
Instance.configData.RadiationEnable = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("RadiationRadius", $"If radiation is enabled, this is how far the bubble will reach from the centre of the train (in meters)", () => Instance.configData.RadiationRadius, val => {
Instance.configData.RadiationRadius = (float)val;
}
, typeof(float), 0F, 3600F);
AddInteractiveConfigValue("RadiationTier", $"Radiation tier, from 0 (minimal) to 3 (high)", () => Instance.configData.RadiationTier, val => {
Instance.configData.RadiationTier = (int)val;
}
, typeof(int), 0, 3);
AddInteractiveConfigValue("SplatsEnable", $"If true, the train will \"leak\" acid-green splats on the train track when the Train is on the move (acting as highly visible \"breadcrumbs\" to track the train down).Each one of them will despawn at random in the timeframe defined by SplatsLifetimeMin and SplatsLifetimeMax or when the plugin is reloaded.", () => Instance.configData.SplatsEnable, val => {
Instance.configData.SplatsEnable = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("SplatsPerSecond", $"The frequency of splatting (on average); the higher it is (up to 3 splats pers second), the more splats it produces.WARNING: More than 1 is not recommended due to possible performance drop.", () => Instance.configData.SplatsPerSecond, val => {
Instance.configData.SplatsPerSecond = (float)val;
}
, typeof(float), 0.01F, 3F);
AddInteractiveConfigValue("SplatsLifetimeMin", $"The minimum time before a splat despawns (in seconds)", () => Instance.configData.SplatsLifetimeMin, val => {
Instance.configData.SplatsLifetimeMin = (float)val;
}
, typeof(float), 1F, 1000F);
AddInteractiveConfigValue("SplatsLifetimeMax", $"The maximum time before a splat despawns (in seconds)", () => Instance.configData.SplatsLifetimeMax, val => {
Instance.configData.SplatsLifetimeMax = (float)val;
}
, typeof(float), 1F, 1000F);
AddInteractiveConfigValue("NotifyWhenStarted", $"If true, when the event starts, a global message will appear in the chat, according to the format specified in your lang JSON", () => Instance.configData.NotifyWhenStarted, val => {
Instance.configData.NotifyWhenStarted = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("NotifyWhenEnded", $"If true, when the event ends, a global message will appear in the chat, according to the format specified in your lang JSON", () => Instance.configData.NotifyWhenEnded, val => {
Instance.configData.NotifyWhenEnded = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("NotifyAboutSelfDestruct", $"If true, when train initiates a self-destruct sequence, a global message will appear in the chat, according to the format specified in your lang JSON", () => Instance.configData.NotifyAboutSelfDestruct, val => {
Instance.configData.NotifyAboutSelfDestruct = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("NotifyAboutCrateHacking", $"If true, when one of the locked crates on the train starts being hacked, a global message will appear in the chat, according to the format specified in your lang JSON", () => Instance.configData.NotifyAboutCrateHacking, val => {
Instance.configData.NotifyAboutCrateHacking = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("NotifyAboutDriverDeath", $"If true, when the Train Driver NPC is killed, a global message will appear in the chat, according to the format specified in your lang JSON", () => Instance.configData.NotifyAboutDriverDeath, val => {
Instance.configData.NotifyAboutDriverDeath = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("NotifyAboutPosition", $"If true, the train will announce its current position on the grid as a global message in the chat, according to the format specified in your lang JSON", () => Instance.configData.NotifyAboutPosition, val => {
Instance.configData.NotifyAboutPosition = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("NotifyAboutPositionEvery", $"How often to announce the train's grid position (in seconds)", () => Instance.configData.NotifyAboutPositionEvery, val => {
Instance.configData.NotifyAboutPositionEvery = (float)val;
}
, typeof(float), 1F, 1000F);
AddInteractiveConfigValue("AttachTimeCounters", $"If true, the train will come equipped with power counters to display the remaining minutes and seconds before self destruction initiation (or when already initiated, minutes and seconds before actual self destruction).The position/rotation of the counters can be set with the 12 values below.", () => Instance.configData.AttachTimeCounters, val => {
Instance.configData.AttachTimeCounters = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("CamerasAreFunctional", $"If true, any cameras attached to the train will be powered and assigned an ID for the event.If false, they will only be used for decoration ", () => Instance.configData.CamerasAreFunctional, val => {
Instance.configData.CamerasAreFunctional = val.Equals(true);
}
, typeof(bool));
AddInteractiveConfigValue("CounterMinutePosX", $"If counters are enabled, this is the position X of the minute counter, relative to the Train", () => Instance.configData.CounterMinutePosX, val => {
Instance.configData.CounterMinutePosX = (float)val;
}
, typeof(float));
AddInteractiveConfigValue("CounterMinutePosY", $"If counters are enabled, this is the position Y of the minute counter, relative to the Train", () => Instance.configData.CounterMinutePosY, val => {
Instance.configData.CounterMinutePosY = (float)val;
}
, typeof(float));
AddInteractiveConfigValue("CounterMinutePosZ", $"If counters are enabled, this is the position Z of the minute counter, relative to the Train", () => Instance.configData.CounterMinutePosZ, val => {
Instance.configData.CounterMinutePosX = (float)val;
}
, typeof(float));
AddInteractiveConfigValue("CounterMinuteRotX", $"If counters are enabled, this is the rotation X of the minute counter, relative to the Train", () => Instance.configData.CounterMinuteRotX, val => {
Instance.configData.CounterMinuteRotX = (float)val;
}
, typeof(float));
AddInteractiveConfigValue("CounterMinuteRotY", $"If counters are enabled, this is the rotation Y of the minute counter, relative to the Train", () => Instance.configData.CounterMinuteRotY, val => {
Instance.configData.CounterMinuteRotY = (float)val;
}
, typeof(float));
AddInteractiveConfigValue("CounterMinuteRotZ", $"If counters are enabled, this is the rotation Z of the minute counter, relative to the Train", () => Instance.configData.CounterMinuteRotZ, val => {
Instance.configData.CounterMinuteRotZ = (float)val;
}
, typeof(float));
AddInteractiveConfigValue("CounterSecondPosX", $"If counters are enabled, this is the position X of the second counter, relative to the Train", () => Instance.configData.CounterSecondPosX, val => {
Instance.configData.CounterSecondPosX = (float)val;
}
, typeof(float));
AddInteractiveConfigValue("CounterSecondPosY", $"If counters are enabled, this is the position Y of the second counter, relative to the Train", () => Instance.config
// ... [Code preview truncated due to length. Full source in attached file] ...