C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections.Generic;
using Network;
using Newtonsoft.Json;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Flashbang", "birthdates", "1.0.5")]
[Description("Throw a flashbang to temporarily blind your enemies")]
public class Flashbang : RustPlugin
{
#region Command
[ConsoleCommand("flashbang")]
private void FlashbangCommand(ConsoleSystem.Arg arg)
{
if (arg.Player() != null && !arg.Player().IPlayer.HasPermission(GivePermission) || arg.Args == null ||
arg.Args.Length < 1) return;
var amount = 1;
var count = arg.Args.Length;
if (arg.Args.Length > 1)
{
if (!int.TryParse(arg.Args[arg.Args.Length - 1], out amount)) amount = 1;
else count--;
}
var player = BasePlayer.Find(string.Join(" ", arg.Args, 0, count));
if (player == null) return;
var item = ItemManager.CreateByName("grenade.smoke", amount, _config.SkinID);
item.name = "Flashbang";
player.GiveItem(item);
}
#endregion
#region Variables
/// <summary>
/// Layer mask containing players
/// </summary>
private int PlayerMask { get; } = LayerMask.GetMask("Player (Server)");
/// <summary>
/// Layer mask that will obstruct a flashbang
/// </summary>
private int ObstructionMask { get; } = LayerMask.GetMask("Construction", "World", "Terrain");
/// <summary>
/// Intense flash UI
/// </summary>
private CuiElementContainer FlashCui { get; } = new CuiElementContainer();
/// <summary>
/// Small flash UI
/// </summary>
private CuiElementContainer SmallFlashCui { get; } = new CuiElementContainer();
/// <summary>
/// Flashbang expiry cache
/// </summary>
private IDictionary<ulong, long> FlashExpiry { get; } = new Dictionary<ulong, long>();
/// <summary>
/// Main CUI name (for both <see cref="Flashbang.FlashCui" /> & <see cref="Flashbang.SmallFlashCui" />)
/// </summary>
private const string CuiName = "Flashed";
/// <summary>
/// The permission for <see cref="Flashbang.FlashbangCommand" />
/// </summary>
private const string GivePermission = "flashbang.give";
#endregion
#region Hooks
private void Init()
{
permission.RegisterPermission(GivePermission, this);
GenerateUI(FlashCui, "1 1 1 1");
GenerateUI(SmallFlashCui, "1 1 1 0.6");
// Unsub if default behaviour
if (_config.Stack == 3) Unsubscribe(nameof(OnMaxStackable));
}
private void OnServerInitialized()
{
timer.Every(1f, CheckForExpired);
}
private object OnMaxStackable(Item item)
{
return item.skin != _config.SkinID ? (object) null : _config.Stack;
}
private void OnExplosiveThrown(BasePlayer player, TimedExplosive entity, ThrownWeapon item)
{
OnThrown(player, entity, item);
}
private void OnExplosiveDropped(BasePlayer player, TimedExplosive entity, ThrownWeapon item)
{
OnThrown(player, entity, item);
}
private void Unload()
{
// Un-flash everyone
foreach (var entry in FlashExpiry) UnFlash(BasePlayer.FindByID(entry.Key));
}
private void OnPlayerDisconnected(BasePlayer player)
{
FlashExpiry.Remove(player.userID);
}
#endregion
#region Core Logic
/// <summary>
/// Generate flashbang UI
/// </summary>
/// <param name="container">Target UI container</param>
/// <param name="color">Flashbang color</param>
private void GenerateUI(CuiElementContainer container, string color)
{
container.Add(
new CuiPanel
{
FadeOut = _config.FadeOut, Image = {Color = color},
RectTransform = {AnchorMin = "0 0", AnchorMax = "1 1"}
}, "Overlay", CuiName);
}
/// <summary>
/// Check for expired flashes and clear the player's UI
/// </summary>
private void CheckForExpired()
{
foreach (var entry in new Dictionary<ulong, long>(FlashExpiry))
{
if (entry.Value > DateTime.UtcNow.Ticks) continue;
var player = BasePlayer.FindByID(entry.Key);
if (player == null)
{
FlashExpiry.Remove(entry.Key);
continue;
}
UnFlash(player);
}
}
/// <summary>
/// Is this item a flashbang?
/// </summary>
/// <param name="item">Target item</param>
/// <returns>True if this item is a flashbang.False, otherwise.</returns>
[HookMethod("IsFlashbang")]
private bool IsFlashbang(Item item)
{
return item != null && item.skin.Equals(_config.SkinID);
}
/// <summary>
/// Is this player flashed?
/// </summary>
/// <param name="player">Target player</param>
/// <returns>True, if this player is flashed.False, otherwise.</returns>
[HookMethod("IsFlashed")]
private bool IsFlashed(BasePlayer player)
{
return IsFlashed(player.userID);
}
/// <summary>
/// Is this player flashed?
/// </summary>
/// <param name="id">Target player's ID</param>
/// <returns>True, if this player is flashed.False, otherwise.</returns>
[HookMethod("IsFlashed")]
private bool IsFlashed(ulong id)
{
return FlashExpiry.ContainsKey(id);
}
/// <summary>
/// Called on explosive thrown
/// </summary>
/// <param name="player">Player who threw the explosive</param>
/// <param name="entity">Explosive entity</param>
/// <param name="weapon">The weapon</param>
private void OnThrown(BasePlayer player, TimedExplosive entity, ThrownWeapon weapon)
{
var item = weapon.GetItem();
if (!IsFlashbang(item))
return;
var collider = entity.GetComponent<Collider>();
var rigidBody = collider.attachedRigidbody;
entity.SetVelocity(rigidBody.velocity * _config.VelocityMultiplier);
entity.CancelInvoke(entity.Explode);
entity.Invoke(() => Flash(entity), _config.DeployTime);
}
/// <summary>
/// Flash a player from a source (this takes the ignore angle into account)
/// </summary>
/// <param name="player">Target player</param>
/// <param name="flash">Source flashbang</param>
[HookMethod("Flash")]
private void Flash(BasePlayer player, Component flash)
{
if (!player.userID.IsSteamId()) return;
var angle = AngleTo(player, flash);
if (angle >= _config.IgnoreAngle) return;
Flash(player, angle >= _config.SmallAngle);
}
/// <summary>
/// Flash a player
/// </summary>
/// <param name="player">Target player</param>
/// <param name="small">Small flash?</param>
private void Flash(BasePlayer player, bool small = false)
{
FlashExpiry[player.userID] =
DateTime.UtcNow.AddSeconds(small ? _config.SmallBlindTime : _config.BlindTime).Ticks;
CuiHelper.DestroyUi(player, CuiName);
CuiHelper.AddUi(player, small ? SmallFlashCui : FlashCui);
if (!small)
PlayPrefab(_config.ScreamPrefab, player.transform.position);
}
/// <summary>
/// Un-flash a player
/// </summary>
/// <param name="player">Target player</param>
[HookMethod("UnFlash")]
private void UnFlash(BasePlayer player)
{
if (player == null) return;
CuiHelper.DestroyUi(player, CuiName);
FlashExpiry.Remove(player.userID);
}
/// <summary>
/// Is this player obstructed from this flashbang?
/// </summary>
/// <param name="basePlayer">Target player</param>
/// <param name="flash">Source flashbang</param>
/// <returns>True if something is obstructing the view of the player's eyes from the flashbang.False, otherwise.</returns>
private bool IsObstructed(BasePlayer basePlayer, BaseEntity flash)
{
var transform = flash.transform;
var position = transform.position;
var dir = (basePlayer.eyes.position - position).normalized;
return Physics.Raycast(position, dir, basePlayer.Distance(flash), ObstructionMask,
QueryTriggerInteraction.Ignore);
}
/// <summary>
/// Produce a flashbang effect from a flashbang entity (flash entities around it and destroy the entity)
/// </summary>
/// <param name="flash">Target entity</param>
[HookMethod("Flash")]
private void Flash(BaseEntity flash)
{
flash.Kill();
var players = new List<BasePlayer>();
var position = flash.transform.position;
Vis.Entities(position, _config.Radius, players, PlayerMask, QueryTriggerInteraction.Ignore);
PlayPrefab(_config.SmokePrefab, position, Vector3.up);
foreach (var basePlayer in players)
{
// Play directionally and close because the default sound is really quiet/mute at distances
var playerPos = basePlayer.transform.position;
var dir = (flash.transform.position - playerPos).normalized;
PlayPrefab(_config.DeployPrefab, playerPos + dir, source: basePlayer.Connection);
if (!IsObstructed(basePlayer, flash))
Flash(basePlayer, flash);
}
}
/// <summary>
/// Play a certain prefab at a position (and direction)
/// </summary>
/// <param name="prefab">Target prefab</param>
/// <param name="position">Prefab source position</param>
/// <param name="direction">Prefab direction</param>
/// <param name="source">Source connection</param>
private static void PlayPrefab(string prefab, Vector3 position, Vector3 direction = default(Vector3),
Connection source = null)
{
if (string.IsNullOrEmpty(prefab)) return;
if (source != null)
{
var effect = new Effect();
effect.Init(Effect.Type.Generic, position, direction);
effect.pooledString = prefab;
EffectNetwork.Send(effect, source);
return;
}
Effect.server.Run(prefab, position, direction);
}
/// <summary>
/// Get an angle in degrees from the player's rotation to a target component
/// </summary>
/// <param name="player">Target player</param>
/// <param name="target">Target component</param>
/// <returns>An angle in degrees (less than 180, greater than 0)</returns>
private static float AngleTo(BasePlayer player, Component target)
{
var pos = new Vector3(player.eyes.position.x, 0, player.eyes.position.z);
var targetDir = (pos - target.transform.position).normalized;
targetDir.y = 0;
var ray = player.eyes.GetLookRotation() * Vector3.forward;
ray.y = 0;
return Math.Abs(180f - Vector3.Angle(ray, targetDir));
}
#endregion
#region Configuration & Localization
private ConfigFile _config;
protected override void LoadConfig()
{
base.LoadConfig();
_config = Config.ReadObject<ConfigFile>();
if (_config == null) LoadDefaultConfig();
}
protected override void LoadDefaultConfig()
{
_config = ConfigFile.DefaultConfig();
PrintWarning("Default configuration has been loaded.");
}
protected override void SaveConfig()
{
Config.WriteObject(_config);
}
private class ConfigFile
{
/// <summary>
/// The smoke/flash skin ID (See <see href="https://www.youtube.com/watch?v=Bz8RNnM7Bgo" /> for custom icons)
/// </summary>
[JsonProperty("Smoke Skin ID")]
public ulong SkinID { get; set; }
/// <summary>
/// Time from throw until the flashbang will do it's effects
/// </summary>
[JsonProperty("Flashbang Deploy Time (seconds)")]
public float DeployTime { get; set; }
/// <summary>
/// The flashbang's deploy radius
/// </summary>
[JsonProperty("Flashbang Radius (metres)")]
public float Radius { get; set; }
/// <summary>
/// Flashbang velocity multiplier (speed)
/// </summary>
[JsonProperty("Flashbang Velocity Multiplier")]
public float VelocityMultiplier { get; set; }
/// <summary>
/// The time an intense flash will blind you for (seconds)
/// </summary>
[JsonProperty("Flashbang Blind Time (seconds)")]
public float BlindTime { get; set; }
/// <summary>
/// The time a small flash will blind you for (seconds)
/// </summary>
[JsonProperty("Flashbang Small Blind Time (seconds)")]
public float SmallBlindTime { get; set; }
/// <summary>
/// The minimum angle required for a small flash
/// </summary>
[JsonProperty("Flashbang Small Angle (degrees)")]
public float SmallAngle { get; set; }
/// <summary>
/// The minimum angle at which we don't flash at all
/// </summary>
[JsonProperty("Ignore Angle (degrees)")]
public float IgnoreAngle { get; set; }
/// <summary>
/// The fadeout time for a flash
/// </summary>
[JsonProperty("Flashbang Fadeout Time (seconds)")]
public float FadeOut { get; set; }
/// <summary>
/// The maximum stack for flashbangs
/// </summary>
[JsonProperty("Flashbang Stack")]
public int Stack { get; set; }
/// <summary>
/// The flashbang deploy prefab
/// </summary>
[JsonProperty("Flashbang Deploy Prefab (leave blank for disable)")]
public string DeployPrefab { get; set; }
/// <summary>
/// The flashbang scream prefab (only played on full flash)
/// </summary>
[JsonProperty("Scream Prefab (leave blank for disable)")]
public string ScreamPrefab { get; set; }
/// <summary>
/// The smoke deploy prefab (called on flash on the flashbang)
/// </summary>
[JsonProperty("Smoke Prefab (leave blank for disable)")]
public string SmokePrefab { get; set; }
public static ConfigFile DefaultConfig()
{
return new ConfigFile
{
SkinID = 2555437667UL,
DeployTime = 3f,
DeployPrefab = "assets/prefabs/tools/smoke grenade/effects/ignite.prefab",
SmokePrefab = "assets/prefabs/weapons/rocketlauncher/effects/rocket_launch_fx.prefab",
ScreamPrefab = "assets/bundled/prefabs/fx/player/gutshot_scream.prefab",
Radius = 35f,
VelocityMultiplier = 1.5f,
BlindTime = 4f,
SmallBlindTime = 1f,
SmallAngle = 60f,
IgnoreAngle = 150f,
FadeOut = 1f,
Stack = 1
};
}
}
#endregion
}
}