C#:
// Requires: UiEngineFramework
using static Oxide.Plugins.UiEngineFramework;
using static Oxide.Plugins.QualityCrafting;
using static Oxide.Plugins.QualityCrafting.InventoryOverlay;
using Oxide.Game.Rust.Cui;
using Oxide.Core;
using System.Collections.Generic;
using Oxide.Core.Plugins;
using System.Linq;
using UnityEngine;
using Oxide.Core.Libraries.Covalence;
using System.Data;
using Newtonsoft.Json;
using Oxide.Plugins.QualityCraftingExtensionMethods;
using System;
using Rust;
using System.IO;
namespace Oxide.Plugins {
[Info("QualityCrafting", "mr01sam", "3.0.4")]
[Description("Level crafting skills to craft higher quaility items with better stats and features.")]
public partial class QualityCrafting : CovalencePlugin {
public static QualityCrafting INSTANCE;
public static Configuration CONFIG => INSTANCE.config;
[PluginReference]
private Plugin QualityCraftingExtended;
private static bool ExtendedIsLoaded => INSTANCE?.QualityCraftingExtended?.IsLoaded ?? false;
public static UiEngineInstance SCOPE;
public const string PermissionAdmin = "qualitycrafting.admin";
public bool PluginIsLoaded = false;
public static Oxide.Core.VersionNumber? PluginVersion => INSTANCE?.Version;
void Init() {
INSTANCE = this;
config.Version = INSTANCE.Version;
if (UiEngineFramework.PluginVersion.HasValue) {
var currentVersion = UiEngineFramework.PluginVersion.Value;
var targetVersion = new VersionNumber(1, 0, 1);
if (currentVersion < targetVersion) {
PrintError($"[OUTDATED DEPENDENCY] - READ THIS!! This plugin requires UiEngineFramework v{targetVersion} or newer. You currently have v{currentVersion}. You MUST get the latest version of UiEngineFramework for this plugin to work properly.");
}
}
SaveConfig();
}
void OnServerInitialized() {
SCOPE = UiEngine.Init(this);
if (!permission.PermissionExists(PermissionAdmin)) {
permission.RegisterPermission(PermissionAdmin, this);
}
AddCovalenceCommand(CONFIG.SkillsMenu.OpenMenuCommand, nameof(CmdSkills));
LoadAllData();
SyncPerksWithConfig();
// Data
Tables.CreateDefaultCategoryLevelsTable(false);
Tables.CreateDefaultItemLevelsTable(false);
Tables.CreateDefaultQualityChancesTable(false);
CraftingSkills.LoadXpRequirements();
// Images
var images = new Dictionary<string, string> {
["banner"]
= config.InventoryOverlay.InspectionBannerImageUrl, ["star"] = config.QualityTiers.SingleStarImageUrl, ["star1"] = config.QualityTiers.ImageUrls[0], ["star2"] = config.QualityTiers.ImageUrls[1], ["star3"] = config.QualityTiers.ImageUrls[2], ["star4"] = config.QualityTiers.ImageUrls[3], ["star5"] = config.QualityTiers.ImageUrls[4], ["hudbutton.quality"] = CONFIG.HudButtons.QualityButton.IconUrl, ["hudbutton.skills"] = CONFIG.HudButtons.SkillsButton.IconUrl
}
;
images.AddRange(config.Categories.ToDictionary(x => $"cat {x.Key.ToLower()}", x => x.Value.ImageUrl));
UiEngine.AddImages(images);
Leaderboards.InitPlayerRankings();
UiEngine.LoadAddedImages(() => {
// Load Players
foreach (var basePlayer in BasePlayer.activePlayerList) {
InitQualityCraftingPlayer(basePlayer);
}
}
);
foreach (var basePlayer in BasePlayer.activePlayerList) {
InitQualityCraftingPlayer(basePlayer);
}
PluginIsLoaded = true;
}
public void LoadedExtensionPlugin() {
SyncPerksWithConfig();
UiEngine.LoadAddedImages();
ClearCache();
foreach (var basePlayer in BasePlayer.activePlayerList) {
InitQualityCraftingPlayer(basePlayer);
}
}
void OnNewSave(string filename) {
if (config.Options.ResetDataOnMapWipe) {
BackupAndClearData();
}
}
void OnServerSave() {
if (config.Options.SaveOnServerSave) {
SaveAllData();
}
}
void Unload() {
SaveAllData();
foreach (var player in BasePlayer.activePlayerList) {
OnPlayerDisconnected(player);
}
RemoveAndDestroyAllPlayerTickBehaviors();
ClearCache();
SCOPE?.Dispose();
SCOPE = null;
}
void OnPlayerSleep(BasePlayer basePlayer) {
if (basePlayer == null || !basePlayer.IsConnected || !basePlayer.IsRealPlayer()) {
return;
}
Inspection.CloseInspection(basePlayer);
SkillsMenu.Close(basePlayer);
InventoryOverlay.DestroyAll(basePlayer);
Tracking.Destroy(basePlayer);
HudButtons.Close(basePlayer);
}
void OnPlayerSleepEnded(BasePlayer basePlayer) {
if (basePlayer == null || !basePlayer.IsConnected || !basePlayer.IsRealPlayer()) {
return;
}
timer.In(0.2f, () => {
InitQualityCraftingPlayer(basePlayer);
}
);
}
private void InitQualityCraftingPlayer(BasePlayer basePlayer) {
if (basePlayer == null || !basePlayer.IsConnected || !basePlayer.IsRealPlayer()) {
return;
}
Tracking.Show(basePlayer, true);
InventoryOverlay.UpdateAll(basePlayer, basePlayer.inventory.containerBelt);
HudButtons.ShowQualityButton(basePlayer);
HudButtons.ShowSkillsButton(basePlayer);
if (_behaviours.ContainsKey(basePlayer.userID)) {
return;
}
AddPlayerTickBehavior(basePlayer);
}
void OnPlayerDisconnected(BasePlayer basePlayer) {
if (basePlayer == null) {
return;
}
RemovePlayerTickBehavior(basePlayer.userID);
OnPlayerSleep(basePlayer);
}
public void SaveAllData() {
Tracking.SavePlayerTracking();
Leveling.SavePlayerCraftingSkills();
if (CONFIG.SkillsMenu.ShowStatistics) {
Statistics.SaveStatistics();
}
}
public void LoadAllData() {
Tracking.LoadPlayerTracking();
Leveling.LoadPlayerCraftingSkills();
if (CONFIG.SkillsMenu.ShowStatistics) {
Statistics.LoadStatistics();
}
}
public void SyncPerksWithConfig() {
var localization = new Dictionary<string, string>();
// Add perks if they don't exist
foreach (var perk in Perks.ALL.Values) {
if (!config.Perks.ContainsKey(perk.ID)) {
config.Perks.Add(perk.ID, new PerkConfig {
ImageUrl = perk.ImageUrl, MaxRank = perk.MaxRank, Modifier = perk.Modifier, Weight = perk.Weight, Color = perk.Color
}
);
}
}
SaveConfig();
// Update perk info from config
foreach (var kvp in config.Perks) {
if (Perks.ALL.ContainsKey(kvp.Key)) {
var perk = Perks.ALL[kvp.Key];
var value = kvp.Value;
perk.Color = value.Color;
perk.ImageUrl = value.ImageUrl;
perk.Modifier = value.Modifier;
perk.Weight = value.Weight;
perk.MaxRank = value.MaxRank;
}
}
// Add images
UiEngine.AddImages(Perks.ALL.Values.ToDictionary(x => x.IconID, x => x.ImageUrl));
// Calculate weights
WeightedPerksPerCategory = new Dictionary<CraftingCategoryType, List<string>>();
PerksForCategory = new Dictionary<CraftingCategoryType, List<string>>();
foreach(var category in config.Categories) {
var cat = Enum.Parse<CraftingCategoryType>(category.Key);
WeightedPerksPerCategory[cat] = new List<string>();
PerksForCategory[cat] = new List<string>();
localization[$"category {cat.ToString().ToLower()}"] = DefaultCategoryNames.GetValueOrDefault(cat);
}
foreach (var perk in Perks.ALL.Values) {
foreach (var category in perk.Categories) {
WeightedPerksPerCategory.GetValueOrNew(category).AddRange(Enumerable.Repeat(perk.ID, perk.Weight));
PerksForCategory.GetValueOrNew(category).Add(perk.ID);
}
}
// Add localization
foreach (var perk in Perks.ALL.Values) {
localization[$"{perk.ID} name"] = perk.DefaultName;
localization[$"{perk.ID} description"] = perk.DefaultDescription;
}
LoadAdditionalMessages(localization);
}
}
}
namespace Oxide.Plugins {
public partial class QualityCrafting {
}
}
namespace Oxide.Plugins {
public partial class QualityCrafting {
public interface IConfigExtension {
}
public class LegacyConfig {
public VersionNumber Version {
get;
set;
}
= new VersionNumber(0, 0, 0);
}
public partial class Configuration {
[ConfigDescription("Do not edit this.")]
public VersionNumber Version = new VersionNumber(0, 0, 0);
[ConfigDescription("")]
public OptionsConfig Options = new OptionsConfig();
[ConfigDescription("")]
public Dictionary<string, PerkConfig> Perks = new Dictionary<string, PerkConfig>();
[ConfigDescription("")]
public Dictionary<string, CategoryConfig> Categories = new Dictionary<string, CategoryConfig> {
[CraftingCategoryType.Clothing.ToString()]
= new CategoryConfig {
Enabled = true, ImageUrl = "https://i.ibb.co/9YhRZCH/tailoring.png"
}
, [CraftingCategoryType.Melee.ToString()] = new CategoryConfig {
Enabled = true, ImageUrl = "https://i.ibb.co/7Gyb4X4/weaponsmithing.png"
}
, [CraftingCategoryType.Bows.ToString()] = new CategoryConfig {
Enabled = true, ImageUrl = "https://i.ibb.co/x50T2yJ/bowmaking.png"
}
, [CraftingCategoryType.Firearms.ToString()] = new CategoryConfig {
Enabled = true, ImageUrl = "https://i.ibb.co/rxpH6B3/gunsmithing.png"
}
, [CraftingCategoryType.Tools.ToString()] = new CategoryConfig {
Enabled = true, ImageUrl = "https://i.ibb.co/t3gVHKv/toolcrafting.png"
}
}
;
[ConfigDescription("")]
public LevelingConfig ItemLeveling = new LevelingConfig {
Table = Tables.DEFAULT_ITEM_LEVELS_TABLE, XpGainedForItemWorkbenchTier = new int[] {
1, 1, 1, 1, 1
}
}
;
[ConfigDescription("")]
public CategoryLevelingConfig CategoryLeveling = new CategoryLevelingConfig {
Table = Tables.DEFAULT_CATEGORY_LEVELS_TABLE, XpGainedForItemWorkbenchTier = new int[] {
1, 2, 3, 5, 8
}
, DuplicationChancePerLevel = 0.001f, CraftingSpeedIncreasePerLevel = 0.01f
}
;
[ConfigDescription("")]
public QualityConfig QualityTiers = new QualityConfig {
SingleStarImageUrl = "https://i.ibb.co/xLF7pk4/starb.png", ImageUrls = new string[] {
"https://i.ibb.co/HdvFyvS/star1c.png", "https://i.ibb.co/Xyk7XdW/star2c.png", "https://i.ibb.co/PhpMr9t/star3c.png", "https://i.ibb.co/yNgYZW4/star4c.png", "https://i.ibb.co/N1RpDPV/star5c.png"
}
, RarityColors = new string[] {
"1 1 1 1", "0.4 0.929 0.361 1", "0.2 0.459 0.91 1", "0.69 0.38 0.839 1", "1 0.839 0 1", "1 0.412 0 1"
}
, Table = Tables.DEFAULT_QUALITY_CHANCES_TABLE
}
;
[ConfigDescription("")]
public SkillsMenuConfig SkillsMenu = new SkillsMenuConfig();
[ConfigDescription("")]
public TrackingConfig Tracking = new TrackingConfig();
[ConfigDescription("")]
public InventoryOverlayConfig InventoryOverlay = new InventoryOverlayConfig();
[ConfigDescription("")]
public HudButtonsConfig HudButtons = new HudButtonsConfig();
[ConfigDescription("")]
public NotificationsConfig Notifications = new NotificationsConfig();
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public object Extended = null;
}
public class OptionsConfig {
[ConfigDescription("If true then perks will not appear on items that can't benefit from them. For example, Durable will not appear on items that do not have durability.")]
public bool PreventUselessPerks {
get;
set;
}
= false;
[ConfigDescription("If true then this plugin will save whenever your server is configured to save. Do NOT set this to true if you have your server save super frequently like every 1 minute. If you do this, there will be performance issues.")]
public bool SaveOnServerSave {
get;
set;
}
= false;
[ConfigDescription("If true then all player data will be automatically cleared when a new map is created or when the force wipe occurs.")]
public bool ResetDataOnMapWipe {
get;
set;
}
= false;
}
public class QualityConfig {
[ConfigDescription("Name of the table file for altering quality craft chances located within the oxide/data/QualityCrafting/v3 directory on your server.")]
public string Table {
get;
set;
}
= "";
[ConfigDescription("URL image for a single quality star. These are the horizontal stars that appear in the inspection screen.")]
public string SingleStarImageUrl {
get;
set;
}
[ConfigDescription("List of image URLs for each quality level 1-5. These are the stars that appear overlayed on items in the inventory.")]
public string[] ImageUrls {
get;
set;
}
[ConfigDescription("List of colors for each quality level 1-5.")]
public string[] RarityColors {
get;
set;
}
}
public class CategoryConfig {
[ConfigDescription("If true then this crafting category will be enabled. Otherwise it will be disabled for the features of this plugin.")]
public bool Enabled = true;
[ConfigDescription("The image URL for a category.")]
public string ImageUrl = "";
[ConfigDescription("If true then this category will appear as tracked by default for new players.")]
public bool TrackedByDefault = true;
}
public class PerkConfig {
[ConfigDescription("The image that is displayed for a perk. Can be an asset sprite path or a URL to your own hosted image.")]
public string ImageUrl = "";
[ConfigDescription("Color that corresponds to a perk.")]
public string Color = "1 1 1 1";
[ConfigDescription("This property allows you to adjust how effective this perk is per rank. For example, if the perk is Lethal, and the Modifier is set to '20'. Then a weapon with that perk will do 20% more damage per Lethal rank. See the perk descriptions for what each rank increases.")]
public float Modifier = 20;
[ConfigDescription("This is the limit that a crafted item can naturally have for this perk. For example, if the perk is Durable, and MaxRank is set to 3, then a crafted item can never achieve a Durable rank higher than III. If its a 4 or 5 star item, it might have 3 ranks of Durable, but it will also have 2 ranks of some other perk(s). Perks applied manually through a command can bypass this limit.")]
public int MaxRank = 3;
[ConfigDescription("This will allow you to adjust how likely this perk is to appear on crafted items. It is relative to the other perk weights. So if all perks are set to Weight=1, then they will all be equally likely. If you were to then set one of those perks to Weight=2, then that perk will be twice as likely as the others. You can also set Weight=0 to make it so this perk will never appear on crafted items. It can still appear if this perk is manually applied through a command.")]
public int Weight = 1;
}
public class NotificationsConfig {
[ConfigDescription("If true then notifications will show when crafting quality items.")]
public bool Enabled = true;
[ConfigDescription("If true the notifications will play a sound.")]
public bool PlaySfx = true;
[ConfigDescription("The horizontal position. Negative is to the left, positive is to the right. Zero is is the CENTER of the screen.")]
public int X = 24;
[ConfigDescription("The vertical position. Negative is down, positive is up. Zero is is the BOTTOM of the screen.")]
public int Y = 100;
}
public class InventoryOverlayConfig {
[ConfigDescription("If true then clothing resistance modifiers will appear in the inventory overlay screen.")]
public bool ShowClothingModifiers = true;
[ConfigDescription("Allows you to adjust the scale size of the quality stars overlay on inventory items. Note that if this is too big then players will not be able to move items correctly.")]
public float QualityStarsSizeScale = 1f;
[ConfigDescription("The image URL for the background image under the item name on the inspection screen.")]
public string InspectionBannerImageUrl = "https://i.ibb.co/N1THK27/banner-lines.png";
}
public class TrackingConfig {
[ConfigDescription("The max number of items or categories that a player can track at one time. Set to 0 to disable the tracking feature.")]
public int MaxTrackedItems = 8;
[ConfigDescription("The horizontal position. Negative is to the left, positive is to the right. Zero is is the CENTER of the screen.")]
public int X = 310;
[ConfigDescription("The vertical position. Negative is down, positive is up. Zero is is the BOTTOM of the screen.")]
public int Y = 14;
}
public class HudButtonsConfig {
[ConfigDescription("")]
public SingleHudButtonConfig QualityButton = new SingleHudButtonConfig {
IconUrl = SPRITE.Info, X = -240, Y = 24
}
;
[ConfigDescription("")]
public SingleHudButtonConfig SkillsButton = new SingleHudButtonConfig {
IconUrl = SPRITE.XP, X = 215, Y = 24
}
;
}
public class SingleHudButtonConfig {
[ConfigDescription("If true this button will be showned on the player HUD. Otherwise it will be hidden.")]
public bool Show = true;
[ConfigDescription("The sprite asset path or image URL for this button.")]
public string IconUrl = "";
[ConfigDescription("The color of this button.")]
public string Color = "1 1 1 0.5";
[ConfigDescription("If true this button will shown text underneath it. Otherwise there will be no text.")]
public bool ShowText = true;
[ConfigDescription("Font size of the text if shown.")]
public int TextSize = 8;
[ConfigDescription("Pixel width and height of the button.")]
public int Size = 48;
[ConfigDescription("The horizontal position. Negative is to the left, positive is to the right. Zero is is the CENTER of the screen.")]
public int X = 0;
[ConfigDescription("The vertical position. Negative is down, positive is up. Zero is is the BOTTOM of the screen.")]
public int Y = 0;
}
#region Skills Menu
public class SkillsMenuConfig {
[ConfigDescription("The command players can use to open the skills menu in addition to the HUD button.")]
public string OpenMenuCommand = "skills";
[ConfigDescription("If set to true then crafting statistics will be tracked for your players and displayed in their skill menu screen. Otherwise this area will be empty in the skills menu.")]
public bool ShowStatistics = true;
}
#endregion
#region Leveling
public class LevelingConfig {
[ConfigDescription("Name of the table file for the leveling curve located within the oxide/data/QualityCrafting/v3 directory on your server.")]
public string Table = "";
[ConfigDescription("The amount of XP gained when crafting an item for each workbench tier 0-3. An item that does not require a workbench is the first number, and an item that requires a tier 3 workbench is the 4th number.")]
public int[] XpGainedForItemWorkbenchTier = new[] {
1, 1, 1, 1
}
;
}
public class CategoryLevelingConfig : LevelingConfig {
[ConfigDescription("The chance a player has of duplicating an item they craft per category crafting level. For example, if set to 0.01, then this chance will increase by 1% for each category crafting level that player has.")]
public float DuplicationChancePerLevel = 0f;
[ConfigDescription("The speed increase of crafting per category crafting level. For example, if set to 0.1, then the speed will increase by 10% for each category crafting level that player has.")]
public float CraftingSpeedIncreasePerLevel = 0.01f;
}
public LevelingConfig ItemLeveling = new LevelingConfig {
XpGainedForItemWorkbenchTier = new int[] {
1, 1, 1, 1
}
}
;
public CategoryLevelingConfig CategoryLeveling = new CategoryLevelingConfig {
XpGainedForItemWorkbenchTier = new int[] {
1, 2, 3, 5
}
, DuplicationChancePerLevel = 0.001f
}
;
#endregion
#region Boiler Plate Config
public Configuration config;
protected override void LoadConfig() {
//base.LoadConfig();
//LoadDefaultConfig();
//return;
LegacyConfig legacyConfig = null;
try {
var legacyConfigText = File.ReadAllText(Interface.Oxide.ConfigDirectory + @"\QualityCrafting.json");
legacyConfig = JsonConvert.DeserializeObject<LegacyConfig>(legacyConfigText);
}
catch (Exception) {
}
if (legacyConfig != null && legacyConfig.Version.Major < 3 && legacyConfig.Version.Major > 0) {
PrintWarning(new string('#', 20) + "[PLUGIN UPDATED] It seems you are upgrading from an older version of Quality Crafting. YOUR CONFIG HAS BEEN RESET. A lot of things have changed, so make sure to setup your config again! If you would like to import player skill data, see the plugin documentation page for instructions." + new string('#', 20));
base.LoadConfig();
LoadDefaultConfig();
SaveConfig();
}
else {
base.LoadConfig();
try {
config = Config.ReadObject<Configuration>();
if (config == null) throw new Exception();
}
catch {
PrintError("Your configuration file contains an error. Using default configuration values.");
LoadDefaultConfig();
}
SaveConfig();
}
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultConfig() => config = new Configuration();
public void SaveConfigExternally() => SaveConfig();
#endregion
}
}
namespace Oxide.Plugins.QualityCraftingExtensionMethods {
public static class ExtensionMethods {
public static bool WasThrown(this HitInfo info) => info.IsProjectile() && (info.WeaponPrefab is BaseMelee);
public static BasePlayer BasePlayer(this IPlayer player) => player.Object as BasePlayer;
public static string Lang(this BasePlayer basePlayer, string key, params object[] args) => QualityCrafting.Lang(basePlayer.UserIDString, key, args);
public static string Lang(this IPlayer player, string key, params object[] args) => QualityCrafting.Lang(player.Id, key, args);
public static void Chat(this IPlayer player, string key, params object[] args) => player.Message(player.Lang(key, args));
public static void Chat(this BasePlayer basePlayer, string key, params object[] args) => basePlayer.ChatMessage(basePlayer.Lang(key, args));
public static V GetValueOrNew<K, V>(this Dictionary<K, V> dict, K key) where V : new() => GetValueOrNew(dict, key, new V());
public static V GetValueOrNew<K, V>(this Dictionary<K, V> dict, K key, V v) where V : new() {
var value = dict.GetValueOrDefault(key);
if (value != null) {
return value;
}
dict[key] = v != null ? v : new V();
return dict[key];
}
public static T GetValueOrDefault<T>(this ICollection<T> list, int index) {
if (index < list.Count) {
return list.ElementAt(index);
}
return default(T);
}
public static void AddIfNotExists<T>(this ICollection<T> list, Func<T, bool> predicate, T value) {
if (!list.Any(predicate)) {
list.Add(value);
}
}
public static T Random<T>(this List<T> list) {
var idx = UnityEngine.Random.Range(0, list.Count - 1);
return list[idx];
}
public static List<T> Extend<T>(this List<T> list, T extension) {
list.Add(extension);
return list;
}
public static List<T> Extend<T>(this List<T> list, List<T> extension) {
list.AddRange(extension);
return list;
}
public static ulong UserId(this IPlayer player) => ulong.Parse(player.Id);
public static string Image(this CraftingCategoryType category) {
return $"cat {category.ToString().ToLower()}";
}
public static bool IsEnabled(this CraftingCategoryType category) => CONFIG.Categories.ContainsKey(category.ToString()) && CONFIG.Categories[category.ToString()].Enabled;
public static string DisplayName(this CraftingCategoryType category, BasePlayer basePlayer) {
return Oxide.Plugins.QualityCrafting.Lang(basePlayer.UserIDString, $"category {category.ToString().ToLower()}");
}
public static CraftingSkills Skills(this BasePlayer basePlayer) => INSTANCE.PlayerCraftingSkills.GetValueOrNew(basePlayer.userID);
public static CraftingSkills Skills(this IPlayer player) => INSTANCE.PlayerCraftingSkills.GetValueOrNew(player.UserId());
public static QualityItem? Quality(this Item item) {
return QualityItem.Parse(item);
}
public static QualityItem? QualityIfExists(this Item item) {
return item.IsQualityItem() ? QualityItem.Parse(item) : null;
}
public static bool IsQualityItem(this Item item) {
//return item != null && !string.IsNullOrEmpty(item.text) && (item.IsBlueprint() || CraftingCategory.GetCraftingCategoryType(item) != CraftingCategoryType.None);
return item != null && !string.IsNullOrWhiteSpace(item.text) && item.text.Split("|").Length > 1;
}
public static bool IsQualityCraftable(this Item item) {
return CraftingCategory.GetCraftingCategoryType(item) != CraftingCategoryType.None;
}
public static void AddRange<T, V>(this Dictionary<T, V> dict, Dictionary<T, V> otherDict) {
foreach(var kvp in otherDict) {
dict[kvp.Key] = kvp.Value;
}
}
public static Item[] WornItems(this BasePlayer basePlayer) => basePlayer.inventory.containerWear.itemList.ToArray();
public static Item[] EquippedItems(this BasePlayer basePlayer) {
var items = new List<Item>();
items.AddRange(basePlayer.inventory.containerWear.itemList);
var active = basePlayer.GetActiveItem();
if (active != null) {
items.Add(active);
if (active.contents?.itemList != null) {
items.AddRange(active.contents.itemList);
}
}
return items.ToArray();
}
public static float TryGet(this ProtectionProperties protectionProperties, Rust.DamageType damageType) {
try {
return protectionProperties.Get(damageType);
}
catch (Exception) {
return 0f;
}
}
public static float TryGet(this Rust.DamageTypeList damageTypes, Rust.DamageType damageType) {
try {
return damageTypes.Get(damageType);
}
catch (Exception) {
return 0f;
}
}
public static bool IsAssetPath(this string path) => path.StartsWith("assets/");
public static ClothingResistances Resistances(this BasePlayer basePlayer) {
try {
var resistances = new ClothingResistances();
foreach (var item in basePlayer.inventory.containerWear.itemList) {
if (item.IsBackpack()) {
continue;
}
foreach (var value in Enum.GetValues(typeof(Rust.DamageType))) {
var dt = (Rust.DamageType)value;
var ap = item.info.ItemModWearable.armorProperties;
float res = item.info.ItemModWearable.protectionProperties.TryGet(dt);
float bonus = 0f;
var qi = item.QualityIfExists();
if (qi != null) {
bonus = qi.ProtectionBonus.GetValueOrDefault(dt, 0f);
if (bonus > 0f) {
resistances.TypesWithBonuses.Add(dt);
}
else {
resistances.TypesWithBonuses.Remove(dt);
}
}
if (ap.Contains(HitArea.Head)) {
resistances.Head[dt] = resistances.Head.GetValueOrDefault(dt) + res + bonus;
}
if (ap.Contains(HitArea.Leg)) {
resistances.Legs[dt] = resistances.Legs.GetValueOrDefault(dt) + res + bonus;
}
if (ap.Contains(HitArea.Stomach)) {
resistances.Body[dt] = resistances.Body.GetValueOrDefault(dt) + res + bonus;
}
}
}
return resistances;
}
catch (Exception e) {
return new ClothingResistances();
}
}
public static float GetDamage(this AmmoTypes ammo) {
switch (ammo) {
case AmmoTypes.PISTOL_9MM : case AmmoTypes.RIFLE_556MM : case AmmoTypes.BOW_ARROW : return 50;
case AmmoTypes.SHOTGUN_12GUAGE : return 210;
case AmmoTypes.HANDMADE_SHELL : return 180;
default : return 0f;
}
}
public static bool IsPlayerContainer(this ItemContainer container, BasePlayer basePlayer) {
var inventory = basePlayer.inventory;
return container.uid == inventory.containerBelt.uid || container.uid == inventory.containerMain.uid || container.uid == inventory.containerWear.uid;
}
public static ItemContainer[] GetInventories(this BaseCombatEntity entity) {
if (entity is StorageContainer storage) {
return new ItemContainer[] {
storage.inventory
}
;
}
if (entity is PlayerCorpse corpse) {
return corpse.containers;
}
if (entity is DroppedItemContainer dropped) {
return new ItemContainer[] {
dropped.inventory
}
;
}
if (entity is BasePlayer basePlayer) {
return new ItemContainer[] {
basePlayer.inventory.containerBelt, basePlayer.inventory.containerMain, basePlayer.inventory.containerWear
}
;
}
if (entity is IndustrialCrafter crafter) {
return new ItemContainer[] {
crafter._inventory
}
;
}
return new ItemContainer[] {
}
;
}
public static ItemContainer GetInventory(this BaseCombatEntity entity) => GetInventories(entity).FirstOrDefault();
public static bool IsRealPlayer(this BasePlayer basePlayer) => !basePlayer.IsNpc && !basePlayer.IsBot;
public static string Color(this string str, UiColor color) {
return $"<color={color.AsHex()}>{str}</color>";
}
public static string Size(this string str, int fontSize) => $"<size={fontSize}>{str}</size>";
public static string FormatAs(this string str, params object[] args) => str.FormatAs(args.Select(x => x?.ToString()).ToArray());
public static string FormatAs(this string str, params string[] args) => string.Format(str, args);
public static string Percent(this float value) => $"{(int)Math.Round(value * 100)}%";
public static int Floor(this decimal value) => (int)Math.Floor(value);
public static int Floor(this double value) => (int)Math.Floor(value);
public static int Floor(this float value) => (int)Math.Floor(value);
public static int Round(this float value) => (int)Math.Round(value);
public static ItemContainer GetItemContainer(this BasePlayer basePlayer, ulong uid, bool defaultMain = true) {
if (basePlayer.inventory.containerBelt.uid.Value == uid) {
return basePlayer.inventory.containerBelt;
}
if (basePlayer.inventory.containerMain.uid.Value == uid) {
return basePlayer.inventory.containerMain;
}
if (basePlayer.inventory.containerWear.uid.Value == uid) {
return basePlayer.inventory.containerWear;
}
if (defaultMain) {
return basePlayer.inventory.containerMain;
}
return null;
}
public static bool HasItem(this BasePlayer basePlayer, Item item) {
return basePlayer.inventory.AllItems().Any(x => x.uid == item.uid);
}
public static void GiveItemAt(this BasePlayer basePlayer, Item item, ItemContainer container, int position, bool backpack = false) {
if (backpack) {
var backpackItem = basePlayer.inventory.GetBackpackWithInventory();
if (backpackItem != null) {
container = backpackItem.contents;
}
}
if (!item.MoveToContainer(container, position) && !item.MoveToContainer(container) && !basePlayer.inventory.GiveItem(item)) {
item.Drop(basePlayer.inventory.containerMain.dropPosition, basePlayer.inventory.containerMain.dropVelocity);
}
}
public static bool HasItemAmount(this BasePlayer basePlayer, string itemShortName, int amount) {
var items = basePlayer.inventory.AllItems();
var amountDue = amount;
foreach (var item in items) {
if (amountDue <= 0) {
break;
}
if (item.info.shortname != itemShortName) {
continue;
}
var amtToTake = item.amount;
if (amountDue < amtToTake) {
amtToTake = amountDue;
}
amountDue -= amtToTake;
}
return amountDue <= 0;
}
public static void TakeItem(this BasePlayer basePlayer, string itemShortName, int amount) {
var items = basePlayer.inventory.AllItems();
var amountDue = amount;
foreach (var item in items ) {
if (amountDue <= 0) {
break;
}
if (item.info.shortname != itemShortName) {
continue;
}
var amtToTake = item.amount;
if (amountDue < amtToTake) {
amtToTake = amountDue;
}
item.UseItem(amtToTake);
amountDue -= amtToTake;
}
}
public static List<Item> AllItems(this PlayerInventory playerInventory) {
var items = new List<Item>();
playerInventory.GetAllItems(items);
return items;
}
}
}
namespace Oxide.Plugins {
public partial class QualityCrafting {
public class LangDataEntry {
public Dictionary<string, string> Translated;
}
public static readonly Dictionary<string, string> DefaultMessages = new Dictionary<string, string> {
["layer head"]
= "Head", ["layer body"] = "Body", ["layer legs"] = "Legs", ["tip"] = "Tip", ["tip1"] = "You can click on items or categories in the skill menu to track them on your HUD.", ["tip2"] = "Items that require higher workbench tiers grant more category XP.", ["tip3"] = "Quality of an item is represented by stars, higher quality items have more perks.", ["tip4"] = "Chances of crafting a higher quality is determined by item crafting level, not category.", ["tip5"] = "Category crafting level grants bonuses such as crafting speed and a chance to create duplicate items.", ["tip6"] = "Quality stars are shown in your inventory whenever you open a container or click the quality HUD button.", ["button skills"] = "Skills", ["button quality"] = "Quality", ["inspection perks"] = "Perks", ["inspection no perks"] = "This item does not have any perks.", ["inspection title"] = "Item Inspection", ["inspection stats"] = "Stats", ["inspection stat condition"] = "Condition", ["inspection stat damage scale"] = "Damage Scale", ["inspection stat damage"] = "Damage", ["inspection stat protection"] = "Protection", ["inspection stat bullet"] = "Bullet", ["inspection stat stab"] = "Stab", ["inspection stat explosion"] = "Explosion", ["inspection stat freezing"] = "Freezing", ["inspection stat radiation"] = "Radiation", ["inspection stat bite"] = "Bite", ["inspection stat slash"] = "Slash", ["inspection stat fall"] = "Fall", ["inspection stat blunt"] = "Blunt", ["inspection stat electric"] = "Electric", ["inspection stat gathering"] = "Gathering", ["inspection stat wood"] = "Wood", ["inspection stat ore"] = "Ore", ["inspection stat flesh"] = "Flesh", ["menu no statistics to show"] = "No statistics to show.", ["menu bonuses"] = "Bonuses", ["menu crafting speed"] = "Crafting Speed", ["menu duplicate chance"] = "Duplicate Chance", ["menu refining quality"] = "Refining Quality", ["menu no items crafted"] = "You have not crafted any items in this category yet.", ["menu item"] = "Item", ["menu tracking"] = "Tracking", ["menu crafting skills"] = "Crafting Skills", ["menu statistics"] = "Statistics", ["stats items crafted"] = "Items Crafted", ["stats quality percent"] = "Quality Percent", ["stats crafting rank"] = "Crafting Rank", ["stats items duplicated"] = "Items Duplicated", ["stats avg craft time"] = "Avg Craft Time", ["stats cloth consumed"] = "Cloth Consumed", ["stats springs consumed"] = "Springs Consumed", ["stats headshots"] = "Headshots", ["stats metal blades consumed"] = "Metal Blades Consumed", ["stats damage dealt"] = "Damage Dealt", ["stats resources gained"] = "Resources gained", ["stats rope consumed"] = "Rope Consumed", ["stats longest hit"] = "Longest Hit", ["stats damage taken"] = "Damage Taken", ["stats times refined"] = "Times Refined", ["notification crafted"] = "Crafted", ["notification duplicated"] = "Duplicated", ["tracking crafting skills"] = "Crafting Skills", ["xp"] = "XP", ["sec"] = "sec", ["defect"] = "DEFECT"
}
;
protected readonly Dictionary<string, Dictionary<string, string>> langFiles = new Dictionary<string, Dictionary<string, string>>();
protected Dictionary<string, string> LoadLocalizationFromFile(string lang = "en") {
var file = GetLangFile(Name, lang);
if (file == null) {
SaveLangFile(DefaultMessages, this, lang);
file = GetLangFile(Name, lang);
langFiles[lang] = file;
}
else {
foreach(var kvp in DefaultMessages) {
if (!file.ContainsKey(kvp.Key)) {
file[kvp.Key] = kvp.Value;
}
}
SaveLangFile(file, this, lang);
langFiles[lang] = file;
}
return file;
}
public static string Lang(string userIdString, string key, params object[] args) {
var playerLanguage = INSTANCE.lang.GetLanguage(userIdString);
if (!INSTANCE.langFiles.ContainsKey(playerLanguage)) {
INSTANCE.LoadLocalizationFromFile(playerLanguage);
}
var file = INSTANCE.langFiles.GetValueOrDefault(playerLanguage);
if (file == null) {
return key;
}
return string.Format(INSTANCE.langFiles[playerLanguage].GetValueOrDefault(key, key), args);
//return string.Format(INSTANCE.lang.GetMessage(key, INSTANCE, userIdString), args);
}
#region Built-In
private Dictionary<string, string> GetLangFile(string plugin, string lang = "en") {
if (string.IsNullOrEmpty(plugin)) {
return null;
}
char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
foreach (char oldChar in invalidFileNameChars) {
lang = lang.Replace(oldChar, '_');
}
string path = $"{lang}{Path.DirectorySeparatorChar}{plugin}.json";
string path2 = Path.Combine(Interface.Oxide.LangDirectory, path);
if (!File.Exists(path2)) {
return null;
}
return JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(path2));
}
public void SaveLangFile(Dictionary<string, string> messages, Plugin plugin, string lang = "en") {
if (messages == null || string.IsNullOrEmpty(lang) || plugin == null) {
return;
}
if (!Directory.Exists(Path.Combine(Interface.Oxide.LangDirectory, lang))) {
Directory.CreateDirectory(Path.Combine(Interface.Oxide.LangDirectory, lang));
}
File.WriteAllText(Path.Combine(Interface.Oxide.LangDirectory, $"{lang}/{Name}.json"), JsonConvert.SerializeObject(messages, Formatting.Indented));
}
#endregion
protected override void LoadDefaultMessages() {
LoadLocalizationFromFile("en");
}
public void LoadAdditionalMessages(Dictionary<string, string> newMessages) {
DefaultMessages.AddRange(newMessages);
LoadLocalizationFromFile("en");
}
}
}
namespace Oxide.Plugins {
public partial class QualityCrafting {
public static void Debug(string message) {
INSTANCE.Puts(message);
}
public static IPlayer FindPlayer(ulong userId) {
return INSTANCE.covalence.Players.FindPlayerById(userId.ToString());
}
public static IPlayer FindPlayer(string userIdOrName) {
return INSTANCE.covalence.Players.FindPlayer(userIdOrName);
}
public static List<IPlayer> FindPlayers(IEnumerable<ulong> userIds) {
return userIds.Select(x => FindPlayer(x)).Where(x => x != null).ToList();
}
private static float ScaleDamageTypeValue(Rust.DamageType damageType, float value, bool inverse = false) {
switch (damageType) {
case Rust.DamageType.ColdExposure : case Rust.DamageType.RadiationExposure : case Rust.DamageType.Bite : case Rust.DamageType.Explosion : value = inverse ? value / Const.ResistanceRatio : value * Const.ResistanceRatio;
break;
default : break;
}
return Math.Min(0.99f, value);
}
private static List<T> Combine<T>(params T[][] arrs) {
var combined = new List<T>();
foreach(var arr in arrs) {
if (arr == null) {
continue;
}
combined.AddRange(arr);
}
return combined;
}
private static string FormatStatWithModifier(int baseValue, int mod, string format="{0} ({1})", string singleFormat = "{0}") {
if (mod == 0) {
return string.Format(singleFormat, baseValue);
}
var color = mod > 0 ? Const.Colors.PositiveHighlight : Const.Colors.NegativeHighlight;
var symbol = mod > 0 ? "+" : "-";
return string.Format(format, baseValue, (symbol + mod).Color(color));
}
private static string FormatNumberWithSuffix(int number) {
if (number >= 11 && number <= 13) {
return number + "th";
}
int lastDigit = number % 10;
string
// ... [Code preview truncated due to length. Full source in attached file] ...