C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Network;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using UnityEngine;
using Formatter = Oxide.Core.Libraries.Covalence.Formatter;
namespace Oxide.Plugins
{
[Info("RatesController", "Vlad-00003", "3.3.2")]
[Description("All-in-one customizable rate system for the server")]
/*
* Author info:
* E-mail: Vlad-00003@mail.ru
* Vk: vk.com/vlad_00003
* TODO: Вывести частоту срабатывания переплавки в печах
*/
class RatesController : RustPlugin
{
#region Vars
private static PluginConfig _config;
private static RatesController _ratesController;
private PluginData _data;
private bool _isDay = true;
private Dictionary<ItemModCookable, float> _smeltRatesBackup = new Dictionary<ItemModCookable, float>();
private Dictionary<ulong,string> _quarryOwners = new Dictionary<ulong, string>();
private CoalConfig _defaultCoal;
private static class Constants
{
public const string ExcavatorArmPrefab = "assets/content/structures/excavator/prefabs/excavator_yaw.prefab";
}
#endregion
#region Configuration
#region Rates
private sealed class RateType
{
private readonly string _name;
private RateType(string name)
{
_name = name;
}
public override string ToString()
{
return _name;
}
public static implicit operator string(RateType type) => type._name;
public static readonly RateType Quarry = new RateType("Добываемые ресурсы в карьере");
public static readonly RateType Excavator = new RateType("Добываемые ресурсы в экскаваторе");
public static readonly RateType Gather = new RateType("Добываемые ресурсы");
public static readonly RateType Loot = new RateType("Получаемый лут");
public static readonly RateType Pickup = new RateType("Подбираемые ресурсы");
public static readonly RateType OvenSpeed = new RateType("Скорость печей");
public static readonly RateType[] Available = {Quarry, Excavator, Gather, Loot, Pickup, OvenSpeed};
}
private class Rates
{
[JsonProperty("Днём")]
private Dictionary<string, float> _day = new Dictionary<string, float>();
[JsonProperty("Ночью")]
private Dictionary<string, float> _night = new Dictionary<string, float>();
public float GetRate(RateType type, bool day = true)
{
var dict = day ? _day : _night;
float val;
return dict.TryGetValue(type, out val) ? val : 1f;
}
#region Default Сonfig
public static Rates Default => DefaultWith(1f);
public static Rates DefaultWith(float rate) => new Rates
{
_day = RateType.Available.ToDictionary(x => (string) x, x => rate),
_night = RateType.Available.ToDictionary(x => (string) x, x => rate)
};
#endregion
}
#endregion
#region Server Time Config
private class SavedDateConfig
{
[JsonProperty("Устанавливать дату при запуске плагина")]
public bool Use;
[JsonProperty("День")]
private int _day;
[JsonProperty("Месяц")]
private int _month;
[JsonProperty("Год")]
private int _year;
[JsonProperty("Час")]
private int _hour;
[JsonProperty("Минута")]
private int _minute;
[JsonProperty("Секунда")]
private int _second;
[JsonIgnore]
public DateTime Date;
#region Default Config
public static SavedDateConfig DefaultConfig
{
get
{
var now = DateTime.Now;
return new SavedDateConfig
{
Use = false,
_day = now.Day,
_month = now.Month,
_year = now.Year,
_hour = 12,
_minute = 0,
_second = 0
};
}
}
#endregion
[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context)
{
Date = new DateTime(_year,_month,_day,_hour,_minute,_second);
}
}
private class DayNightSwitchConfig
{
[JsonProperty("Рассчитывать время по закату/восходу (false - по часам)")]
public bool UseSun;
[JsonProperty("Час начала дня (игровое время)")]
public int DayStartHour;
[JsonProperty("Час начала ночи (игровое время)")]
public int NightStartHour;
#region Default Config
public static DayNightSwitchConfig DefaultConfig => new DayNightSwitchConfig
{
UseSun = true,
DayStartHour = 6,
NightStartHour = 18
};
#endregion
[JsonIgnore]
public float DayMultiplier;
[JsonIgnore]
public float NightMultiplier;
[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context)
{
DayMultiplier = 24.0f / (NightStartHour - DayStartHour);
NightMultiplier = 24.0f / (24.0f - (NightStartHour - DayStartHour));
}
}
private class TimeConfig
{
[JsonProperty("Длина дня (в минутах)")]
public int DayLength;
[JsonProperty("Длина ночи (в минутах)")]
public int NightLength;
[JsonProperty("Настройки смены дня/ночи")]
public DayNightSwitchConfig DayNightSwitchConfig;
[JsonProperty("Восстанавливать состояние времени при запуске плагина")]
public bool RestoreState;
[JsonProperty("Устанавливаемая при запуске плагина дата")]
public SavedDateConfig SavedDateConfig;
#region Default Config
public static TimeConfig DefaultConfig => new TimeConfig
{
DayLength = 30,
NightLength = 30,
DayNightSwitchConfig = DayNightSwitchConfig.DefaultConfig,
RestoreState = true,
SavedDateConfig = SavedDateConfig.DefaultConfig
};
#endregion
}
#endregion
#region General Options
private class ChatConfig
{
[JsonProperty("Выводить сообщения в чат о начале дня или ночи")]
public bool NotifyTod;
[JsonProperty("Формат сообщений в чате")]
private string _chatFormat;
[JsonIgnore]
public string ChatFormat;
#region Default Config
public static ChatConfig DefaultConfig => new ChatConfig
{
NotifyTod = true,
_chatFormat = "<color=#ff0000>[RatesController]</color>: {0}"
};
#endregion
[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context)
{
ChatFormat = Formatter.ToUnity(_chatFormat);
}
}
[JsonObject(MemberSerialization.OptIn)]
private class DefaultModifiers
{
[JsonProperty("Время переработки ресурсов (в секундах)")]
public Dictionary<string, float> SmeltRates;
[JsonProperty("Множители добываемых ресурсов")]
private Dictionary<string, float> _gatherRates;
[JsonProperty("Множители добываемых ресурсов в карьере")]
private Dictionary<string, float> _quarryRates;
[JsonProperty("Множители добываемых ресурсов в экскаваторе")]
private Dictionary<string, float> _excavatorRates;
[JsonProperty("Множители подбираемых ресурсов")]
private Dictionary<string, float> _pickupRates;
#region Default Config
public static DefaultModifiers DefaultConfig
{
get
{
var modifiers = new DefaultModifiers
{
SmeltRates = new Dictionary<string, float>(),
_gatherRates = new Dictionary<string, float>
{
["Animal Fat"] = 1.0f,
["Raw Bear Meat"] = 1.0f,
["Bone Fragments"] = 1.0f,
["Cloth"] = 1.0f,
["High Quality Metal Ore"] = 1.0f,
["Human Skull"] = 1.0f,
["Leather"] = 1.0f,
["Metal Ore"] = 1.0f,
["Pork"] = 1.0f,
["Raw Chicken Breast"] = 1.0f,
["Raw Human Meat"] = 1.0f,
["Raw Wolf Meat"] = 1.0f,
["Stones"] = 1.0f,
["Sulfur Ore"] = 1.0f,
["Wolf Skull"] = 1.0f,
["Wood"] = 1.0f,
["Raw Deer Meat"] = 1.0f,
["Cactus Flesh"] = 1.0f
},
_quarryRates = new Dictionary<string, float>
{
["High Quality Metal Ore"] = 1.0f,
["Sulfur Ore"] = 1.0f,
["Stones"] = 1.0f,
["Metal Fragments"] = 1.0f,
["Crude Oil"] = 1.0f
},
_excavatorRates = GameManager.server.FindPrefab(Constants.ExcavatorArmPrefab).GetComponent<ExcavatorArm>()?.resourcesToMine.ToDictionary(x => x.itemDef.displayName.english, x => 1f),
_pickupRates = new Dictionary<string, float>
{
["Metal Ore"] = 1.0f,
["Stones"] = 1.0f,
["Sulfur Ore"] = 1.0f,
["Wood"] = 1.0f,
["Hemp Seed"] = 1.0f,
["Corn Seed"] = 1.0f,
["Pumpkin Seed"] = 1.0f,
["Cloth"] = 1.0f,
["Pumpkin"] = 1.0f,
["Corn"] = 1.0f,
["Wolf Skull"] = 1.0f
}
};
ItemManager.GetItemDefinitions().ForEach(def =>
{
var cookable = def.GetComponent<ItemModCookable>();
if (!cookable)
return;
modifiers.SmeltRates[def.displayName.english] = cookable.cookTime;
});
return modifiers;
}
}
#endregion
public float GetModifier(RateType type, ItemDefinition def)
{
if (type == RateType.Gather)
return GetModifier(def, _gatherRates);
if (type == RateType.Quarry)
return GetModifier(def, _quarryRates);
if (type == RateType.Excavator)
return GetModifier(def, _excavatorRates);
if (type == RateType.Pickup)
return GetModifier(def, _pickupRates);
return 1f;
}
private float GetModifier(ItemDefinition def, Dictionary<string, float> data)
{
float mod;
if (data.TryGetValue(def.displayName.english, out mod) || data.TryGetValue(def.shortname, out mod))
return mod;
return 1f;
}
}
#endregion
#region Coal and HQM
private class HqmConfig
{
[JsonProperty("Добавить МВК в список бонусов всех рудных жил")]
public bool AddHqm;
[JsonProperty("Количество МВК в жиле")]
private int _amount;
[JsonIgnore]
public ItemDefinition HqmDefinition;
[JsonIgnore]
public ItemAmount ItemAmount => new ItemAmount(HqmDefinition, _amount);
#region Default Config
public static HqmConfig DefaultConfig => new HqmConfig
{
AddHqm = false,
_amount = 2
};
#endregion
[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context)
{
HqmDefinition = ItemManager.FindItemDefinition("hq.metal.ore");
}
}
private class CoalConfig
{
[JsonProperty("Шанс производства")]
private int _chance;
[JsonProperty("Количество")]
public int Amount;
//UnityEngine.Random.Range(0f, 1f) > burnable.byproductChance
[JsonIgnore]
public float Chance;
#region Default Config
public static CoalConfig DefaultConfig
{
get
{
var coal = ItemManager.FindItemDefinition("wood").GetComponent<ItemModBurnable>();
return new CoalConfig
{
_chance = Mathf.RoundToInt(100 - coal.byproductChance*100),
Amount = coal.byproductAmount
};
}
}
#endregion
public static CoalConfig Save()
{
var coal = ItemManager.FindItemDefinition("wood").GetComponent<ItemModBurnable>();
return new CoalConfig
{
Amount = coal.byproductAmount,
Chance = coal.byproductChance
};
}
[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context)
{
if (Amount < 0)
Amount = 0;
if (_chance <= 0 || Amount <= 0)
Chance = 2;
else
Chance = (100 - _chance) / 100f;
}
}
private class CoalConfigs
{
[JsonProperty("Днём")]
public CoalConfig Day;
[JsonProperty("Ночью")]
public CoalConfig Night;
#region Default Config
public static CoalConfigs DefaultConfig => new CoalConfigs
{
Day = CoalConfig.DefaultConfig,
Night = CoalConfig.DefaultConfig
};
#endregion
public CoalConfig Get(bool isDay) => isDay ? Day : Night;
}
#endregion
#region Loot
private class AllowedItemsConfig
{
[JsonProperty("Тип списка (0 - никак не управлять предметами, 1 - чёрный список, 2 - белый список).")]
private int _type;
[JsonProperty("Список предметов")]
public List<string> Items;
#region Default Config
public static AllowedItemsConfig DefaultConfig => new AllowedItemsConfig
{
_type = 1,
Items = new List<string> { "Blueprint","Rotten Apple", "Spoiled Wolf Meat", "Spoiled Chicken", "Spoiled Human Meat"}
};
#endregion
public bool ShouldChange(Item item)
{
switch (_type)
{
case 1:
return !Items.Any(x => x == item.info.displayName.english || x == item.info.shortname);
case 2:
return Items.Any(x => x == item.info.displayName.english || x == item.info.shortname);
default:
return true;
}
}
}
private class LootConfig
{
[JsonProperty("Предметы, на которые действуют множители")]
public AllowedItemsConfig AllowedItems;
[JsonProperty("Предметы, которые не будут выпадать вообще")]
public List<string> BlackList;
[JsonProperty("Множитель для предметов, не указанных в списке ниже")]
public float DefaultModifier;
[JsonProperty("Список множителей отдельных предметов (SkinID:Множитель)")]
public Dictionary<string, Dictionary<ulong, float>> CustomModifiers;
#region Default Config
public static LootConfig DefaultConfig => new LootConfig
{
AllowedItems = AllowedItemsConfig.DefaultConfig,
BlackList = new List<string>{"Rotten Apple"},
DefaultModifier = 1f,
CustomModifiers = new Dictionary<string, Dictionary<ulong, float>>
{
["battery.small"] = new Dictionary<ulong, float>
{
[0] = 1f,
[12331] = 2f
},
["scrap"] = new Dictionary<ulong, float>
{
[0] = 1f
}
}
};
#endregion
public bool ShouldDrop(Item item) =>
!BlackList.Any(x => x == item.info.displayName.english || x == item.info.shortname);
public float GetModifier(Item item)
{
Dictionary<ulong, float> skins;
if (!CustomModifiers.TryGetValue(item.info.shortname, out skins) &&
!CustomModifiers.TryGetValue(item.info.displayName.english, out skins))
return DefaultModifier;
float mod;
return !skins.TryGetValue(item.skin, out mod) ? DefaultModifier : mod;
}
}
#endregion
private class PluginConfig
{
[JsonProperty("Настройки сообщений в чате")]
public ChatConfig ChatConfig;
[JsonProperty("Настройки даты и времени")]
public TimeConfig TimeConfig;
[JsonProperty("Металл высокого качества")]
public HqmConfig HqmConfig;
[JsonProperty("Производство угля при сжигании дерева")]
public CoalConfigs CoalConfigs;
[JsonProperty("Настройки лута")]
public LootConfig LootConfig;
[JsonProperty("Общие множители")]
public Rates DefaultRates;
[JsonProperty("Множители по привилегиям")]
public Dictionary<string, Rates> CustomRates = new Dictionary<string, Rates>(StringComparer.InvariantCultureIgnoreCase);
[JsonProperty("Множители ресурсов")]
public DefaultModifiers DefaultModifiers;
[JsonProperty("Дополнительные привилегии (прибавляются к основным, любые источники)")]
public Dictionary<string, float> AdditionalPermissions = new Dictionary<string, float>(StringComparer.InvariantCultureIgnoreCase);
[JsonProperty("Переключатели множителей в печах")]
public Dictionary<string, bool> OvenSwitches;
#region Default Config
public static PluginConfig DefaultConfig => new PluginConfig
{
ChatConfig = ChatConfig.DefaultConfig,
TimeConfig = TimeConfig.DefaultConfig,
HqmConfig = HqmConfig.DefaultConfig,
CoalConfigs = CoalConfigs.DefaultConfig,
LootConfig = LootConfig.DefaultConfig,
DefaultRates = Rates.Default,
CustomRates =
{
[nameof(RatesController)+".vip"] = Rates.DefaultWith(2f),
[nameof(RatesController)+".premium"] = Rates.DefaultWith(3f)
},
DefaultModifiers = DefaultModifiers.DefaultConfig,
AdditionalPermissions =
{
["RatesController.NameReward"] = 0.3f
},
OvenSwitches = null
};
#endregion
}
#endregion
#region Data
private class PluginData
{
public bool IsFrozen;
}
#endregion
#region Config and Data initialization
#region Data
private void LoadData()
{
try
{
_data = Interface.Oxide.DataFileSystem.ReadObject<PluginData>(Title);
}
catch (Exception ex)
{
PrintError($"Failed to load data (is the file corrupt?) - no previously created recycles would work ({ex.Message})");
_data = new PluginData();
}
}
private void SaveData()
{
if(TimeController.Initialized)
_data.IsFrozen = TimeController.Instance.IsFrozen;
Interface.Oxide.DataFileSystem.WriteObject(Title, _data);
}
#endregion
#region Config
protected override void LoadConfig()
{
base.LoadConfig();
_config = Config.ReadObject<PluginConfig>();
if (ShouldUpdateConfig())
SaveConfig();
foreach (var customRate in _config.CustomRates)
{
permission.RegisterPermission(customRate.Key, this);
}
foreach (var addPerm in _config.AdditionalPermissions)
{
permission.RegisterPermission(addPerm.Key, this);
}
LoadData();
}
private bool ShouldUpdateConfig()
{
bool res = false;
if (_config.TimeConfig.DayLength < 0)
{
PrintWarning("Day length can't be less then 0, setting to 0");
_config.TimeConfig.DayLength = 0;
res = true;
}
if (_config.TimeConfig.NightLength < 0)
{
PrintWarning("Night length can't be less then 0, setting to 0");
_config.TimeConfig.NightLength = 0;
res = true;
}
if (_config.TimeConfig.DayLength == 0 && _config.TimeConfig.NightLength == 0)
{
PrintWarning("Both night and day length can't be 0 at the same time, setting day length to 30");
_config.TimeConfig.DayLength = 30;
res = true;
}
if (!ConfigExists<Dictionary<string,object>>("Дополнительные привилегии (прибавляются к основным, любые источники)"))
{
PrintWarning("New option added to the config file - additional rates");
_config.AdditionalPermissions = new Dictionary<string, float>() {["RatesController.NameReward"] = 0.3f};
res = true;
}
if (!ConfigExists<double>("Настройки лута", "Множитель для предметов, не указанных в списке ниже"))
{
PrintWarning("New options added to the config file - custom loot items modifiers, furnace selector");
_config.LootConfig.DefaultModifier = 1f;
_config.LootConfig.CustomModifiers = LootConfig.DefaultConfig.CustomModifiers;
res = true;
}
return res;
}
protected override void LoadDefaultConfig()
{
_config = PluginConfig.DefaultConfig;
}
protected override void SaveConfig()
{
Config.WriteObject(_config);
}
private bool ConfigExists<T>(params string[] path)
{
var value = Config.Get(path);
return value is T;
}
#endregion
#endregion
#region Localization
private static string GetMessage(string langKey, string playerId = null)
{
return _ratesController.lang.GetMessage(langKey, _ratesController, playerId);
}
private static string GetMessage(string langKey, string playerId = null, params object[] args)
{
return args.Length == 0 ? GetMessage(langKey, playerId) : string.Format(GetMessage(langKey, playerId), args);
}
private static void SendResponse(BasePlayer player, string langKey, params object[] args)
{
if (!player || !player.IsConnected)
return;
player.ChatMessage(string.Format(_config.ChatConfig.ChatFormat, GetMessage(langKey, player.UserIDString, args)));
}
private static void SendResponse(IPlayer player, string langKey, params object[] args)
{
player.Reply(string.Format(_config.ChatConfig.ChatFormat, GetMessage(langKey, player.Id, args)));
}
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
[RateType.Quarry] = "Quarry gather rates: {0}",
[RateType.Excavator] = "Excavator gather rates: {0}",
[RateType.Gather] = "Gather rates: {0}",
[RateType.Loot] = "Loot rates: {0}",
[RateType.Pickup] = "Pickup rates: {0}",
[RateType.OvenSpeed] = "Oven speed: {0}",
["RatesPersonal"] = "Your personal rates:\n{0}",
["DayStarted"] = "The day has come!\n{0}",
["NightStarted"] ="The night has come!\n{0}",
["PositiveRate"] = "<color=green>{0:P0}</color>",
["NegativeRate"] = "<color=red>{0:P0}</color>",
["NeutralRate"] = "{0:P0}"
}, this);
lang.RegisterMessages(new Dictionary<string, string>
{
[RateType.Quarry] = "Добываемые ресурсы в карьере: {0}",
[RateType.Excavator] = "Добываемые ресурсы в экскаваторе: {0}",
[RateType.Gather] = "Добываемые ресурсы: {0}",
[RateType.Loot] = "Получаемый лут: {0}",
[RateType.Pickup] = "Подбираемые ресурсы: {0}",
[RateType.OvenSpeed] = "Скорость печей: {0}",
["RatesPersonal"] = "Ваши личные рейты:\n{0}",
["DayStarted"] = "Наступает день!\n{0}",
["NightStarted"] ="Наступает ночь!\n{0}",
["PositiveRate"] = "<color=green>{0:P0}</color>",
["NegativeRate"] = "<color=red>{0:P0}</color>",
["NeutralRate"] = "{0:P0}"
}, this, "ru");
}
#endregion
#region Time controller
private class TimeController: FacepunchBehaviour
{
private int _attempt;
private TOD_Sky _sky;
private TOD_Time _time;
private bool _isDaySet;
private float _lastKnownTime; //env.time set workaround
private bool _initialized;
public static TimeController Instance { get; private set; }
public static bool Initialized => Instance && Instance._initialized;
#region Instance public properties
public bool IsFrozen
{
get { return !_time.ProgressTime; }
set { _time.ProgressTime = !value; }
}
#endregion
#region Private properties
private bool IsDay => DayStart <= _sky.Cycle.Hour && _sky.Cycle.Hour < NightStart;
private float DayStart
{
get
{
if (!_config.TimeConfig.DayNightSwitchConfig.UseSun)
return _config.TimeConfig.DayNightSwitchConfig.DayStartHour;
return _sky.SunriseTime;
}
}
private float NightStart
{
get
{
if (!_config.TimeConfig.DayNightSwitchConfig.UseSun)
return _config.TimeConfig.DayNightSwitchConfig.NightStartHour;
return _sky.SunsetTime;
}
}
private float DayLength
{
get
{
if (!_config.TimeConfig.DayNightSwitchConfig.UseSun)
return _config.TimeConfig.DayLength * _config.TimeConfig.DayNightSwitchConfig.DayMultiplier;
return _config.TimeConfig.DayLength * (24.0f / (_sky.SunsetTime - _sky.SunriseTime));
}
}
private float NightLength
{
get
{
if (!_config.TimeConfig.DayNightSwitchConfig.UseSun)
return _config.TimeConfig.NightLength * _config.TimeConfig.DayNightSwitchConfig.NightMultiplier;
return _config.TimeConfig.NightLength * (24.0f / (24.0f - (_sky.SunsetTime - _sky.SunriseTime)));
}
}
#endregion
#region Initialization
public void Initialize()
{
_sky = TOD_Sky.Instance;
if (_sky == null)
{
if (_attempt++ < 10)
{
Invoke(Initialize,1f);
return;
}
_ratesController.PrintError("Failed to get TOD_Sky instance in {0} attempts.", _attempt);
return;
}
_ratesController.Puts("TOD_Sky found after {0} attempts", _attempt);
_time = _sky.Components.Time;
if (_time == null)
{
_ratesController.PrintError("Can't find time component!");
return;
}
_initialized = true;
SetData();
if(IsDay)
OnDayStarted();
else
OnNightStarted();
_time.OnHour += OnHour;
}
private void SetData()
{
_time.UseTimeCurve = false;
if (_config.TimeConfig.SavedDateConfig.Use)
_sky.Cycle.DateTime = _config.TimeConfig.SavedDateConfig.Date;
if (_config.TimeConfig.RestoreState)
_time.ProgressTime = !_ratesController._data.IsFrozen;
_lastKnownTime = _sky.Cycle.Hour;
}
#endregion
#region Day/Night Switch
private void OnDayStarted()
{
if (!Initialized)
return;
var dayLength = DayLength;
if (dayLength <= 0)
{
print("Day skipped");
_sky.Cycle.Hour = NightStart;
_lastKnownTime = _sky.Cycle.Hour;
OnNightStarted();
return;
}
_time.DayLengthInMinutes = dayLength;
Interface.CallHook("OnDayStarted");
_isDaySet = true;
}
private void OnNightStarted()
{
if (!Initialized)
return;
var nightLength = NightLength;
if (nightLength <= 0)
{
print("Night skipped");
_sky.Cycle.Hour += 24 - _sky.Cycle.Hour + DayStart;
_lastKnownTime = _sky.Cycle.Hour;
OnDayStarted();
return;
}
_time.DayLengthInMinutes = nightLength;
Interface.CallHook("OnNightStarted");
_isDaySet = false;
}
#endregion
#region Actions
private void OnHour()
{
if(IsDay && !_isDaySet)
OnDayStarted();
if(!IsDay && _isDaySet)
OnNightStarted();
}
//env.time set workaround
private void Update()
{
if (!Initialized)
return;
if (Math.Abs(_sky.Cycle.Hour - _lastKnownTime) > Time.deltaTime)
OnHour();
_lastKnownTime = _sky.Cycle.Hour;
}
#endregion
public void Kill()
{
if (!Initialized)
return;
_time.OnHour -= OnHour;
Destroy(gameObject);
Instance = null;
}
public static TimeController Create()
{
if (Instance)
{
_ratesController.PrintWarning("TimeController wasn't destroyed");
Instance.Kill();
}
var go = new GameObject(nameof(RatesController) + "." + nameof(TimeController));
DontDestroyOnLoad(go);
Instance = go.AddComponent<TimeController>();
return Instance;
}
}
#endregion
#region Initialization and quitting
private void Init()
{
_ratesController = this;
Unsubscribe("OnPluginLoaded");
Unsubscribe("OnPlayerDisconnected");
Unsubscribe("OnPluginUnloaded");
Unsubscribe("OnPlayerConnected");
}
private void Unload()
{
LootController.KillAll();
TimeController.Instance?.Kill();
UpdateCoalRates(true);
OnServerSave();
foreach (var pair in _smeltRatesBackup)
pair.Key.cookTime = pair.Value;
_config = null;
_ratesController = null;
_data = null;
_defaultCoal = null;
_smeltRatesBackup.Clear();
_smeltRatesBackup = null;
_quarryOwners.Clear();
_quarryOwners = null;
_ratesCache.Clear();
}
private void OnServerSave() => SaveData();
private void OnServerInitialized()
{
var ovens = GameManager.server.preProcessed.prefabList.Values.Select(x => x.GetComponent<BaseOven>()).Where(x => x != null && (int)x.temperature >= 2);
if (_config.OvenSwitches == null)
{
_config.OvenSwitches = ovens.ToDictionary(x => x.ShortPrefabName, x => true);
SaveConfig();
PrintWarning("Oven switches list was null, filling with default values.Config was overwritten");
}
else
{
var changed = false;
foreach (var oven in ovens)
{
if (_config.OvenSwitches.ContainsKey(oven.ShortPrefabName))
continue;
_config.OvenSwitches[oven.ShortPrefabName] = true;
changed = true;
}
if (changed)
{
SaveConfig();
PrintWarning("New type(s) of oven added to the Oven Switch List");
}
}
_defaultCoal = CoalConfig.Save();
TimeController.Create().Initialize();
ItemManager.GetItemDefinitions().ForEach(SetCookableDefinition);
UpdateCoalRates();
}
#endregion
#region Time Hooks
private void OnDayStarted()
{
_isDay = true;
UpdateCoalRates();
_ratesCache.Clear();
if (!_config.ChatConfig.NotifyTod)
return;
foreach (var player in BasePlayer.activePlayerList)
SendRates(player, "DayStarted");
}
private void OnNightStarted()
{
_isDay = false;
UpdateCoalRates();
_ratesCache.Clear();
if (!_config.ChatConfig.NotifyTod)
return;
foreach (var player in BasePlayer.activePlayerList)
SendRates(player, "NightStarted");
}
#endregion
#region Oxide Hooks (Rates Cache)
private void OnUserGroupAdded(string playerId, string groupName)
{
if (permission.GetGroupPermissions(groupName).Any(groupPermission =>
_config.CustomRates.ContainsKey(groupPermission) || _config.AdditionalPermissions.ContainsKey(groupPermission)))
{
_ratesCache.Remove(playerId);
}
}
private void OnUserGroupRemoved(string playerId, string groupName)
{
if (permission.GetGroupPermissions(groupName).Any(groupPermission
=> _config.CustomRates.ContainsKey(groupPermission) || _config.AdditionalPermissions.ContainsKey(groupPermission)))
{
_ratesCache.Remove(playerId);
}
}
private void OnUserPermissionGranted(string playerId, string perm)
{
if (!_config.CustomRates.ContainsKey(perm) && !_config.AdditionalPermissions.ContainsKey(perm))
return;
_ratesCache.Remove(playerId);
}
private void OnUserPermissionRevoked(string playerId, string perm)
{
if (!_config.CustomRates.ContainsKey(perm) && !_config.AdditionalPermissions.ContainsKey(perm))
return;
_ratesCache.Remove(playerId);
}
private void OnGroupPermissionGranted(string group, string perm)
{
if (!_config.CustomRates.ContainsKey(perm) && !_config.AdditionalPermissions.ContainsKey(perm))
return;
foreach (var user in permission.GetUsersInGroup(group))
_ratesCache.Remove(user.Split(' ')[0]);
}
private void OnGroupPermissionRevoked(string group, string perm)
{
if (!_config.CustomRates.ContainsKey(perm) && !_config.AdditionalPermissions.ContainsKey(perm))
return;
foreach (var user in permission.GetUsersInGroup(group))
_ratesCache.Remove(user.Split(' ')[0]);
}
#endregion
#region Oxide Hooks (Sub)
private void OnQuarryToggled(MiningQuarry quarry, BasePlayer player)
{
if (!player || !quarry || !quarry.IsOn() || quarry.OwnerID != 0)
return;
_quarryOwners[quarry.net.ID.Value] = player.UserIDString;
}
private void OnExcavatorResourceSet(ExcavatorArm arm, string resource, BasePlayer player)
{
if (!player || !arm || arm.OwnerID != 0)
return;
_quarryOwners[arm.net.ID.Value] = player.UserIDString;
}
private void CanMoveItem(Item item, PlayerInventory playerLoot, ItemContainerId targetContainer, int targetSlot, int amount)
{
var container = item.GetRootContainer()?.entityOwner as LootContainer;
if (!container)
return;
LootController.GetOrAdd(container).OnMove(item, playerLoot, amount);
}
private void OnItemAction(Item item, string action, BasePlayer player)
{
if (action != "drop")
return;
var container = item.GetRootContainer()?.entityOwner as LootContainer;
if (!container)
return;
LootController.GetOrAdd(container).OnDrop(item, player);
}
private void OnItemAddedToContainer(ItemContainer inventory, Item item)
{
var container = item.GetRootContainer()?.entityOwner as LootContainer;
if (!container)
return;
LootController.GetOrAdd(container).OnRefill(inventory);
}
#endregion
#region Oxide Hooks (Rates)
private void OnQuarryGather(MiningQuarry quarry, Item item)
{
UpdateAmount(item, RateType.Quarry, GetQuarryOwner(quarry));
}
private void OnExcavatorGather(ExcavatorArm arm, Item item)
{
UpdateAmount(item, RateType.Excavator, GetQuarryOwner(arm));
}
private void OnDispenserGather(ResourceDispenser dispenser, BasePlayer player, Item item)
{
UpdateAmount(item, RateType.Gather, player.UserIDString);
if (!_config.HqmConfig.AddHqm || dispenser.gatherType != ResourceDispenser.GatherType.Ore)
return;
if (dispenser.finishBonus.Any(x => x.itemDef == _config.HqmConfig.HqmDefinition))
return;
dispenser.finishBonus.Add(_config.HqmConfig.ItemAmount);
}
private void OnDispenserBonus(ResourceDispenser dispenser, BasePlayer player, Item item)
{
UpdateAmount(item, RateType.Gather, player.UserIDString);
}
private object OnCollectiblePickup(CollectibleEntity entity, BasePlayer player)
{
if (!player || !entity)
return null;
entity.itemList = UpdateAmounts(entity.itemList, RateType.Pickup, player.UserIDString).ToArray();
return entity.itemList == null ? (object) false : null;
}
void OnGrowableGathered(GrowableEntity plant, Item item, BasePlayer player)
{
string owner = player.UserIDString;
if (plant.OwnerID != 0)
owner = plant.OwnerID.ToString();
UpdateAmount(item, RateType.Pickup, owner);
}
private void OnLootEntity(BasePlayer player, LootContainer container)
{
LootController.GetOrAdd(container).OnOpen(player);
}
private void OnLootEntityEnd(BasePlayer player, LootContainer container)
{
LootController.GetOrAdd(container).OnClose(player);
}
private void OnContainerDropItems(ItemContainer container)
{
var lootContainer = container?.entityOwner as LootContainer;
if (lootContainer == null)
return;
LootController.GetOrAdd(lootContainer).OnDropItems();
}
#region Cooking
private object OnOvenCook(BaseOven oven, Item fuelItem)
{
bool isOn;
if (_config.OvenSwitches.TryGetValue(oven.ShortPrefabName, out isOn) && !isOn)
return null;
if ((fuelItem == null && !oven.CanRunWithNoFuel) || oven.OwnerID == 0 || (int)oven.temperature < 2)
return null;
var userRate = GetUserRate(RateType.OvenSpeed, oven.OwnerID.ToString());
foreach (var item2 in oven.inventory.itemList.Where(item2 => item2.position >= oven._inputSlotIndex && item2.position < oven._inputSlotIndex + oven.inputSlots && !item2.HasFlag(global::Item.Flag.Cooking)))
{
item2.SetFlag(global::Item.Flag.Cooking, true);
item2.MarkDirty();
}
IncreaseCookTime(oven, 0.5f * oven.GetSmeltingSpeed() * userRate);
var slot = oven.GetSlot(BaseEntity.Slot.FireMod);
if (slot)
slot.SendMessage("Cook", 0.5f, SendMessageOptions.DontRequireReceiver);
if (fuelItem != null)
{
var burnable = fuelItem.info.GetComponent<ItemModBurnable>();
var requiredFuel = 0.5f * userRate * (oven.cookingTemperature / 200f);
if (!fuelItem.HasFlag(global::Item.Flag.OnFire))
{
fuelItem.SetFlag(global::Item.Flag.OnFire, true);
fuelItem.MarkDirty();
}
if (fuelItem.fuel >= requiredFuel)
{
fuelItem.fuel -= requiredFuel;
if (fuelItem.fuel <= 0f)
{
oven.ConsumeFuel(fuelItem, burnable);
}
}
else
{
while (requiredFuel > 0)
{
var dif = fuelItem.fuel - requiredFuel;
if (dif >= 0)
{
fuelItem.fuel -= requiredFuel;
if (fuelItem.fuel <= 0f)
{
oven.ConsumeFuel(fuelItem, burnable);
}
break;
}
requiredFuel -= fuelItem.fuel;
oven.ConsumeFuel(fuelItem, burnable);
if (!fuelItem.IsValid())
break;
}
}
}
//oven.OnCooked();
if (oven is ModularCarOven)
{
if (WaterLevel.Test(oven.transform.position, true, false))
{
oven.StopCooking();
}
}
Interface.CallHook("OnOvenCooked", oven, fuelItem, slot);
return false;
}
private void IncreaseCookTime(BaseOven oven, float amount)
{
List<global::Item> list = Facepunch.Pool.GetList<global::Item>();
foreach (global::Item item in oven.inventory.itemList)
{
if (item.HasFlag(global::Item.Flag.Cooking))
{
list.Add(item);
}
}
float num = amount / (float)list.Count;
foreach (global::Item item2 in list)
{
item2.OnCycle(num);
}
Facepunch.Pool.FreeList<global::Item>(ref list);
}
#endregion
#endregion
#region Console Commands
[ConsoleCommand("env.freeze")]
private void FreezeCmd(ConsoleSystem.Arg arg)
{
if (arg.Connection != null && arg.Connection.authLevel < 2)
return;
if (!TimeController.Initialized)
return;
TimeController.Instance.IsFrozen = !TimeController.Instance.IsFrozen;
arg.ReplyWith(TimeController.Instance.IsFrozen ? "Frozen" : "Unfrozen");
}
#endregion
#region Chat Commands
[ChatCommand("Rates")]
private void RatesCmd(BasePlayer player, string command, string[] args)
{
SendRates(player, "RatesPersonal");
}
#endregion
#region Loot Controller
private class LootController : FacepunchBehaviour
{
private static readonly Dictionary<LootContainer, LootController> LootControllers = new Dictionary<LootContainer, LootController>();
private LootContainer _lootContainer;
private
// ... [Code preview truncated due to length. Full source in attached file] ...