C#:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("JumpScare", "RustFlash", "1.0.0")]
[Description("Halloween Jumpscare Plugin")]
class JumpScare : RustPlugin
{
#region Fields
[PluginReference] private Plugin ImageLibrary;
private ConfigData configData;
private Timer jumpscareTimer;
private List<string> scareImages = new List<string>();
private Dictionary<ulong, DateTime> lastJumpscare = new Dictionary<ulong, DateTime>();
private const string JUMPSCARE_UI = "JumpScareUI";
private string dataDirectory;
private DateTime nextJumpscareTime;
private const string PERMISSION_RECEIVE = "jumpscare.receive";
#endregion
#region Configuration
private class ConfigData
{
public int JumpscareIntervalMinutes { get; set; } = 60;
public float JumpscareDisplaySeconds { get; set; } = 3f;
public float MinPlayersBetweenScares { get; set; } = 5f;
public float MaxPlayersBetweenScares { get; set; } = 15f;
public bool EnableSounds { get; set; } = true;
public bool OnlyAtNight { get; set; } = true;
public int MinPlayersOnline { get; set; } = 2;
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
configData = Config.ReadObject<ConfigData>();
if (configData == null)
LoadDefaultConfig();
}
catch
{
LoadDefaultConfig();
}
SaveConfig();
}
protected override void LoadDefaultConfig()
{
configData = new ConfigData();
Puts("Default configuration created.");
}
protected override void SaveConfig() => Config.WriteObject(configData);
#endregion
#region Oxide Hooks
void Init()
{
dataDirectory = Path.Combine(Interface.Oxide.DataDirectory, "JumpScare");
if (!Directory.Exists(dataDirectory))
{
Directory.CreateDirectory(dataDirectory);
Puts($"Created JumpScare data directory: {dataDirectory}");
}
permission.RegisterPermission(PERMISSION_RECEIVE, this);
}
void OnServerInitialized()
{
if (ImageLibrary == null)
{
PrintError("ImageLibrary plugin not found! This plugin requires ImageLibrary to work.");
return;
}
LoadScareImages();
StartJumpscareTimer();
}
void Unload()
{
jumpscareTimer?.Destroy();
foreach (BasePlayer player in BasePlayer.activePlayerList)
{
CuiHelper.DestroyUi(player, JUMPSCARE_UI);
}
}
#endregion
#region Core Functions
private void LoadScareImages()
{
scareImages.Clear();
string[] imageFiles = Directory.GetFiles(dataDirectory, "*.png")
.Concat(Directory.GetFiles(dataDirectory, "*.jpg"))
.Concat(Directory.GetFiles(dataDirectory, "*.jpeg"))
.ToArray();
if (imageFiles.Length == 0)
{
PrintWarning($"No images found in {dataDirectory}! Place your scare images there.");
return;
}
foreach (string imagePath in imageFiles)
{
string fileName = Path.GetFileNameWithoutExtension(imagePath);
scareImages.Add(fileName);
byte[] imageData = File.ReadAllBytes(imagePath);
ImageLibrary?.Call("AddImageData", fileName, imageData, 0UL);
}
Puts($"Loaded {scareImages.Count} scare images.");
}
private void StartJumpscareTimer()
{
jumpscareTimer?.Destroy();
float nextScare = UnityEngine.Random.Range(configData.MinPlayersBetweenScares, configData.MaxPlayersBetweenScares) * 60f;
nextJumpscareTime = DateTime.Now.AddSeconds(nextScare);
jumpscareTimer = timer.Once(nextScare, () =>
{
TriggerRandomJumpscare();
StartJumpscareTimer();
});
}
private void TriggerRandomJumpscare()
{
if (BasePlayer.activePlayerList.Count < configData.MinPlayersOnline)
return;
if (configData.OnlyAtNight && !IsNightTime())
return;
if (scareImages.Count == 0)
{
PrintWarning("No scare images available!");
return;
}
var eligiblePlayers = BasePlayer.activePlayerList.Where(p =>
permission.UserHasPermission(p.UserIDString, PERMISSION_RECEIVE) &&
(!lastJumpscare.ContainsKey(p.userID) ||
(DateTime.Now - lastJumpscare[p.userID]).TotalMinutes >= configData.JumpscareIntervalMinutes)
).ToList();
if (eligiblePlayers.Count == 0)
return;
BasePlayer targetPlayer = eligiblePlayers[UnityEngine.Random.Range(0, eligiblePlayers.Count)];
string randomImage = scareImages[UnityEngine.Random.Range(0, scareImages.Count)];
ExecuteJumpscare(targetPlayer, randomImage);
lastJumpscare[targetPlayer.userID] = DateTime.Now;
}
private void ExecuteJumpscare(BasePlayer player, string imageName)
{
if (player == null || !player.IsConnected)
return;
if (configData.EnableSounds)
{
PlayWorkingScareSound(player);
}
ShowJumpscareImage(player, imageName);
player.ChatMessage("<color=red><size=16>:scarecrowhead: You got jumpscared! Happy Halloween :exclamation:</size></color>");
timer.Once(configData.JumpscareDisplaySeconds, () =>
{
if (player != null && player.IsConnected)
{
CuiHelper.DestroyUi(player, JUMPSCARE_UI);
}
});
}
private void PlayWorkingScareSound(BasePlayer player)
{
try
{
string[] bothSounds = new string[]
{
"assets/bundled/prefabs/fx/player/beartrap_scream.prefab",
"assets/bundled/prefabs/fx/player/howl.prefab"
};
foreach(string soundPath in bothSounds)
{
Effect effect = new Effect(soundPath, player.transform.position, Vector3.up);
EffectNetwork.Send(effect, player.Connection);
}
}
catch (Exception ex)
{
PrintWarning($"Failed to play scare sounds: {ex.Message}");
}
}
private void PlayScareSound(BasePlayer player)
{
PlayWorkingScareSound(player);
}
private void ShowJumpscareImage(BasePlayer player, string imageName)
{
string imageId = ImageLibrary?.Call<string>("GetImage", imageName, 0UL) as string;
if (string.IsNullOrEmpty(imageId))
{
PrintWarning($"Failed to get image ID for {imageName}");
return;
}
var container = new CuiElementContainer();
container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0.95" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
}, "Overlay", JUMPSCARE_UI);
container.Add(new CuiElement
{
Parent = JUMPSCARE_UI,
Components =
{
new CuiRawImageComponent { Png = imageId },
new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1" }
}
});
CuiHelper.AddUi(player, container);
}
private bool IsNightTime()
{
float currentTime = TOD_Sky.Instance.Cycle.Hour;
return currentTime >= 20f || currentTime <= 6f;
}
#endregion
#region Commands
[ChatCommand("scare")]
private void CmdScare(BasePlayer player, string command, string[] args)
{
if (!player.IsAdmin)
{
player.ChatMessage("No permission.");
return;
}
if (args.Length == 0)
{
if (scareImages.Count == 0)
{
player.ChatMessage("No scare images available!");
return;
}
string randomImage = scareImages[UnityEngine.Random.Range(0, scareImages.Count)];
ExecuteJumpscare(player, randomImage);
}
else if (args.Length == 1)
{
BasePlayer target = BasePlayer.Find(args[0]);
if (target == null)
{
player.ChatMessage("Player not found.");
return;
}
if (scareImages.Count == 0)
{
player.ChatMessage("No scare images available!");
return;
}
string randomImage = scareImages[UnityEngine.Random.Range(0, scareImages.Count)];
ExecuteJumpscare(target, randomImage);
}
}
[ChatCommand("scareinfo")]
private void CmdScareInfo(BasePlayer player, string command, string[] args)
{
if (!player.IsAdmin)
{
player.ChatMessage("No permission.");
return;
}
string nextScareInfo;
if (jumpscareTimer != null && nextJumpscareTime > DateTime.Now)
{
double secondsUntilNext = (nextJumpscareTime - DateTime.Now).TotalSeconds;
nextScareInfo = $"{secondsUntilNext:F0} seconds";
}
else
{
nextScareInfo = "Timer not active";
}
string imageList = scareImages.Count > 0 ? string.Join(", ", scareImages) : "None loaded";
string infoMessage = $"<color=orange><size=14> :pumpkin: JumpScare Info :pumpkin:</size></color>\n" +
$"<color=white>Loaded Images: <color=yellow>{scareImages.Count}</color></color>\n" +
$"<color=white>Next Scare: <color=yellow>{nextScareInfo}</color></color>\n" +
$"<color=white>Images: <color=yellow>{imageList}</color></color>";
player.ChatMessage(infoMessage);
}
[ChatCommand("reloadscares")]
private void CmdReloadScares(BasePlayer player, string command, string[] args)
{
if (!player.IsAdmin)
{
player.ChatMessage("No permission.");
return;
}
LoadScareImages();
player.ChatMessage($"Reloaded {scareImages.Count} scare images.");
}
#endregion
}
}