C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using System.Collections.Generic;
using System;
using System.Linq;
using System.Reflection;
using UnityEngine;
using static BaseEntity;
namespace Oxide.Plugins {
[Info("Deployable Zipline", "BlackLightning", "1.5.2")]
public class DeployableZipline : CovalencePlugin {
#region Fields
[PluginReference]
private Plugin Economics, NoEscape, ServerRewards;
private const string DespawnConfigName = "DespawnConfig";
private const string PermissionProtect = "deployablezipline.protect";
private const string PermissionBuyTool = "deployablezipline.buy.tool";
private const string PermissionBuyCable = "deployablezipline.buy.cable";
private const string TeslaCoilPrefab = "assets/prefabs/deployable/playerioents/teslacoil/teslacoil.deployed.prefab";
private const string LaunchPointPrefab = "assets/prefabs/building/powerline.zipline/ziplinelaunchpoint.prefab";
private const string RemoverToolRefundItemName = "ZiplineCable";
private const int AllowIgnoreLayers = 1 << (int)Rust.Layer.Deployed | 1 << (int)Rust.Layer.Tree;
private const int MountCheckMask = 1 << (int)Rust.Layer.World | 1 << (int)Rust.Layer.Terrain | 1 << (int)Rust.Layer.Construction | 1 << (int)Rust.Layer.Prevent_Movement;
private const float LowerSpearOffset = 0.37f;
private const float UpperSpearOffset = 2.2f;
private static readonly object False = false;
private static readonly Vector3 TeslaCoilCableLocalOffset = new Vector3(0, 0.5f, 0);
private static readonly Vector3 SpearCableLocalOffset = new Vector3(0, 2.6f, 0);
private static readonly Vector3[] ExtraDismountPositions = {
new Vector3(0, -1, 0)
}
;
private delegate void CalculateLinePointsDelegate(ZiplineLaunchPoint launchPoint, List<Vector3> targets, ref List<Vector3> points);
private static readonly CalculateLinePointsDelegate ZLP_CalculateLinePointsDelegate = (CalculateLinePointsDelegate)Delegate.CreateDelegate(typeof(CalculateLinePointsDelegate), typeof(ZiplineLaunchPoint).GetMethod("CalculateZiplinePoints", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
private static readonly FieldInfo ZLP_LinePointsField = typeof(ZiplineLaunchPoint).GetField("linePoints", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
private ItemDefinition _hammerItemDefinition;
private ItemDefinition _toolgunItemDefinition;
private ItemDefinition _spearItemDefinition;
private ItemDefinition _laserSightItemDefinition;
private ProtectionProperties _immortalProtection;
private Configuration _config;
private SavedData _data;
private ZiplineManager _ziplineManager;
private ToolSessionManager _toolSessionManager;
private EconomicsPaymentProvider _economicsPaymentProvider;
private ServerRewardsPaymentProvider _serverRewardsPaymentProvider;
private readonly PreferencesManager _preferencesManager = new PreferencesManager();
private readonly CooldownManager _toolCooldownManager = new CooldownManager();
private readonly RemovableEntityInfo _removableEntityInfo = new RemovableEntityInfo();
public DeployableZipline() {
_economicsPaymentProvider = new EconomicsPaymentProvider(this);
_serverRewardsPaymentProvider = new ServerRewardsPaymentProvider(this);
}
#endregion
#region Hooks
private void Init() {
_config.Init(this);
_data = SavedData.Load();
_ziplineManager = new ZiplineManager(this);
_toolSessionManager = new ToolSessionManager(this, _ziplineManager, _toolCooldownManager, _config, _data);
permission.RegisterPermission(PermissionProtect, this);
permission.RegisterPermission(PermissionBuyTool, this);
permission.RegisterPermission(PermissionBuyCable, this);
if (_config.CableItem?.ItemId == 0 || _config.CableItem?.ItemSkinId == 0) {
Unsubscribe(nameof(CanBeRecycled));
}
}
private void OnServerInitialized() {
}
private void OnServerInitialized(bool initial) {
_hammerItemDefinition = ItemManager.FindItemDefinition("hammer");
_toolgunItemDefinition = ItemManager.FindItemDefinition("toolgun");
_spearItemDefinition = ItemManager.FindItemDefinition("speargun.spear");
_laserSightItemDefinition = ItemManager.FindItemDefinition("weapon.mod.lasersight");
_immortalProtection = ScriptableObject.CreateInstance<ProtectionProperties>();
_immortalProtection.name = $"{nameof(DeployableZipline)}Protection";
_immortalProtection.Add(1);
_ziplineManager.OnServerInitialized(initial);
foreach (var player in BasePlayer.activePlayerList) {
if (player.IsSleeping()) continue;
OnActiveItemChanged(player, null, player.GetActiveItem());
}
}
private void OnNewSave() {
_data.Reset();
}
private void OnServerSave() {
_data.SaveIfNeeded();
}
private void Unload() {
_data.SaveIfNeeded();
_ziplineManager.Unload();
UnityEngine.Object.Destroy(_immortalProtection);
}
private void OnPluginLoaded(Plugin plugin) {
if (plugin.Name == DespawnConfigName) {
_ziplineManager.CancelItemDespawn();
}
}
private void OnActiveItemChanged(BasePlayer player, Item oldItem, Item newItem) {
if (!_config.ToolItem.ItemMatches(oldItem) && _config.ToolItem.ItemMatches(newItem)) {
var profile = _config.GetToolProfile(permission, player.UserIDString);
if (profile == null) {
ChatMessage(player, LangEntry.SelectErrorNoPermission);
return;
}
_toolSessionManager.StartPlayerSession(player, profile);
var extraInfo = profile.AllowBidirectional ? (_preferencesManager.IsBidirectionalEnabled(player) ? $" {GetMessage(player.UserIDString, LangEntry.SelectInfoBidirectionalEnabled)}" : $" {GetMessage(player.UserIDString, LangEntry.SelectInfoBidirectionalDisabled)}") : string.Empty;
ChatMessage(player, LangEntry.SelectInfo, extraInfo);
}
}
private object CanCombineDroppedItem(DroppedItem itemEntity, DroppedItem otherItemEntity) {
var item = itemEntity.item;
var otherItem = otherItemEntity.item;
if (item.info == _spearItemDefinition) {
if (_ziplineManager.HasEntity(itemEntity) || _ziplineManager.HasEntity(otherItemEntity)) return False;
return null;
}
var cableItem = _config.CableItem;
if (cableItem.ItemId != 0 && cableItem.ItemSkinId != 0) {
if (item.info.itemid != cableItem.ItemId || otherItem.info.itemid != cableItem.ItemId) return null;
if ((item.skin == cableItem.ItemSkinId) != (otherItem.skin == cableItem.ItemSkinId)) return False;
return null;
}
return null;
}
// Detect when the Spear is picked up.private object OnItemPickup(Item item, BasePlayer player)
{
if (item.info != _spearItemDefinition) return null;
var entity = item.GetWorldEntity() as DroppedItem;
if (entity == null) return null;
var controller = _ziplineManager.GetController(entity);
if (controller == null) return null;
if (_config.PickupRestrictions.RequireToolToPickUp) {
var activeItem = player.GetActiveItem();
var activeItemDefinition = activeItem?.info;
if (activeItemDefinition != _hammerItemDefinition && activeItemDefinition != _toolgunItemDefinition && !_config.ToolItem.ItemMatches(activeItem)) {
ChatMessage(player, LangEntry.PickupErrorToolRequired);
return False;
}
}
if (!VerifyCanPickupZiplinePoint(player, entity, controller)) return False;
GiveRefund(player, controller);
entity.ClientRPC(null, "PickupSound");
item.RemoveFromWorld();
item.Remove();
player.SignalBroadcast(Signal.Gesture, "pickup_item");
return False;
}
// Detect when the Tesla Coil is picked up.private object CanPickupEntity(BasePlayer player, TeslaCoil entity)
{
var controller = _ziplineManager.GetController(entity);
if (controller == null) return null;
if (!VerifyCanPickupZiplinePoint(player, entity, controller)) return False;
GiveRefund(player, controller);
controller.Kill();
return False;
}
private object CanMountEntity(BasePlayer player, ZiplineMountable ziplineMountable) {
var mountPosition = ziplineMountable.endPosition;
var controller = _ziplineManager.GetControllerByLaunchPoint(mountPosition);
if (controller == null) return null;
var capsuleHeight = 1.2f;
var capsuleRadius = 0.4f;
var pointA = mountPosition - new Vector3(0, capsuleRadius, 0);
var pointB = mountPosition - new Vector3(0, capsuleRadius + capsuleHeight, 0);
if (Physics.CheckCapsule(pointA, pointB, capsuleRadius, MountCheckMask, QueryTriggerInteraction.Ignore)) {
ChatMessage(player, LangEntry.MountErrorNoSpace);
return False;
}
return null;
}
private void OnEntityMounted(ZiplineMountable ziplineMountable, BasePlayer player) {
var mountPosition = ziplineMountable.endPosition;
bool isForwardLaunchPoint;
var controller = _ziplineManager.GetControllerByLaunchPoint(mountPosition, out isForwardLaunchPoint);
if (controller == null) return;
// Raise the mount anchor so the player is where the mountable is (does not affect visuals).// This reduces the likelihood that they will technically clip into terrain violation.ziplineMountable.mountAnchor.localPosition = ziplineMountable.mountAnchor.localPosition.WithY(0);
AddDismountPositions(ziplineMountable, ExtraDismountPositions);
var ignoreColliders = ziplineMountable.ignoreColliders;
if (ignoreColliders != null) {
for (var i = ignoreColliders.Count - 1; i >= 0; i--) {
var collider = ignoreColliders[i];
if (((1 << collider.gameObject.layer) & AllowIgnoreLayers) == 0) {
ignoreColliders.RemoveAt(i);
}
}
}
var speedProfile = _config.GetSpeedProfile(permission, player.UserIDString);
if (speedProfile != null) {
Vector3 startPoint;
Vector3 endPoint;
if (isForwardLaunchPoint) {
startPoint = controller.Data.StartPoint.Position;
endPoint = controller.Data.EndPoint.Position;
}
else {
startPoint = controller.Data.EndPoint.Position;
endPoint = controller.Data.StartPoint.Position;
}
var directionalMultiplier = 1f;
var angle = AngleBetween(startPoint, endPoint);
if (angle > 0) {
var speedPenalty = speedProfile.UphillSpeedPenalty;
directionalMultiplier = Mathf.Max(speedPenalty.MinSpeedPercent, 100f - angle * speedPenalty.PercentDecreasePerAngle) / 100f;
}
else if (angle < 0) {
var speedBonus = speedProfile.DownhillSpeedBonus;
directionalMultiplier = Mathf.Min(speedBonus.MaxSpeedPercent, 100f - angle * speedBonus.PercentIncreasePerAngle) / 100f;
}
ziplineMountable.MoveSpeed = speedProfile.MoveSpeed * directionalMultiplier;
ziplineMountable.ForwardAdditive = speedProfile.BonusMoveSpeed * directionalMultiplier;
ziplineMountable.SpeedUpTime = speedProfile.AccelerationTimeSeconds;
}
}
// Only subscribed while Cable Item is valid and has a skin.private object CanBeRecycled(Item item, Recycler recycler)
{
if (item != null && item.skin == _config.CableItem?.ItemSkinId && item.info.Blueprint != null) return False;
return null;
}
// RemoverTool
private Dictionary<string, object> OnRemovableEntityInfo(BaseEntity entity, BasePlayer player) {
var controller = _ziplineManager.GetController(entity);
if (controller == null) return null;
_removableEntityInfo.SetDisplayName(GetMessage(player.UserIDString, LangEntry.ZiplineName));
var refundAmount = controller.GetRefundAmount();
if (refundAmount > 0) {
var cableName = GetMessage(player.UserIDString, LangEntry.CableName);
_removableEntityInfo.SetRefund(cableName, refundAmount);
}
else {
_removableEntityInfo.SetNoRefund();
}
return _removableEntityInfo.GetDictionary();
}
// RemoverTool
private void OnRemovableEntityGiveRefund(BaseEntity entity, BasePlayer player, string itemName, int amount) {
if (itemName != RemoverToolRefundItemName) return;
var controller = _ziplineManager.GetController(entity);
if (controller == null) return;
GiveRefund(player, controller);
}
// RemoverTool
private object canRemove(BasePlayer player, BaseEntity entity) {
var controller = _ziplineManager.GetController(entity);
if (controller == null) return null;
if (controller.IsBusy()) return GetMessage(player.UserIDString, LangEntry.PickupErrorGeneric);
if (controller.Data.Protected && !permission.UserHasPermission(player.UserIDString, PermissionProtect)) return GetMessage(player.UserIDString, LangEntry.ErrorNoPermission);
if (!_config.PickupRestrictions.AllowPickupWhileBuildingBlockedAtOtherEnd) {
var otherEntity = controller.GetOtherEntity(entity);
if (otherEntity == null || otherEntity.IsDestroyed) return False;
if (player.IsBuildingBlocked(otherEntity.WorldSpaceBounds())) {
return GetMessage(player.UserIDString, LangEntry.PickupErrorBuildingBlockedOtherEnd);
}
}
return null;
}
#endregion
#region Dependencies
private bool IsRaidBlocked(BasePlayer player) {
var result = NoEscape?.Call("IsRaidBlocked", player);
return result is bool && (bool)result;
}
private bool IsCombatBlocked(BasePlayer player) {
var result = NoEscape?.Call("IsCombatBlocked", player);
return result is bool && (bool)result;
}
#region Payment Providers
private interface IPaymentProvider {
int GetBalance(BasePlayer player);
bool TakeBalance(BasePlayer player, int amount);
}
private class EconomicsPaymentProvider : IPaymentProvider {
private DeployableZipline _plugin;
private Plugin _ownerPlugin => _plugin.Economics;
public EconomicsPaymentProvider(DeployableZipline plugin) {
_plugin = plugin;
}
public bool IsAvailable => _ownerPlugin != null;
public int GetBalance(BasePlayer player) {
return Convert.ToInt32(_ownerPlugin.Call("Balance", player.userID));
}
public bool TakeBalance(BasePlayer player, int amount) {
var result = _ownerPlugin.Call("Withdraw", player.userID, Convert.ToDouble(amount));
return result is bool && (bool)result;
}
}
private class ServerRewardsPaymentProvider : IPaymentProvider {
private DeployableZipline _plugin;
private Plugin _ownerPlugin => _plugin.ServerRewards;
public ServerRewardsPaymentProvider(DeployableZipline plugin) {
_plugin = plugin;
}
public bool IsAvailable => _ownerPlugin != null;
public int GetBalance(BasePlayer player) {
return Convert.ToInt32(_ownerPlugin.Call("CheckPoints", player.userID));
}
public bool TakeBalance(BasePlayer player, int amount) {
var result = _ownerPlugin.Call("TakePoints", player.userID, amount);
return result is bool && (bool)result;
}
}
private class ItemsPaymentProvider : IPaymentProvider {
private ItemDefinition _itemDefinition;
private ulong _skinId;
private int _itemId;
public ItemDefinition ItemDefinition {
get {
if (_itemDefinition == null) {
_itemDefinition = ItemManager.FindItemDefinition(_itemId);
}
return _itemDefinition;
}
}
public ItemsPaymentProvider(int itemId, ulong skinId) {
_itemId = itemId;
_skinId = skinId;
}
public int GetBalance(BasePlayer player) {
return SumPlayerItems(player, _itemId, _skinId);
}
public bool TakeBalance(BasePlayer player, int amount) {
if (amount <= 0) return true;
string displayName;
TakePlayerItems(player, _itemId, _skinId, amount, out displayName);
ShowGiveNotice(player, _itemId, -amount, displayName);
return true;
}
}
#endregion
#region Remover Tool Integration
private class RemovableEntityInfo {
private const string DisplayNameField = "DisplayName";
private const string RefundField = "Refund";
private const string AmountField = "Amount";
private Dictionary<string, object> _removeInfo;
private Dictionary<string, object> _refundInfo;
public RemovableEntityInfo() {
_refundInfo = new Dictionary<string, object> {
[AmountField]
= 0,
}
;
_removeInfo = new Dictionary<string, object> {
["Refund"]
= new Dictionary<string, object> {
[RemoverToolRefundItemName]
= _refundInfo,
}
,
}
;
}
public void SetDisplayName(string displayName) {
_removeInfo[DisplayNameField] = displayName;
}
public void SetRefund(string displayName, int amount) {
_refundInfo[DisplayNameField] = displayName;
_refundInfo[AmountField] = amount;
}
public void SetNoRefund() {
_refundInfo[AmountField] = 0;
}
public Dictionary<string, object> GetDictionary() {
return _removeInfo;
}
}
#endregion
#endregion
#region Commands
[Command("zipline")]
private void CommandZipline(IPlayer player, string cmd, string[] args) {
if (player.IsServer) return;
var basePlayer = player.Object as BasePlayer;
var firstArg = args.FirstOrDefault()?.ToLower();
switch (firstArg) {
case "buy" : {
var toolInfo = _config.ToolItem;
if (toolInfo.ItemDefinition == null) return;
if (!VerifyHasPermission(player, PermissionBuyTool)) return;
var costInfo = toolInfo.PurchaseInfo.Cost;
var paymentProvider = costInfo.GetPaymentProvider(_economicsPaymentProvider, _serverRewardsPaymentProvider);
var currentBalance = paymentProvider.GetBalance(basePlayer);
var currencyName = GetCurrencyName(player.Id, paymentProvider);
if (currentBalance < costInfo.Amount) {
ReplyToPlayer(player, LangEntry.PurchaseErrorInsufficientFunds, currentBalance, costInfo.Amount, currencyName);
return;
}
if (!paymentProvider.TakeBalance(basePlayer, costInfo.Amount)) {
ReplyToPlayer(player, LangEntry.PurchaseErrorPaymentFailed);
return;
}
var attachmentItemDefinitions = toolInfo.PurchaseInfo.AttachmentItemDefinitions;
var toolItem = toolInfo.CreateItem(1, "Zipline Tool");
if (toolItem.contents != null && attachmentItemDefinitions != null) {
foreach (var itemDefinition in attachmentItemDefinitions) {
toolItem.contents.AddItem(itemDefinition, 1);
}
}
if (attachmentItemDefinitions.Contains(_laserSightItemDefinition)) {
var heldEntity = toolItem.GetHeldEntity() as HeldEntity;
if (heldEntity != null) {
heldEntity.SetLightsOn(true);
}
}
var toolName = GetMessage(player.Id, LangEntry.ToolName);
GiveItem(basePlayer, toolItem, toolName, GiveItemReason.Crafted);
var message = GetMessage(player.Id, LangEntry.PurchaseToolSuccess, toolName);
if (costInfo.Amount > 0) {
message += $" {GetMessage(player.Id, LangEntry.PurchaseSuccessInfo, costInfo.Amount, currencyName)}";
}
player.Reply(message);
return;
}
case "buycable" : {
var cableInfo = _config.CableItem;
if (cableInfo.ItemDefinition == null) return;
if (!VerifyHasPermission(player, PermissionBuyCable)) return;
int buyAmount;
if (args.Length < 2 || !int.TryParse(args[1], out buyAmount)) {
ReplyToPlayer(player, LangEntry.PurchaseCableErrorSyntax, cmd);
return;
}
var costInfo = cableInfo.Cost;
var paymentProvider = costInfo.GetPaymentProvider(_economicsPaymentProvider, _serverRewardsPaymentProvider);
var currentBalance = paymentProvider.GetBalance(basePlayer);
var currencyName = GetCurrencyName(player.Id, paymentProvider);
var spendAmount = buyAmount * costInfo.Amount;
if (currentBalance < spendAmount) {
ReplyToPlayer(player, LangEntry.PurchaseErrorInsufficientFunds, currentBalance, spendAmount, currencyName);
return;
}
if (!paymentProvider.TakeBalance(basePlayer, spendAmount)) {
ReplyToPlayer(player, LangEntry.PurchaseErrorPaymentFailed);
return;
}
var cableItem = cableInfo.CreateItem(buyAmount, "Zipline Cable");
var cableName = GetMessage(player.Id, LangEntry.CableName);
GiveItem(basePlayer, cableItem, cableName, GiveItemReason.Crafted);
var message = GetMessage(player.Id, LangEntry.PurchaseCableSuccessInfo, buyAmount, cableName);
if (spendAmount > 0) {
message += $" {GetMessage(player.Id, LangEntry.PurchaseSuccessInfo, spendAmount, currencyName)}";
}
player.Reply(message);
return;
}
case "toggle" : {
var profile = _config.GetToolProfile(permission, player.Id);
if (profile == null) {
ReplyToPlayer(player, LangEntry.SelectErrorNoPermission);
return;
}
if (!profile.AllowBidirectional) {
ReplyToPlayer(player, LangEntry.BidirectionalErrorDisallowed);
return;
}
var isBidirectional = !_preferencesManager.IsBidirectionalEnabled(basePlayer);
_preferencesManager.SetBidirectional(basePlayer, isBidirectional);
var langKey = isBidirectional ? LangEntry.BidirectionalEnabled : LangEntry.BidirectionalDisabled;
ReplyToPlayer(player, langKey);
return;
}
case "protect" : {
if (!VerifyHasPermission(player, PermissionProtect)) return;
var protectionEnabled = !_preferencesManager.IsProtectionEnabled(basePlayer);
_preferencesManager.SetProtectionEnabled(basePlayer, protectionEnabled);
var langKey = protectionEnabled ? LangEntry.ProtectionEnabled : LangEntry.ProtectionDisabled;
ReplyToPlayer(player, langKey);
return;
}
default : {
// TODO: Print help commands
return;
}
}
}
#endregion
#region Helper Methods
private bool VerifyHasPermission(IPlayer player, string perm) {
if (player.HasPermission(perm)) return true;
ReplyToPlayer(player, LangEntry.ErrorNoPermission);
return false;
}
private bool VerifyCanPickupZiplinePoint(BasePlayer player, BaseEntity entity, ZiplineController controller) {
if (controller.IsBusy()) {
ChatMessage(player, LangEntry.PickupErrorGeneric);
return false;
}
if (controller.Data.Protected && !permission.UserHasPermission(player.UserIDString, PermissionProtect)) {
ChatMessage(player, LangEntry.ErrorNoPermission);
SendEffect(player, _config.Effects.PickupFailed);
return false;
}
if (player.IsBuildingBlocked() || player.IsBuildingBlocked(entity.WorldSpaceBounds())) {
ChatMessage(player, LangEntry.ErrorBuildingBlocked);
SendEffect(player, _config.Effects.PickupFailed);
return false;
}
if (!_config.PickupRestrictions.AllowPickupWhileBuildingBlockedAtOtherEnd) {
var otherEntity = controller.GetOtherEntity(entity);
if (otherEntity == null || otherEntity.IsDestroyed) return false;
if (player.IsBuildingBlocked(otherEntity.WorldSpaceBounds())) {
ChatMessage(player, LangEntry.PickupErrorBuildingBlockedOtherEnd);
SendEffect(player, _config.Effects.PickupFailed);
return false;
}
}
return true;
}
private void GiveRefund(BasePlayer player, ZiplineController controller) {
var cableCost = controller.Profile.CableCost;
if (cableCost == null || cableCost.CostPerMeter <= 0 || cableCost.RefundPerMeter <= 0) return;
var refundAmount = controller.GetRefundAmount();
if (refundAmount <= 0) return;
var refundItem = _config.CableItem.CreateItem(refundAmount);
if (refundItem == null) return;
var itemName = GetMessage(player.UserIDString, LangEntry.CableName);
GiveItem(player, refundItem, itemName, GiveItemReason.PickedUp);
}
private static void AddDismountPositions(ZiplineMountable ziplineMountable, Vector3[] dismountPositions) {
var originalLength = ziplineMountable.dismountPositions.Length;
Array.Resize(ref ziplineMountable.dismountPositions, originalLength + dismountPositions.Length);
for (var i = 0; i < dismountPositions.Length; i++) {
var transform = ziplineMountable.gameObject.CreateChild().transform;
transform.localPosition = dismountPositions[i];
ziplineMountable.dismountPositions[originalLength + i] = transform;
}
}
private static float AngleBetween(Vector3 pointA, Vector3 pointB) {
var directionalMultiplier = pointA.y < pointB.y ? 1 : -1;
var yDistance = Mathf.Abs(pointA.y - pointB.y);
var xZDistance = (pointA - pointB).MagnitudeXZ();
return directionalMultiplier * Mathf.Atan2(yDistance, xZDistance) * Mathf.Rad2Deg;
}
private static bool IsBuildingBlocked(BasePlayer player, Vector3 position) {
return player.IsBuildingBlocked(position, Quaternion.identity, default(Bounds));
}
private static bool UserHasPermission(Permission permission, UserData userData, string perm) {
return userData.Perms.Contains(perm) || permission.GroupsHavePermission(userData.Groups, perm);
}
private static string FormatTime(float seconds) {
return TimeSpan.FromSeconds(Math.Ceiling(seconds)).ToString("c");
}
private static BaseEntity FindEntity(ulong entityId) {
return BaseNetworkable.serverEntities.Find(new NetworkableId(entityId)) as BaseEntity;
}
private static void ShowGiveNotice(BasePlayer player, int itemId, int amount, string name = null, GiveItemReason reason = GiveItemReason.Generic) {
player.Command("note.inv", itemId, amount, name, (int)reason);
}
private static void GiveItem(BasePlayer player, Item item, string name, GiveItemReason reason = GiveItemReason.Generic) {
if (!player.inventory.GiveItem(item)) {
item.Drop(player.inventory.containerBelt.dropPosition, player.inventory.containerMain.dropVelocity);
}
ShowGiveNotice(player, item.info.itemid, item.amount, name, reason);
}
private static int SumContainerItems(ItemContainer container, int itemId, ulong skinId) {
var sum = 0;
foreach (var item in container.itemList) {
if (item.info.itemid != itemId) continue;
if (skinId != 0 && item.skin != skinId) continue;
sum += item.amount;
}
return sum;
}
private static int SumPlayerItems(BasePlayer player, int itemId, ulong skinId) {
return SumContainerItems(player.inventory.containerMain, itemId, skinId) + SumContainerItems(player.inventory.containerBelt, itemId, skinId);
}
private static int TakeContainerItems(ItemContainer container, int itemId, ulong skinId, int amountToTake, out string displayName) {
displayName = null;
if (amountToTake == 0) return 0;
var amountTaken = 0;
for (var i = container.itemList.Count - 1; i >= 0; i--) {
var item = container.itemList[i];
if (item.info.itemid != itemId) continue;
if (skinId != 0 && item.skin != skinId) continue;
displayName = item.name;
var takeAmount = Math.Min(item.amount, amountToTake - amountTaken);
item.amount -= takeAmount;
amountTaken += takeAmount;
if (item.amount <= 0) {
item.RemoveFromContainer();
item.Remove();
}
else {
item.MarkDirty();
}
if (amountTaken >= amountToTake) break;
}
return amountTaken;
}
private static int TakePlayerItems(BasePlayer player, int itemId, ulong skinId, int amountToTake, out string displayName) {
var amountTaken = TakeContainerItems(player.inventory.containerMain, itemId, skinId, amountToTake, out displayName);
if (amountTaken >= amountToTake) return amountTaken;
amountTaken += TakeContainerItems(player.inventory.containerBelt, itemId, skinId, amountToTake - amountTaken, out displayName);
return amountTaken;
}
private static void RunEffect(string effectPrefab, Vector3 position) {
if (string.IsNullOrWhiteSpace(effectPrefab)) return;
Effect.server.Run(effectPrefab, position);
}
private static void SendEffect(BasePlayer player, string effectPrefab, Vector3 position, Vector3 normal) {
if (string.IsNullOrWhiteSpace(effectPrefab)) return;
var effect = new Effect(effectPrefab, position, normal);
EffectNetwork.Send(effect, player.net.connection);
}
private static void SendEffect(BasePlayer player, string effectPrefab) {
if (string.IsNullOrWhiteSpace(effectPrefab)) return;
var effect = new Effect(effectPrefab, player, 0, Vector3.zero, Vector3.forward);
EffectNetwork.Send(effect, player.net.connection);
}
private static bool IsFacingUpward(Quaternion rotation) {
return Vector3.Dot(rotation * Vector3.up, Vector3.up) > 0.5f;
}
private static Vector3 GetZiplineWorldOffset(Quaternion pointRotation) {
var isTall = IsFacingUpward(pointRotation);
var localOffset = isTall ? SpearCableLocalOffset : TeslaCoilCableLocalOffset;
return pointRotation * localOffset;
}
private static void DestroyGroundWatch(BaseEntity entity) {
UnityEngine.Object.DestroyImmediate(entity.GetComponent<GroundWatch>());
UnityEngine.Object.DestroyImmediate(entity.GetComponent<DestroyOnGroundMissing>());
}
private static ZiplineLaunchPoint CreateLaunchPoint(Vector3 startPosition, Quaternion rotation, Vector3 endPosition, ulong ownerId) {
var launchPoint = GameManager.server.CreateEntity(LaunchPointPrefab, startPosition, rotation) as ZiplineLaunchPoint;
if (launchPoint == null) return null;
launchPoint.ziplineTargets.Add(endPosition);
launchPoint.OwnerID = ownerId;
launchPoint.EnableSaving(false);
launchPoint.Spawn();
if (ZLP_CalculateLinePointsDelegate != null && ZLP_LinePointsField != null) {
List<Vector3> linePoints = null;
ZLP_CalculateLinePointsDelegate.Invoke(launchPoint, launchPoint.ziplineTargets, ref linePoints);
ZLP_LinePointsField.SetValue(launchPoint, linePoints);
var lastPosition = linePoints[linePoints.Count - 1];
var direction = (lastPosition - linePoints[linePoints.Count - 2]).normalized;
linePoints.Add(lastPosition + direction * 1.5f);
}
return launchPoint;
}
private BaseEntity CreateZiplinePoint(ZiplinePointData pointData, ulong ownerId, bool isTall, bool isUpper = false) {
if (isTall) return CreateSpearPoint(pointData, ownerId, isUpper);
return CreateTeslaCoilPoint(pointData, ownerId);
}
private static void HideInputsAndOutputs(IOEntity ioEntity) {
foreach (var input in ioEntity.inputs) {
input.type = IOEntity.IOType.Generic;
}
foreach (var output in ioEntity.outputs) {
output.type = IOEntity.IOType.Generic;
}
}
private static bool TryRaycast(BasePlayer player, out RaycastHit hit, float maxDistance, int layerMask = Rust.Layers.Solid) {
return Physics.Raycast(player.eyes.HeadRay(), out hit, maxDistance, layerMask, QueryTriggerInteraction.Ignore);
}
private static bool TryCapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask = Rust.Layers.Solid) {
return Physics.CapsuleCast(point1, point2, radius, direction, out hitInfo, maxDistance, layerMask, QueryTriggerInteraction.Ignore);
}
private DroppedItem CreateSpearPoint(ZiplinePointData pointData, ulong ownerId, bool isUpper) {
var rotation = pointData.Rotation * Quaternion.LookRotation(isUpper ? Vector3.up : Vector3.down);
var position = pointData.Position + pointData.Rotation * new Vector3(0, isUpper ? UpperSpearOffset : LowerSpearOffset, 0);
var item = ItemManager.Create(_spearItemDefinition);
var spearEntity = item.CreateWorldObject(position, rotation) as DroppedItem;
if (spearEntity == null) {
item.Remove();
return null;
}
spearEntity.OwnerID = ownerId;
spearEntity.EnableSaving(false);
spearEntity.CancelInvoke(spearEntity.IdleDestroy);
spearEntity.syncPosition = false;
if (spearEntity.PositionTickFixedTime) {
spearEntity.CancelInvokeFixedTime(spearEntity.NetworkPositionTick);
}
else {
spearEntity.CancelInvoke(spearEntity.NetworkPositionTick);
}
UnityEngine.Object.DestroyImmediate(spearEntity.GetComponent<Rigidbody>());
UnityEngine.Object.DestroyImmediate(spearEntity.GetComponent<PhysicsEffects>());
return spearEntity;
}
private TeslaCoil CreateTeslaCoilPoint(ZiplinePointData pointData, ulong ownerId) {
var teslaCoil = GameManager.server.CreateEntity(TeslaCoilPrefab, pointData.Position, pointData.Rotation) as TeslaCoil;
if (teslaCoil == null) return null;
teslaCoil.baseProtection = _immortalProtection;
DestroyGroundWatch(teslaCoil);
HideInputsAndOutputs(teslaCoil);
teslaCoil.OwnerID = ownerId;
teslaCoil.EnableSaving(false);
teslaCoil.Spawn();
return teslaCoil;
}
#endregion
#region Preferences Manager
private class PlayerPreferences {
public bool Bidirectional;
public bool Protection;
}
private class PreferencesManager {
private readonly Dictionary<ulong, PlayerPreferences> _playerPreferences = new Dictionary<ulong, PlayerPreferences>();
public PlayerPreferences GetPlayerPreferences(BasePlayer player) {
PlayerPreferences preferences;
return _playerPreferences.TryGetValue(player.userID, out preferences) ? preferences : null;
}
public PlayerPreferences EnsurePreferences(BasePlayer player) {
var preferences = GetPlayerPreferences(player);
if (preferences == null) {
preferences = new PlayerPreferences();
_playerPreferences[player.userID] = preferences;
}
return preferences;
}
public bool IsBidirectionalEnabled(BasePlayer player) {
return GetPlayerPreferences(player)?.Bidirectional ?? false;
}
public void SetBidirectional(BasePlayer player, bool value) {
EnsurePreferences(player).Bidirectional = value;
}
public bool IsProtectionEnabled(BasePlayer player) {
return GetPlayerPreferences(player)?.Protection ?? false;
}
public void SetProtectionEnabled(BasePlayer player, bool value) {
EnsurePreferences(player).Protection = value;
}
}
#endregion
#region Tool Session Manager
private class ToolSessionManager {
private const float InputCooldownTime = 0.25f;
private const int LOSCheckMask = 1 << (int)Rust.Layer.Default | 1 << (int)Rust.Layer.Deployed | 1 << (int)Rust.Layer.World | 1 << (int)Rust.Layer.Construction | 1 << (int)Rust.Layer.Terrain | 1 << (int)Rust.Layer.Vehicle_Large | 1 << (int)Rust.Layer.Tree;
private class ToolSession {
public BasePlayer Player {
get;
}
public ToolProfile Profile {
get;
}
public float LastInputTime;
public ZiplinePointData StartPointData;
public BaseEntity StartHostEntity;
public ToolSession(BasePlayer player, ToolProfile profile) {
Player = player;
Profile = profile;
LastInputTime = UnityEngine.Time.time;
}
}
private DeployableZipline _plugin;
private ZiplineManager _ziplineManager;
private CooldownManager _toolCooldownManager;
private Configuration _config;
private SavedData _data;
private Timer _timer;
private Action _handleTimerCached;
private List<ToolSession> _sessions = new List<ToolSession>();
private PreferencesManager _preferencesManager => _plugin._preferencesManager;
public ToolSessionManager(DeployableZipline plugin, ZiplineManager ziplineManager, CooldownManager toolCooldownManager, Configuration config, SavedData data) {
_plugin = plugin;
_ziplineManager = ziplineManager;
_toolCooldownManager = toolCooldownManager;
_config = config;
_data = data;
_handleTimerCached = HandleTimer;
}
public void StartPlayerSession(BasePlayer player, ToolProfile profile) {
var session = new ToolSession(player, profile);
_sessions.Add(session);
if (_timer == null || _timer.Destroyed) {
_timer = _plugin.timer.Every(0, _handleTimerCached);
}
}
private bool CanUseWeapon(BasePlayer player) {
if (player == null || player.IsDestroyed || !player.IsConnected || player.IsDead()) return false;
var activeItem = player.GetActiveItem();
if (activeItem == null) return false;
return _config.ToolItem.ItemMatches(activeItem);
}
private bool VerifyLocationAcceptable(ToolSession session, PointRuleset ruleset, RaycastHit hit, bool isStart) {
var player = session.Player;
var collider = hit.collider;
if (!ruleset.AllowWhileBuildingBlocked && IsBuildingBlocked(player, hit.point)) {
var langEntry = isStart ? LangEntry.DeployStartErrorBuildingBlocked : LangEntry.DeployEndBuildingBlocked;
_plugin.ChatMessage(player, langEntry);
return false;
}
if (ruleset.DisallowedTopologyMask != 0) {
var topology = TerrainMeta.TopologyMap.GetTopology(hit.point);
if (!ruleset.ShouldAllowTopology(topology)) {
var langEntry = isStart ? LangEntry.DeployStartErrorLocationRestricted : LangEntry.DeployEndErrorLocationRestricted;
_plugin.ChatMessage(player, langEntry);
return false;
}
}
if (ruleset.AllowedLayerMask != 0 && !ruleset.ShouldAllowLayer(collider.gameObject.layer)) {
var langEntry = isStart ? LangEntry.DeployStartErrorSurfaceRestricted : LangEntry.DeployEndErrorSurfaceRestricted;
_plugin.ChatMessage(player, langEntry);
return false;
}
if (ruleset.MinHeightAboveTerrain > 0) {
var diff = hit.point.y - TerrainMeta.HeightMap.GetHeight(hit.point);
if (diff < ruleset.MinHeightAboveTerrain) {
_plugin.ChatMessage(player, LangEntry.DeployErrorTooCloseToTerrain, diff, ruleset.MinHeightAboveTerrain);
return false;
}
}
if (ruleset.MaxDeployDistance > 0) {
var distance = Vector3.Distance(player.eyes.position, hit.point);
if (distance > ruleset.MaxDeployDistance) {
var langEntry = isStart ? LangEntry.DeployStartErrorMaxDistance : LangEntry.DeployEndErrorMaxDistance;
_plugin.ChatMessage(player, langEntry, distance, ruleset.MaxDeployDistance);
return false;
}
}
var entity = collider.ToBaseEntity();
if (entity != null && collider.GetComponentInParent<Rigidbody>() != null) {
var langKey = isStart ? LangEntry.DeployStartErrorObjectRestricted : LangEntry.DeployEndErrorObjectRestricted;
_plugin.ChatMessage(player, langKey);
return false;
}
return true;
}
private bool HasMinimumRequiredCable(ToolSession session, out int minRequiredAmount) {
minRequiredAmount = 0;
var cableCost = session.Profile.CableCost;
if (cableCost == null) return true;
minRequiredAmount = session.Profile.GetMinCableRequired();
if (minRequiredAmount < 0) return true;
var currentAmount = SumPlayerItems(session.Player, _config.CableItem.ItemId, _config.CableItem.ItemSkinId);
return currentAmount >= minRequiredAmount;
}
private bool VerifySufficientSpace(ToolSession session, RaycastHit hit) {
var player = session.Player;
var startPointData = session.StartPointData;
var startPosition = startPointData.Position;
var startRotation = startPointData.Rotation;
var hitRotation = Quaternion.FromToRotation(Vector3.up, hit.normal);
var offsetStartPosition = startPosition + GetZiplineWorldOffset(startRotation);
var offsetEndPosition = hit.point + GetZiplineWorldOffset(hitRotation);
var capsuleHeight = 1.5f;
var capsuleRadius = 0.25f;
var startPointTop = offsetStartPosition - new Vector3(0, capsuleRadius, 0);
va
// ... [Code preview truncated due to length. Full source in attached file] ...