C#:
using System;
using System.Collections.Generic;
using System.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
using Random = System.Random;
namespace Oxide.Plugins
{
[Info("Air Drop Event", "0killrest", "1.0.0")]
[Description("PUBG-style airdrop events with planes, smoke, and loot")]
internal class AirDropEvent : RustPlugin
{
#region Configuration
private static ConfigData config;
private class ConfigData
{
public bool Enabled { get; set; } = true;
public float DropIntervalMinutes { get; set; } = 10f;
public int DropsPerEvent { get; set; } = 1;
public bool AnnounceDrop { get; set; } = true;
public bool PlayPlaneSound { get; set; } = true;
public bool ShowMapMarker { get; set; } = true;
public bool DropSmokeEffect { get; set; } = true;
public float PlaneFlyHeight { get; set; } = 300f;
public float PlaneFlySpeed { get; set; } = 80f;
public int LootAmountMin { get; set; } = 5;
public int LootAmountMax { get; set; } = 12;
public List<LootItem> LootTable { get; set; }
public ConfigData()
{
LootTable = new List<LootItem>
{
// Оружие
new LootItem { ShortName = "rifle.ak", MinAmount = 1, MaxAmount = 1, Chance = 15, DisplayName = "AK-47" },
new LootItem { ShortName = "rifle.lr300", MinAmount = 1, MaxAmount = 1, Chance = 12, DisplayName = "LR-300" },
new LootItem { ShortName = "smg.mp5", MinAmount = 1, MaxAmount = 1, Chance = 15, DisplayName = "MP5" },
new LootItem { ShortName = "pistol.m92", MinAmount = 1, MaxAmount = 1, Chance = 20, DisplayName = "M92" },
new LootItem { ShortName = "shotgun.spas12", MinAmount = 1, MaxAmount = 1, Chance = 12, DisplayName = "SPAS-12" },
new LootItem { ShortName = "bolt.rifle", MinAmount = 1, MaxAmount = 1, Chance = 8, DisplayName = "Bolt Rifle" },
// Патроны
new LootItem { ShortName = "ammo.rifle", MinAmount = 30, MaxAmount = 60, Chance = 80, DisplayName = "Rifle Ammo" },
new LootItem { ShortName = "ammo.pistol", MinAmount = 30, MaxAmount = 60, Chance = 70, DisplayName = "Pistol Ammo" },
new LootItem { ShortName = "ammo.shotgun", MinAmount = 12, MaxAmount = 24, Chance = 60, DisplayName = "Shotgun Shells" },
// Аптечки и хилки
new LootItem { ShortName = "large.medkit", MinAmount = 1, MaxAmount = 3, Chance = 90, DisplayName = "Medkit" },
new LootItem { ShortName = "syringe.medical", MinAmount = 2, MaxAmount = 5, Chance = 85, DisplayName = "Syringe" },
new LootItem { ShortName = "bandage", MinAmount = 5, MaxAmount = 10, Chance = 95, DisplayName = "Bandage" },
// Броня
new LootItem { ShortName = "metal.plate.torso", MinAmount = 1, MaxAmount = 1, Chance = 25, DisplayName = "Metal Chestplate" },
new LootItem { ShortName = "metal.plate.pants", MinAmount = 1, MaxAmount = 1, Chance = 25, DisplayName = "Metal Pants" },
new LootItem { ShortName = "coffeecan.helmet", MinAmount = 1, MaxAmount = 1, Chance = 30, DisplayName = "Coffee Can Helmet" },
// Еда
new LootItem { ShortName = "apple", MinAmount = 3, MaxAmount = 8, Chance = 85, DisplayName = "Apple" },
new LootItem { ShortName = "can.beans", MinAmount = 2, MaxAmount = 5, Chance = 70, DisplayName = "Canned Beans" },
new LootItem { ShortName = "chocolate", MinAmount = 2, MaxAmount = 4, Chance = 65, DisplayName = "Chocolate Bar" },
// Гранаты и взрывчатка
new LootItem { ShortName = "grenade.f1", MinAmount = 1, MaxAmount = 3, Chance = 40, DisplayName = "F1 Grenade" },
new LootItem { ShortName = "explosive.timed", MinAmount = 1, MaxAmount = 2, Chance = 15, DisplayName = "C4" },
new LootItem { ShortName = "ammo.rocket.basic", MinAmount = 2, MaxAmount = 5, Chance = 10, DisplayName = "Rockets" },
// Ресурсы
new LootItem { ShortName = "scrap", MinAmount = 50, MaxAmount = 200, Chance = 100, DisplayName = "Scrap" },
new LootItem { ShortName = "metal.refined", MinAmount = 100, MaxAmount = 300, Chance = 75, DisplayName = "Metal Fragments" },
new LootItem { ShortName = "sulfur", MinAmount = 100, MaxAmount = 250, Chance = 65, DisplayName = "Sulfur" }
};
}
}
public class LootItem
{
public string ShortName { get; set; }
public int MinAmount { get; set; }
public int MaxAmount { get; set; }
public int Chance { get; set; }
public string DisplayName { get; set; }
}
protected override void LoadConfig()
{
base.LoadConfig();
config = Config.ReadObject<ConfigData>();
if (config == null)
{
LoadDefaultConfig();
}
}
protected override void LoadDefaultConfig()
{
config = new ConfigData();
PrintWarning("Default configuration created.Edit it in oxide/config/AirDropEvent.json");
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
#endregion
#region Data
private Timer dropTimer;
private Random random = new Random();
private List<DropCrate> activeDrops = new List<DropCrate>();
private class DropCrate
{
public SupplyDrop Crate { get; set; }
public Vector3 Position { get; set; }
public float LandTime { get; set; }
public bool IsLanded { get; set; }
}
#endregion
#region Hooks
private void Init()
{
permission.RegisterPermission("airdropevent.admin", this);
}
private void OnServerInitialized()
{
if (config.Enabled)
{
StartDropTimer();
}
}
private void Unload()
{
dropTimer?.Destroy();
}
private void StartDropTimer()
{
if (dropTimer != null)
{
dropTimer.Destroy();
}
dropTimer = timer.Repeat(config.DropIntervalMinutes * 60f, 0, () =>
{
ExecuteAirDrop();
});
Puts($"AirDropEvent enabled: drops every {config.DropIntervalMinutes} minutes");
}
private void ExecuteAirDrop()
{
if (!config.Enabled) return;
for (int i = 0; i < config.DropsPerEvent; i++)
{
SpawnAirDrop();
}
}
private void SpawnAirDrop()
{
// Get random position on map
Vector3 dropPosition = GetRandomDropPosition();
// Play plane sound effect (server-wide)
if (config.PlayPlaneSound)
{
Effect.server.Run("assets/prefabs/npc/cargo plane/effects/plane-flyby.prefab", dropPosition, Vector3.zero);
}
// Announce to all players
if (config.AnnounceDrop)
{
string announceMessage = $"<color=orange>✈️ AIRDROP INCOMING!</color> A supply drop is falling at <color=yellow>{GetGridPosition(dropPosition)}</color>!";
Server.Broadcast(announceMessage);
PrintToChat(announceMessage);
}
// Spawn the supply drop
SupplyDrop supplyDrop = GameManager.server.CreateEntity("assets/prefabs/misc/supply drop/supply_drop.prefab", dropPosition) as SupplyDrop;
if (supplyDrop == null)
{
PrintError("Failed to create supply drop!");
return;
}
// Configure the drop
supplyDrop.Invoke(new Action(supplyDrop.InitSupplyDrop), 0.1f);
supplyDrop.Spawn();
// Add smoke effect
if (config.DropSmokeEffect)
{
AddSmokeEffect(dropPosition);
}
// Add map marker
if (config.ShowMapMarker)
{
AddMapMarker(dropPosition);
}
// Customize loot
CustomizeLoot(supplyDrop);
// Track active drop
activeDrops.Add(new DropCrate
{
Crate = supplyDrop,
Position = dropPosition,
LandTime = Time.realtimeSinceStartup + 15f,
IsLanded = false
});
Puts($"Airdrop spawned at {dropPosition}");
// Schedule cleanup for landed drops (10 minutes)
timer.Once(600f, () =>
{
if (supplyDrop != null && !supplyDrop.IsDestroyed)
{
supplyDrop.Kill();
activeDrops.RemoveAll(d => d.Crate == supplyDrop);
}
});
}
private Vector3 GetRandomDropPosition()
{
// Try up to 10 times to find a valid position (not in water, not too close to edge)
for (int i = 0; i < 10; i++)
{
float x = (float)random.NextDouble() * (TerrainMeta.Size.x - 200f) + 100f;
float z = (float)random.NextDouble() * (TerrainMeta.Size.z - 200f) + 100f;
float y = TerrainMeta.HeightMap.GetHeight(new Vector3(x, 0, z)) + 5f;
Vector3 pos = new Vector3(x, y, z);
// Check if not in water
if (!IsInWater(pos))
{
return pos;
}
}
// Fallback to center of map
float centerX = TerrainMeta.Size.x / 2f;
float centerZ = TerrainMeta.Size.z / 2f;
float centerY = TerrainMeta.HeightMap.GetHeight(new Vector3(centerX, 0, centerZ)) + 5f;
return new Vector3(centerX, centerY, centerZ);
}
private bool IsInWater(Vector3 position)
{
// Simple water check
return position.y < TerrainMeta.WaterMap.GetHeight(position);
}
private string GetGridPosition(Vector3 position)
{
// Convert world position to grid reference (e.g., "G5")
int gridSize = 10;
float mapSize = TerrainMeta.Size.x;
float cellSize = mapSize / gridSize;
char xChar = (char)('A' + Mathf.FloorToInt(position.x / cellSize));
int zNum = Mathf.FloorToInt(position.z / cellSize) + 1;
return $"{xChar}{zNum}";
}
private void AddSmokeEffect(Vector3 position)
{
// Add smoke grenade effect at drop location
Effect.server.Run("assets/prefabs/npc/sam_site_turret/effects/sam_site_rocket_smoke.prefab", position, Vector3.zero);
}
private void AddMapMarker(Vector3 position)
{
// Create a map marker for all players
foreach (var player in BasePlayer.activePlayerList)
{
player.SendConsoleCommand("createmarker",
$"airdrop_{position}",
"🎁 Air Drop",
"red",
position);
}
// Auto-remove marker after 5 minutes
timer.Once(300f, () =>
{
foreach (var player in BasePlayer.activePlayerList)
{
player.SendConsoleCommand("removemarker", $"airdrop_{position}");
}
});
}
private void CustomizeLoot(SupplyDrop supplyDrop)
{
if (supplyDrop == null || supplyDrop.inventory == null) return;
// Clear default loot
supplyDrop.inventory.Clear();
// Determine how many items to add
int itemCount = random.Next(config.LootAmountMin, config.LootAmountMax + 1);
// Filter loot table by chance
List<LootItem> availableLoot = new List<LootItem>();
foreach (var lootItem in config.LootTable)
{
if (random.Next(100) < lootItem.Chance)
{
availableLoot.Add(lootItem);
}
}
// Shuffle and pick items
availableLoot = availableLoot.OrderBy(x => random.Next()).ToList();
for (int i = 0; i < Math.Min(itemCount, availableLoot.Count); i++)
{
var lootItem = availableLoot[i];
int amount = random.Next(lootItem.MinAmount, lootItem.MaxAmount + 1);
ItemDefinition itemDef = ItemManager.FindItemDefinition(lootItem.ShortName);
if (itemDef != null)
{
Item item = ItemManager.Create(itemDef, amount);
if (item != null && !supplyDrop.inventory.IsFull())
{
supplyDrop.inventory.Insert(item);
}
}
}
// Always add some scrap if not enough items
if (supplyDrop.inventory.itemList.Count < 3)
{
Item scrap = ItemManager.Create(ItemManager.FindItemDefinition("scrap"), random.Next(50, 150));
if (scrap != null)
{
supplyDrop.inventory.Insert(scrap);
}
}
Puts($"Airdrop loot populated with {supplyDrop.inventory.itemList.Count} items");
}
private void OnLootEntity(BasePlayer player, SupplyDrop entity)
{
// Check if this is our drop
var drop = activeDrops.FirstOrDefault(d => d.Crate == entity);
if (drop == null) return;
// Announce who looted
if (drop.IsLanded && config.AnnounceDrop)
{
string lootMessage = $"<color=green>🎁 {player.displayName} looted the airdrop at {GetGridPosition(drop.Position)}!</color>";
Server.Broadcast(lootMessage);
}
// Remove from active drops
activeDrops.Remove(drop);
}
private void OnSupplyDropLanded(SupplyDrop supplyDrop)
{
var drop = activeDrops.FirstOrDefault(d => d.Crate == supplyDrop);
if (drop != null)
{
drop.IsLanded = true;
if (config.AnnounceDrop)
{
string landedMessage = $"<color=orange>📦 The airdrop has landed at {GetGridPosition(drop.Position)}! Go get it!</color>";
Server.Broadcast(landedMessage);
}
// Add smoke effect on landing
Effect.server.Run("assets/prefabs/npc/sam_site_turret/effects/sam_site_rocket_smoke.prefab", drop.Position, Vector3.zero);
}
}
#endregion
#region Commands
[ChatCommand("airdrop")]
private void cmdAirdrop(BasePlayer player, string command, string[] args)
{
if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, "airdropevent.admin"))
{
SendReply(player, "You don't have permission to use this command.");
return;
}
if (args.Length > 0 && args[0].ToLower() == "spawn")
{
SpawnAirDrop();
SendReply(player, "Airdrop spawned!");
}
else
{
float nextDrop = dropTimer != null ? config.DropIntervalMinutes : 0;
SendReply(player, $"<color=yellow>=== AirDrop Info ===</color>");
SendReply(player, $"Next drop in: {nextDrop} minutes");
SendReply(player, $"Active drops: {activeDrops.Count}");
SendReply(player, $"Type <color=green>/airdrop spawn</color> to force a drop");
}
}
[ConsoleCommand("airdrop.spawn")]
private void cmdConsoleSpawn(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "airdropevent.admin"))
{
args.ReplyWith("No permission");
return;
}
SpawnAirDrop();
args.ReplyWith("Airdrop spawned");
}
[ConsoleCommand("airdrop.reload")]
private void cmdConsoleReload(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "airdropevent.admin"))
{
args.ReplyWith("No permission");
return;
}
LoadConfig();
args.ReplyWith("AirDropEvent reloaded");
}
[ConsoleCommand("airdrop.status")]
private void cmdConsoleStatus(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "airdropevent.admin"))
{
args.ReplyWith("No permission");
return;
}
args.ReplyWith($"AirDropEvent: {activeDrops.Count} active drops, interval {config.DropIntervalMinutes} min");
}
#endregion
}
}