C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using Oxide.Plugins.AutoCrafterNamespace;
using Oxide.Plugins.AutoCrafterNamespace.Extensions;
using Rust;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Plugins.AutoCrafterNamespace.UI;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using System.Globalization;
using ProtoBuf;
using Oxide.Plugins.AutoCrafterNamespace.JsonConverters;
namespace Oxide.Plugins {
[Info("AutoCrafter", "Skipcast", "1.0.4", ResourceId = 2582)]
[Description("A machine that automatically crafts items so the player can do more interesting stuff instead.")]
public class AutoCrafter : RustPlugin {
private readonly List<ItemAmount> UpgradeCost = new List<ItemAmount>();
/// <summary>
/// Used for keeping track of when research tables were placed so we know if enough time has passed that upgrading is impossible./// </summary>
private readonly List<BaseEntity> upgradeableEntities = new List<BaseEntity>();
/// <summary>
/// List of players that have received the first join message./// </summary>
private List<ulong> introducedPlayers = new List<ulong>();
private bool serverInitialized = false;
#region Rust hooks
private object OnItemCraft(ItemCraftTask task) {
var player = task.owner;
var crafter = CrafterManager.FindByPlayer(player);
if (crafter != null && crafter.PlayerCanAccess(player)) {
crafter.AddCraftTask(task);
return true;
}
return null;
}
private void OnLootEntity(BasePlayer player, BaseEntity entity) {
var recycler = entity as Recycler;
if (recycler == null) return;
var crafter = CrafterManager.GetCrafter(recycler);
if (crafter == null) return;
// Open the output container instead of the recycler ui.NextFrame(() =>
{
if (!crafter.PlayerCanAccess(player)) {
crafter.PlayLockedSound();
player.CloseInventory();
return;
}
player.inventory.loot.Clear();
player.inventory.loot.StartLootingEntity(crafter.Output);
player.inventory.loot.AddContainer(crafter.OutputInventory);
player.inventory.loot.SendImmediate();
player.ClientRPCPlayer(null, player, "RPC_OpenLootPanel", crafter.Output.lootPanelName);
if (crafter.IsLocked()) crafter.PlayAccessSound();
}
);
}
void OnEntityGroundMissing(BaseEntity entity) {
var recycler = entity as Recycler;
if (recycler == null) return;
var crafter = CrafterManager.GetCrafter(recycler);
if (crafter == null) return;
// Empty recycler, otherwise the hidden items inside it will drop into the world.recycler.inventory.Clear();
recycler.inventory.itemList.Clear();
}
void OnEntitySpawned(BaseNetworkable networkable) {
if (!serverInitialized) // Check if server is initialized.This hook tends to call on startup before OnServerInitialized has been called.return;
var entity = networkable as BaseEntity;
if (entity == null) return;
if (entity.OwnerID == 0) return;
var researchTable = entity as ResearchTable;
if (researchTable == null) return;
upgradeableEntities.Add(researchTable);
timer.Once(Constants.TimeToUpgrade, () => upgradeableEntities.Remove(researchTable));
}
void OnEntityKill(BaseNetworkable entity) {
if (!serverInitialized) // Check if server is initialized.This hook tends to call on startup before OnServerInitialized has been called.return;
var researchTable = entity as ResearchTable;
if (researchTable != null) {
upgradeableEntities.Remove(researchTable);
}
var recycler = entity as Recycler;
if (recycler == null) return;
var crafter = CrafterManager.GetCrafter(recycler);
if (crafter == null) return;
CrafterManager.DestroyCrafter(crafter, false, false);
}
void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) {
float newHealth = entity.Health() - info.damageTypes.Total();
// Empty recycler inventory if it's about to be killed to avoid dropping hidden items.if (newHealth <= 0)
{
var recycler = entity as Recycler;
if (!(entity is Recycler)) return;
var crafter = CrafterManager.GetCrafter(recycler);
if (crafter == null) return;
recycler.inventory.Clear();
recycler.inventory.itemList.Clear();
}
}
void OnPlayerInput(BasePlayer player, InputState input) {
if (input.WasJustPressed(BUTTON.FIRE_PRIMARY)) {
var activeItem = player.GetActiveItem();
if (activeItem?.info.itemid != -975723312) // Codelock
return;
var ray = player.eyes.HeadRay();
RaycastHit hit;
if (!Physics.Raycast(ray, out hit, 2.2f, 1 << (int) Layer.Deployed)) return;
var recycler = hit.transform.GetComponentInParent<Recycler>();
if (recycler == null) return;
if (player.IsBuildingBlocked(recycler.ServerPosition, recycler.ServerRotation, recycler.bounds)) return;
var crafter = CrafterManager.GetCrafter(recycler);
if (crafter == null) return;
if (crafter.AddCodeLock()) {
activeItem.UseItem();
FxManager.PlayFx(crafter.CodeLock.ServerPosition, Constants.CodelockPlaceSoundPrefab);
}
}
}
// Show message if enabled
void OnPlayerSpawn(BasePlayer player) {
if (!serverInitialized) // Check if server is initialized.This hook tends to call on startup before OnServerInitialized has been called.return;
timer.Once(1, () => {
ShowJoinMessage(player);
}
);
}
// Make sure nothing is clipping into recycler.Pretty hacky method, but the recycler doesn't block things like other deployables.object CanBuild(Planner plan, Construction prefab, Vector3 position)
{
BasePlayer player = plan.GetOwnerPlayer();
List<Recycler> recyclers = new List<Recycler>();
Vis.Entities(position, prefab.bounds.size.magnitude / 3f, recyclers, 1 << (int) Layer.Deployed);
if (recyclers.Count <= 0) {
return null;
}
return true;
}
private object OnServerCommand(ConsoleSystem.Arg arg) {
if (arg.Connection == null) return null;
var player = (BasePlayer) arg.Connection.player;
if (arg.cmd?.FullName == "craft.canceltask") {
int taskid = arg.GetInt(0, -1);
if (taskid == -1) return null;
var crafters = CrafterManager.FindAllByPlayer(player);
foreach (var crafter in crafters) {
if (crafter.CancelByTaskId(player, taskid)) return true;
}
return null;
}
return null;
}
private object OnHammerHit(BasePlayer player, HitInfo info) {
var entity = info.HitEntity as BaseCombatEntity;
var recycler = entity as Recycler;
var researchTable = entity as ResearchTable;
if (entity == null || (recycler == null && researchTable == null)) return null;
Func<string> hpMessage = () => {
return Lang.Translate(player, "hp-message", entity.Health(), entity.MaxHealth());
}
;
// Don't allow upgrading/downgrading/repairing if there's less than 8 seconds since the entity was attacked.if (entity.SecondsSinceAttacked < 8)
{
if (recycler != null && CrafterManager.ContainsRecycler(recycler)) {
// Show hp info if repairing is blocked.player.ShowScreenMessage(hpMessage(), 2);
}
return null;
}
if (!lastHammerHit.ContainsKey(player)) lastHammerHit[player] = 0;
((DecayEntity) entity).DecayTouch();
// Reset decay
// Make sure entity is full health, otherwise repair.if (entity.Health() < entity.MaxHealth())
{
if (recycler == null) return null;
if (!CrafterManager.ContainsRecycler(recycler)) return null;
if (Time.time - lastHammerHit[player] > Constants.HammerConfirmTime) {
player.ShowScreenMessage(hpMessage() + "\n\n" + Lang.Translate(player, "hit-again-to-repair"), Constants.HammerConfirmTime);
lastHammerHit[player] = Time.time;
return true;
}
lastHammerHit[player] = Time.time;
player.HideScreenMessage();
entity.DoRepair(player);
player.ShowScreenMessage(hpMessage(), 2);
// Reset last hammer hit so that the player won't accidentally downgrade/upgrade with the next hammer hit.if (entity.Health() >= entity.MaxHealth())
{
lastHammerHit[player] = 0;
}
return true;
}
// Only allow upgrading/downgrading if we have building permission.if (player.IsBuildingBlocked(entity.ServerPosition, entity.ServerRotation, entity.bounds))
{
if (recycler != null && CrafterManager.ContainsRecycler(recycler)) // Only show hp info if this is a crafter
{
// Show hp info if building blocked.player.ShowScreenMessage(hpMessage(), 2);
}
return null;
}
// Check permission and if the entity owner is the current player.if (!permission.UserHasPermission(player.UserIDString, Constants.UsePermission) || entity.OwnerID != player.userID)
{
if (recycler != null && CrafterManager.ContainsRecycler(recycler)) player.ShowScreenMessage(hpMessage(), 2);
return null;
}
if (researchTable != null) // Upgrade to crafter (if less than 10 minutes since placement)
{
if (!upgradeableEntities.Contains(researchTable)) return null;
return HandleUpgradeRequest(player, researchTable);
}
var crafter = CrafterManager.GetCrafter(recycler);
if (crafter == null) return null;
if (DateTime.UtcNow - crafter.CreationTime > TimeSpan.FromSeconds(Constants.TimeToUpgrade)) {
player.ShowScreenMessage(hpMessage(), 2);
return null;
}
// Check if player has authed on potential codelock.if (!crafter.PlayerCanAccess(player))
{
crafter.PlayLockedSound();
return true;
}
return HandleDowngradeRequest(player, crafter);
}
protected override void LoadDefaultConfig() {
Utility.Config = new PluginConfig();
Utility.Config.UpgradeCost.AddRange(new List<PluginConfig.ItemAmount> {
new PluginConfig.ItemAmount("metal.refined", 25), new PluginConfig.ItemAmount("metal.fragments", 500), new PluginConfig.ItemAmount("techparts", 3), new PluginConfig.ItemAmount("gears", 3)
}
);
}
private void Loaded() {
if (Utility.Config == null) {
Utility.Config = Config.ReadObject<PluginConfig>();
Config.WriteObject(Utility.Config);
// Save any new or removed properties.}
else {
Config.WriteObject(Utility.Config);
}
}
private void OnServerInitialized() {
Utility.Timer = timer;
Config.Settings.AddConverters();
permission.RegisterPermission(Constants.UsePermission, this);
lang.RegisterMessages(Lang.DefaultMessages, this, "en");
UiManager.Initialize();
Lang.Initialize(this, lang);
FxManager.Initialize();
foreach (var itemAmount in Utility.Config.UpgradeCost) {
var itemDef = ItemManager.FindItemDefinition(itemAmount.Shortname);
if (itemDef == null) {
PrintError(Lang.Translate(null, "item-notfound-skipping-ingredient", itemAmount.Shortname));
continue;
}
UpgradeCost.Add(new ItemAmount(itemDef, itemAmount.Amount));
}
CrafterManager.Initialize();
CrafterManager.Load();
if (Utility.Config.ShowPlayerInstructionsOnFirstJoin) {
// Load previously introduced players
introducedPlayers = Core.Interface.Oxide.DataFileSystem.ReadObject<List<ulong>>("AutoCrafter/IntroducedPlayers");
foreach (var player in BasePlayer.activePlayerList) {
ShowJoinMessage(player);
}
}
serverInitialized = true;
}
private void OnServerSave() {
CrafterManager.Save();
if (Utility.Config.ShowPlayerInstructionsOnFirstJoin) {
Core.Interface.Oxide.DataFileSystem.WriteObject("AutoCrafter/IntroducedPlayers", introducedPlayers);
}
}
private void Unload() {
if (!serverInitialized) // Check if server is initialized.This hook tends to call on startup before OnServerInitialized has been called.return;
FxManager.Destroy();
CrafterManager.Destroy();
UiManager.Destroy();
}
private object OnRecycleItem(Recycler recycler, Item item) {
if (CrafterManager.ContainsRecycler(recycler)) {
// Prevent recycling
return true;
}
return null;
}
object OnRecyclerToggle(Recycler recycler, BasePlayer player) {
var crafter = CrafterManager.GetCrafter(recycler);
if (crafter == null) return null;
if (!crafter.PlayerCanAccess(player)) {
crafter.PlayLockedSound();
return true;
}
return null;
}
void OnUserPermissionGranted(string id, string perm) {
if (perm == Constants.UsePermission) {
ShowJoinMessage(BasePlayer.Find(id));
}
}
void OnGroupPermissionGranted(string name, string perm) {
if (perm == Constants.UsePermission) {
foreach (var player in BasePlayer.activePlayerList) {
ShowJoinMessage(player);
}
}
}
#endregion
#region Chat commands
[ChatCommand("autocrafter")]
private void ChatCmd_Autocrafter(BasePlayer player, string command, string[] args) {
if (!permission.UserHasPermission(player.UserIDString, Constants.UsePermission)) {
player.TranslatedChatMessage("nopermission");
return;
}
string submenu = args.FirstOrDefault();
StringBuilder message = new StringBuilder();
string title = null;
Action appendMenus = () => {
message.AppendLine("- craft : " + Lang.Translate(player, "chat-description-craft"));
message.Append("- more : " + Lang.Translate(player, "chat-description-more"));
}
;
switch (submenu) {
default : {
message.Append(Lang.Translate(player, "chat-unknown-selection") + "\n");
appendMenus();
break;
}
case null : {
message.AppendLine(Lang.Translate(player, "chat-default-text"));
appendMenus();
break;
}
case "craft" : {
title = Lang.Translate(player, "chat-title-craft");
message.AppendLine(Lang.Translate(player, "chat-craft-text-top"));
foreach (var itemAmount in UpgradeCost) {
message.AppendLine("- " + itemAmount.amount + "x " + itemAmount.itemDef.displayName.english);
}
message.AppendLine();
message.AppendLine(Lang.Translate(player, "chat-craft-text-bottom"));
break;
}
case "usage" : {
title = Lang.Translate(player, "chat-title-usage");
message.AppendLine(Lang.Translate(player, "chat-usage-text"));
if (Utility.Config.ScanForWorldItems) {
message.AppendLine(Lang.Translate(player, "chat-usage-text-droptop"));
}
break;
}
case "more" : {
title = Lang.Translate(player, "chat-title-more");
message.AppendLine(Lang.Translate(player, "chat-more-text"));
break;
}
}
message.Insert(0, "<size=20>" + Lang.Translate(player, "chat-title") + (title != null ? (" - " + title) : "") + "</size>\n");
player.ChatMessage(message.ToString());
}
#endregion
// For keeping track of how long ago they requested with the previous hammer hit.Used for confirming by hitting twice with hammer to upgrade, downgrade, or repair.private readonly Dictionary<BasePlayer, float> lastHammerHit = new Dictionary<BasePlayer, float>();
// Return value:
// - null = continue with default behaviour of hammer hit
// - anything else: prevent default behaviour.private object HandleUpgradeRequest(BasePlayer player, ResearchTable researchTable)
{
if (UpgradeCost.Count > 0) {
if (!player.CanCraft(UpgradeCost)) {
StringBuilder builder = new StringBuilder();
foreach (var ingredient in UpgradeCost) {
builder.AppendLine("- x" + ingredient.amount.ToString("0") + " " + ingredient.itemDef.displayName.english);
}
string ingredientsStr = builder.ToString();
player.ShowScreenMessage(Lang.Translate(player, "ingredients-missing-youneed") + "\n" + ingredientsStr, 10, TextAnchor.MiddleLeft);
return true;
}
}
float lastHit = lastHammerHit[player];
if (Time.time - lastHit > Constants.HammerConfirmTime) // Confirm the upgrade
{
lastHammerHit[player] = Time.time;
player.ShowScreenMessage(Lang.Translate(player, "hammer-confirm-upgrade"), Constants.HammerConfirmTime);
return true;
}
lastHammerHit[player] = 0;
// Reset time
foreach (var ingredient in UpgradeCost) {
List<Item> takenItems = new List<Item>();
player.inventory.Take(takenItems, ingredient.itemid, (int)ingredient.amount);
}
CrafterManager.CreateCrafter(researchTable);
FxManager.PlayFx(researchTable.ServerPosition, Constants.UpgradeTopTierFxPrefab);
player.HideScreenMessage();
return true;
}
// Return value:
// - null = continue with default behaviour of hammer hit
// - anything else: prevent default behaviour.private object HandleDowngradeRequest(BasePlayer player, Crafter crafter)
{
float lastRequest = lastHammerHit[player];
if (Time.time - lastRequest > Constants.HammerConfirmTime) // Confirm the downgrade
{
string message = Lang.Translate(player, "hp-message", crafter.Recycler.Health(), crafter.Recycler.MaxHealth());
message += "\n\n" + Lang.Translate(player, "hammer-confirm-downgrade");
lastHammerHit[player] = Time.time;
player.ShowScreenMessage(message, Constants.HammerConfirmTime);
return true;
}
lastHammerHit[player] = 0;
// Reset time
CrafterManager.DestroyCrafter(crafter, true, false);
FxManager.PlayFx(crafter.Position, Constants.UpgradeMetalFxPrefab);
player.HideScreenMessage();
foreach (var itemAmount in UpgradeCost) {
player.GiveItem(ItemManager.CreateByItemID(itemAmount.itemid, (int) itemAmount.amount));
}
// Refund codelock if one is attached
if (crafter.CodeLock != null) {
var item = ItemManager.Create(ItemManager.FindItemDefinition("lock.code"));
player.GiveItem(item);
}
return true;
}
private void ShowJoinMessage(BasePlayer player) {
if (!Utility.Config.ShowPlayerInstructionsOnFirstJoin || !permission.UserHasPermission(player.UserIDString, Constants.UsePermission) || introducedPlayers.Contains(player.userID)) return;
string message = Lang.Translate(player, "join-message");
if (Utility.Config.ShowInstructionsAsGameTip) player.ShowGameTip(message, 10f);
else player.ChatMessage(message);
introducedPlayers.Add(player.userID);
}
}
}
namespace Oxide.Plugins.AutoCrafterNamespace {
public static class Constants {
public const string ItemDropPrefab = "assets/prefabs/misc/item drop/item_drop.prefab";
public const string StaticRecyclerPrefab = "assets/bundled/prefabs/static/recycler_static.prefab";
public const string DeployedResearchTablePrefab = "assets/prefabs/deployable/research table/researchtable_deployed.prefab";
public const string StackSoundFxPrefab = "assets/bundled/prefabs/fx/notice/stack.world.fx.prefab";
public const string UpgradeTopTierFxPrefab = "assets/bundled/prefabs/fx/build/promote_toptier.prefab";
public const string UpgradeMetalFxPrefab = "assets/bundled/prefabs/fx/build/promote_metal.prefab";
public const string CodelockPrefab = "assets/prefabs/locks/keypad/lock.code.prefab";
public const string CodelockPlaceSoundPrefab = "assets/prefabs/locks/keypad/effects/lock-code-deploy.prefab";
public const int RecyclerNumInputSlots = 6;
public const float CrafterNearRadius = 0.6f;
public const float HammerConfirmTime = 2f;
public const float TimeToUpgrade = 600f;
public const string UsePermission = "autocrafter.use";
}
}
namespace Oxide.Plugins.AutoCrafterNamespace {
public class Crafter {
public delegate void PlayerEnterDelegate(Crafter crafter, BasePlayer player);
public delegate void PlayerLeaveDelegate(Crafter crafter, BasePlayer player);
public class CraftTask {
[JsonIgnore]
public ItemBlueprint Blueprint;
public int Amount;
public int SkinID;
[JsonProperty("ItemID")]
private int _itemid => Blueprint.targetItem.itemid;
[JsonIgnore]
public List<Item> TakenItems {
get;
set;
}
= new List<Item>();
[JsonProperty("TakenItems")]
private object _takenItems => TakenItems.Select(item => new {
item.info.itemid, item.skin, item.amount
}
).ToList();
/// <summary>
/// Number of seconds this has been crafting for./// </summary>
public float Elapsed;
public CraftTask(ItemBlueprint blueprint, int amount, int skinId) {
Blueprint = blueprint;
Amount = amount;
SkinID = skinId;
}
}
[JsonIgnore]
public Recycler Recycler {
get;
private set;
}
[JsonIgnore]
public Vector3 Position => Recycler.ServerPosition;
/// <summary>
/// Gets a list of players that are near the crafter, and should receive craft queue updates and be able to add/delete from queue
/// </summary>
[JsonIgnore]
public List<BasePlayer> NearbyPlayers {
get;
private set;
}
= new List<BasePlayer>();
public List<CraftTask> CraftingTasks {
get;
private set;
}
= new List<CraftTask>();
[JsonIgnore]
public ItemContainer OutputInventory => outputInventory;
[JsonIgnore]
public DroppedItemContainer Output => outputContainer;
/// <summary>
/// Gets the codelock on this crafter.May be null./// </summary>
[JsonIgnore]
public CodeLock CodeLock {
get;
private set;
}
#region Json exclusive properties for saving/loading
[JsonProperty("Code")]
private string _code => CodeLock?.code;
[JsonProperty("GuestCode")]
private string _guestCode => CodeLock?.guestCode;
[JsonProperty("AuthedPlayers")]
private List<ulong> _authedPlayers => CodeLock?.whitelistPlayers;
[JsonProperty("GuestPlayers")]
private List<ulong> _guestPlayers => CodeLock?.guestPlayers;
[JsonProperty("HasCodeLock")]
private bool _hasCodelock => CodeLock != null;
[JsonProperty("IsLocked")]
private bool _locked => CodeLock?.IsLocked() ?? false;
[JsonProperty("OutputItems")]
private object _outputItems => OutputInventory.itemList.Select(item => {
if (item.info.itemid == 98228420) // Hidden item
return null;
return new {
item.position, item.info.itemid, item.amount, item.skin
}
;
}
).Where(obj => obj != null).ToList();
[JsonProperty("On")]
private bool _turnedOn => Recycler.IsOn();
[JsonProperty("Health")]
private float _health => Recycler.Health();
[JsonProperty("DecayTimer")]
private float _decayTimer => (float) (typeof (DecayEntity).GetField("decayTimer", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(Recycler) ?? 0f);
#endregion
public event PlayerEnterDelegate PlayerEnter;
public event PlayerLeaveDelegate PlayerLeave;
/// <summary>
/// Gets or sets the time this was created in UTC./// </summary>
public DateTime CreationTime {
get;
set;
}
// Lookup table for players on each crafting task.private readonly Dictionary<BasePlayer, Dictionary<CraftTask, int>> taskLookup = new Dictionary<BasePlayer, Dictionary<CraftTask, int>>();
private DroppedItemContainer outputContainer;
private ItemContainer outputInventory;
private readonly Timer resetDespawnTimer;
private float nextPickup = Time.time;
private const float pickupDelay = 0.5f;
private float nextUiUpdate = Time.time;
private const float uiUpdateDelay = 0.5f;
/// <param name="recycler">The recycler entity we're "overwriting".</param>
public Crafter(Recycler recycler) {
CreationTime = DateTime.UtcNow;
Recycler = recycler;
CreateOutputContainer();
// Reset despawn timer on loot bag once per minute.resetDespawnTimer = Utility.Timer.Every(60, () =>
{
if (!outputContainer.IsDestroyed) outputContainer.ResetRemovalTime();
}
);
recycler.gameObject.AddComponent<GroundWatch>();
recycler.gameObject.AddComponent<DestroyOnGroundMissing>();
recycler.repair.enabled = true;
recycler.repair.itemTarget = ItemManager.FindItemDefinition("wall.frame.shopfront.metal");
// Set health to 1000
Recycler._maxHealth = 1000;
Recycler.health = recycler.MaxHealth();
// Set up damage protection
Recycler.baseProtection.density = 4;
for (int i = 0; i < Recycler.baseProtection.amounts.Length; ++i) {
Recycler.baseProtection.amounts[i] = Utility.Config.CrafterProtectionProperties[i];
}
// Set up decay
var researchPrefab = GameManager.server.FindPrefab(Constants.DeployedResearchTablePrefab);
// Copying decay settings from research table
if (researchPrefab == null) {
Utility.LogWarning("Could not find research table prefab, skipping decay setup");
}
else {
uint prefabID = researchPrefab.GetComponent<BaseEntity>().prefabID;
var decay = PrefabAttribute.server.Find<Decay>(prefabID);
var decayPoints = PrefabAttribute.server.FindAll<DecayPoint>(prefabID);
typeof (DecayEntity).GetField("decay", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(Recycler, decay);
typeof (DecayEntity).GetField("decayPoints", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(Recycler, decayPoints);
BuildingManager.DecayEntities.Add(Recycler);
}
}
private void CreateOutputContainer() {
Vector3 position = Position + (Recycler.transform.forward * 0f) + (Recycler.transform.up * 0.72f) + (Recycler.transform.right * -0.25f);
Quaternion rotation = Recycler.ServerRotation * Quaternion.Euler(90, 0, 0);
outputContainer = CreateItemContainer(position, rotation, Lang.Translate(null, "crafted-items"), out outputInventory);
var rigidBody = outputContainer.GetComponent<Rigidbody>();
rigidBody.isKinematic = true;
// Prevent physics from moving the container.// Add a hidden inventory slot in output container to prevent it from despawning when closing empty loot.outputInventory.capacity = 37;
var item = ItemManager.Create(ItemManager.FindItemDefinition("gears"), 1);
item.MoveToContainer(outputInventory, outputInventory.capacity - 1);
}
public void Tick(float elapsed) {
if (outputContainer.IsDestroyed) {
CrafterManager.DestroyCrafter(this, false, destroyOutputContainer : false);
// Don't destroy output container because it's already destroyed.}
ProcessWorldItems();
ProcessNearbyPlayers();
ProcessQueue(elapsed);
ProcessUiUpdates();
}
/// <param name="unloading">Specify true if the plugin is unloading.</param>
public void Destroy(bool destroyOutputContainer, bool unloading = false) {
resetDespawnTimer.DestroyToPool();
foreach (var player in NearbyPlayers) {
OnPlayerLeave(player);
}
if (!unloading) {
// Drop queue items
if (CraftingTasks.Count > 0) {
var container = new ItemContainer();
container.ServerInitialize(null, 36);
foreach (var task in CraftingTasks) {
foreach (var ingredient in task.Blueprint.ingredients) {
var item = ItemManager.CreateByItemID(ingredient.itemid, (int) ingredient.amount * task.Amount);
if (!item.MoveToContainer(container)) item.Drop(Position + Recycler.transform.up * 1.25f, Recycler.GetDropVelocity(), Recycler.ServerRotation);
}
}
var droppedContainer = container.Drop(Constants.ItemDropPrefab, Position + Recycler.transform.up * 1.25f, Recycler.ServerRotation);
droppedContainer.playerName = Lang.Translate(null, "queue-items");
}
}
Recycler.Kill();
CodeLock?.Kill();
if (!outputContainer.IsDestroyed) {
// Remove rock from output container that keeps it from despawning when emptied
outputInventory.GetSlot(outputInventory.capacity - 1).Remove();
// Force kill output bag if there's nothing in it.if (!destroyOutputContainer && OutputInventory.AnyItems())
{
// Enable physics on output container
outputContainer.GetComponent<Rigidbody>().isKinematic = false;
}
else {
outputContainer.Kill();
}
}
}
private void ProcessQueue(float elapsed) {
if (!Recycler.IsOn() || CraftingTasks.Count <= 0) return;
var currentTask = CraftingTasks.FirstOrDefault();
if (currentTask != null) {
currentTask.Elapsed += elapsed;
if (currentTask.Elapsed >= currentTask.Blueprint.time) {
ulong workshopSkinId = Rust.Global.SteamServer.Inventory.FindDefinition(currentTask.SkinID)?.GetProperty<ulong>("workshopdownload") ?? 0;
if (workshopSkinId == 0) workshopSkinId = (ulong)currentTask.SkinID;
var item = ItemManager.CreateByItemID(currentTask.Blueprint.targetItem.itemid, currentTask.Blueprint.amountToCreate, workshopSkinId);
if (!GiveItem(item)) {
item.Drop(Recycler.GetDropPosition(), Recycler.GetDropVelocity());
Recycler.StopRecycling();
}
currentTask.Amount -= 1;
currentTask.Elapsed -= currentTask.Blueprint.time;
// Take used items
foreach (var ingredient in currentTask.Blueprint.ingredients) {
foreach (var taskItem in currentTask.TakenItems) {
if (taskItem.info.itemid != ingredient.itemid) continue;
taskItem.amount -= (int) ingredient.amount;
if (taskItem.amount <= 0) {
taskItem.Remove();
currentTask.TakenItems.Remove(taskItem);
}
break;
}
}
if (currentTask.Amount <= 0) {
// Remove from ui
foreach (var player in NearbyPlayers) {
SendRemoveCraftingTask(player, currentTask);
}
CraftingTasks.RemoveAt(0);
// Stop recycler if there's nothing more to craft.if (CraftingTasks.Count <= 0)
{
Recycler.StopRecycling();
}
}
else {
foreach (var player in NearbyPlayers) {
SendCraftingTaskProgress(player, currentTask);
}
}
}
}
}
private void ProcessWorldItems() {
if (Utility.Config.ScanForWorldItems && Recycler.IsOn()) {
List<BaseEntity> entities = new List<BaseEntity>();
Vector3 position = Position + (Recycler.transform.up * 1.5f) + (Recycler.transform.forward * 0.1f) + (Recycler.transform.right * -0.25f);
float radius = 0.3f;
Vis.Entities(position, radius, entities);
entities = entities.Where(ent => ent.GetComponent<WorldItem>() != null).ToList();
if (nextPickup <= Time.time) {
foreach (var entity in entities) {
if (nextPickup > Time.time) break;
var worldItem = (WorldItem) entity;
bool partiallyInserted = false;
for (int i = 0; i < outputInventory.capacity - 1; ++i) {
var slot = outputInventory.GetSlot(i);
if (slot == null) {
worldItem.item.MoveToContainer(outputInventory, i);
partiallyInserted = true;
break;
}
if (slot.info == worldItem.item.info && slot.skin == worldItem.item.skin && slot.amount < slot.info.stackable) {
int available = slot.info.stackable - slot.amount;
int toMove = Math.Min(available, worldItem.item.amount);
worldItem.item.amount -= toMove;
slot.amount += toMove;
slot.MarkDirty();
partiallyInserted = true;
if (worldItem.item.amount <= 0) {
worldItem.item.Remove();
worldItem.Kill();
break;
}
}
}
if (partiallyInserted) {
FxManager.PlayFx(worldItem.ServerPosition, Constants.StackSoundFxPrefab);
}
}
}
}
}
private void ProcessNearbyPlayers() {
List<BasePlayer> nearPlayers = new List<BasePlayer>();
Vector3 checkPosition = Position + Recycler.transform.up * 0.75f + Recycler.transform.forward * 1f + Recycler.transform.right * -0.2f;
float checkRadius = Constants.CrafterNearRadius;
Vis.Entities(checkPosition, checkRadius, nearPlayers);
var previousNearbyPlayers = NearbyPlayers.ToList();
// Nearby players last tick
// Keep all players that are the following:
// - Alive and not sleeping
// - Has codelock access
// - Can see the recycler from their position, aka not behind a wall or anything
nearPlayers = nearPlayers.Where(plr => plr.IsAlive() && !plr.IsSleeping() && PlayerCanAccess(plr) && Recycler.IsVisible(plr.ServerPosition)).ToList();
var playersLeaving = previousNearbyPlayers.Where(plr => !nearPlayers.Contains(plr)).ToList();
var playersEntering = nearPlayers.Where(plr => !previousNearbyPlayers.Contains(plr)).ToList();
foreach (var player in playersLeaving) {
NearbyPlayers.Remove(player);
OnPlayerLeave(player);
}
foreach (var player in playersEntering) {
NearbyPlayers.Add(player);
OnPlayerEnter(player);
}
/*foreach (var player in BasePlayer.activePlayerList)
{
player.SendConsoleCommand("ddraw.sphere", 0.5f, Color.red, checkPosition, checkRadius);
}*/
}
private void ProcessUiUpdates() {
if (!(Time.time > nextUiUpdate)) return;
nextUiUpdate = Time.time + uiUpdateDelay;
foreach (var player in NearbyPlayers) {
SendCraftingListUpdate(player);
}
}
/// <summary>
/// Called when a player comes into range of this crafter./// </summary>
private void OnPlayerEnter(BasePlayer player) {
if (CraftingTasks.Count > 0) {
SendCraftingList(player);
}
PlayerEnter?.Invoke(this, player);
}
/// <summary>
/// Called when a player goes out of range of this crafter./// </summary>
private void OnPlayerLeave(BasePlayer player) {
SendClearCraftingList(player);
PlayerLeave?.Invoke(this, player);
}
private void SendCraftingList(BasePlayer player) {
foreach (var task in CraftingTasks) {
SendAddCraftingTask(player, task);
}
}
private void SendCraftingListUpdate(BasePlayer player) {
foreach (var task in CraftingTasks) {
SendUpdateCraftingTask(player, task);
}
}
private void SendAddCraftingTask(BasePlayer player, CraftTask task) {
var crafting = player.inventory.crafting;
crafting.taskUID++;
// The reason for always sending 2 as amount is because if a craft task is started with 1 item, the amount counter won't show in clientside, even if amount is incremented later.// The real amount will be sent straight after, but then it will show with the counter, even if there's only 1.player.Command("note.craft_add", crafting.taskUID, task.Blueprint.targetItem.itemid, 2, task.SkinID);
// Correct the craft amount.player.Command("note.craft_done", crafting.taskUID, 0, task.Amount);
var dict = GetTaskLookupDict(player);
dict.Add(task, crafting.taskUID);
}
private void SendUpdateCraftingTask(BasePlayer player, CraftTask task) {
var lookup = GetTaskLookupDict(player);
int taskUID = lookup[task];
float time = task.Blueprint.time - task.Elapsed;
if (!Recycler.IsOn()) time = (float) Math.Ceiling(time) - 0.01f;
player.Command("note.craft_start", taskUID, time, task.Amount);
}
private void SendCraftingTaskProgress(BasePlayer player, CraftTask task) {
var lookup = GetTaskLookupDict(player);
var taskUID = lookup[task];
player.Command("note.craft_done", taskUID, 0, task.Amount);
}
private void SendClearCraftingList(BasePlayer player) {
var lookup = GetTaskLookupDict(player);
foreach (var kv in lookup.ToDictionary(kv => kv.Key, kv => kv.Value)) {
SendRemoveCraftingTask(player, kv.Key);
}
}
private void SendRemoveCraftingTask(BasePlayer player, CraftTask task) {
var lookup = GetTaskLookupDict(player);
int taskUID = lookup[task];
player.Command("note.craft_done", taskUID, 0);
lookup.Remove(task);
}
private Dictionary<CraftTask, int> GetTaskLookupDict(BasePlayer player) {
if (taskLookup.ContainsKey(player)) return taskLookup[player];
var dictionary = new Dictionary<CraftTask, int>();
taskLookup.Add(player, dictionary);
return dictionary;
}
#region Public api methods
public CraftTask AddCraftTask(ItemBlueprint blueprint, int amount, int skinId = 0, bool startRecycler = true, List<Item> takenItems = null) {
bool wasEmpty = CraftingTasks.Count == 0;
// Merge with current craft queue if the item is in queue with matching skin.var craftTask = CraftingTasks.FirstOrDefault(task => task.Blueprint.targetItem.itemid == blueprint.targetItem.itemid && task.SkinID == skinId);
if (craftTask != null) {
craftTask.Amount += amount;
// Send new amount to all players
foreach (var player in NearbyPlayers) {
SendCraftingTaskProgress(player, craftTask);
}
return craftTask
// ... [Code preview truncated due to length. Full source in attached file] ...