C#:
using System;
using System.Collections.Generic;
using System.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Monster Hunt", "0killrest", "1.0.0")]
[Description("Periodic monster spawns with rewards for killing them")]
internal class MonsterHunt : RustPlugin
{
#region Configuration
private static ConfigData config;
private class ConfigData
{
public bool Enabled { get; set; } = true;
public float SpawnIntervalMinutes { get; set; } = 30f;
public int MaxMonstersAtOnce { get; set; } = 1;
public bool AnnounceToAllPlayers { get; set; } = true;
public List<MonsterConfig> Monsters { get; set; }
public List<RewardItem> KillRewards { get; set; }
public ConfigData()
{
Monsters = new List<MonsterConfig>
{
new MonsterConfig
{
Name = "Radioactive Bear",
Prefab = "assets/rust.ai/agents/bear/bear.prefab",
Health = 1000f,
Damage = 50f,
Scale = 1.5f,
Color = "red",
SpawnChance = 0.5f
},
new MonsterConfig
{
Name = "Frozen Wolf",
Prefab = "assets/rust.ai/agents/wolf/wolf.prefab",
Health = 600f,
Damage = 35f,
Scale = 1.3f,
Color = "cyan",
SpawnChance = 0.3f
},
new MonsterConfig
{
Name = "Lava Boar",
Prefab = "assets/rust.ai/agents/boar/boar.prefab",
Health = 800f,
Damage = 40f,
Scale = 1.4f,
Color = "orange",
SpawnChance = 0.2f
}
};
KillRewards = new List<RewardItem>
{
new RewardItem { ShortName = "scrap", Amount = 100, MinAmount = 50, MaxAmount = 200, DisplayName = "Scrap" },
new RewardItem { ShortName = "metal.refined", Amount = 50, MinAmount = 25, MaxAmount = 100, DisplayName = "Metal Fragments" },
new RewardItem { ShortName = "ammo.rifle", Amount = 30, MinAmount = 15, MaxAmount = 60, DisplayName = "Rifle Ammo" },
new RewardItem { ShortName = "cloth", Amount = 100, MinAmount = 50, MaxAmount = 200, DisplayName = "Cloth" }
};
}
}
public class MonsterConfig
{
public string Name { get; set; }
public string Prefab { get; set; }
public float Health { get; set; }
public float Damage { get; set; }
public float Scale { get; set; } = 1f;
public string Color { get; set; } = "white";
public float SpawnChance { get; set; } = 0.5f;
}
public class RewardItem
{
public string ShortName { get; set; }
public int Amount { get; set; }
public int MinAmount { get; set; }
public int MaxAmount { 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/MonsterHunt.json");
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
#endregion
#region Data Storage
private List<HuntMonster> activeMonsters = new List<HuntMonster>();
private Timer spawnTimer;
private Random random = new Random();
private class HuntMonster
{
public BaseEntity Entity { get; set; }
public BaseCombatEntity Combat { get; set; }
public MonsterConfig Config { get; set; }
public Vector3 SpawnPosition { get; set; }
public float SpawnTime { get; set; }
public bool IsAlive { get; set; } = true;
}
#endregion
#region Hooks
private void Init()
{
LoadConfig();
permission.RegisterPermission("monsterhunt.admin", this);
}
private void OnServerInitialized()
{
if (config.Enabled)
{
StartSpawnTimer();
}
}
private void Unload()
{
spawnTimer?.Destroy();
DespawnAllMonsters();
}
private void StartSpawnTimer()
{
if (spawnTimer != null)
{
spawnTimer.Destroy();
}
spawnTimer = timer.Repeat(config.SpawnIntervalMinutes * 60f, 0, () =>
{
SpawnMonsters();
});
Puts($"MonsterHunt enabled: spawning every {config.SpawnIntervalMinutes} minutes");
}
private void SpawnMonsters()
{
if (!config.Enabled) return;
// Clean up dead monsters
activeMonsters.RemoveAll(m => m.Entity == null || m.Entity.IsDestroyed || (m.Combat != null && m.Combat.IsDead()));
int currentCount = activeMonsters.Count;
int toSpawn = Math.Max(0, config.MaxMonstersAtOnce - currentCount);
if (toSpawn <= 0) return;
for (int i = 0; i < toSpawn; i++)
{
SpawnRandomMonster();
}
if (config.AnnounceToAllPlayers && toSpawn > 0)
{
string message = $"<color=red>⚠️ A deadly monster has appeared on the island!</color> Find and kill it for rewards!";
Server.Broadcast(message);
PrintToChat(message);
}
}
private void SpawnRandomMonster()
{
// Select random monster based on spawn chance
float totalChance = config.Monsters.Sum(m => m.SpawnChance);
float randomValue = (float)random.NextDouble() * totalChance;
float cumulative = 0f;
MonsterConfig selectedConfig = null;
foreach (var monster in config.Monsters)
{
cumulative += monster.SpawnChance;
if (randomValue <= cumulative)
{
selectedConfig = monster;
break;
}
}
if (selectedConfig == null) selectedConfig = config.Monsters.First();
// Find spawn position (random on map, away from monuments)
Vector3 spawnPos = GetRandomSpawnPosition();
// Spawn the entity
BaseEntity entity = GameManager.server.CreateEntity(selectedConfig.Prefab, spawnPos);
if (entity == null)
{
PrintError($"Failed to spawn monster: {selectedConfig.Prefab}");
return;
}
BaseCombatEntity combat = entity as BaseCombatEntity;
if (combat == null)
{
entity.Kill();
PrintError($"Spawned entity is not a combat entity: {selectedConfig.Prefab}");
return;
}
// Apply monster properties
combat.health = selectedConfig.Health;
combat.maxHealth = selectedConfig.Health;
combat.StartHealthRegeneration(0f, 0f, 0f);
// Scale the entity
entity.transform.localScale = Vector3.one * selectedConfig.Scale;
// Set custom name
combat.displayName = $"<color={selectedConfig.Color}>★ {selectedConfig.Name} ★</color>";
entity.Spawn();
// Add to active monsters
activeMonsters.Add(new HuntMonster
{
Entity = entity,
Combat = combat,
Config = selectedConfig,
SpawnPosition = spawnPos,
SpawnTime = Time.realtimeSinceStartup,
IsAlive = true
});
Puts($"Spawned monster: {selectedConfig.Name} at {spawnPos}");
// Create map marker
CreateMonsterMarker(entity, selectedConfig);
}
private Vector3 GetRandomSpawnPosition()
{
// Try up to 20 times to find a valid spawn position
for (int i = 0; i < 20; i++)
{
float x = (float)random.NextDouble() * TerrainMeta.Size.x;
float z = (float)random.NextDouble() * TerrainMeta.Size.z;
float y = TerrainMeta.HeightMap.GetHeight(new Vector3(x, 0, z));
Vector3 pos = new Vector3(x, y, z);
// Check if not too close to monuments or other monsters
bool valid = true;
foreach (var monument in UnityEngine.Object.FindObjectsOfType<MonumentInfo>())
{
if (Vector3.Distance(pos, monument.transform.position) < 100f)
{
valid = false;
break;
}
}
if (valid) return pos;
}
// Fallback to a default position
return new Vector3(0, TerrainMeta.HeightMap.GetHeight(new Vector3(0, 0, 0)), 0);
}
private void CreateMonsterMarker(BaseEntity entity, MonsterConfig config)
{
var marker = GameManager.server.CreateEntity("assets/prefabs/deployable/chickencoop/chickencoop.deployed.prefab", entity.transform.position);
if (marker != null)
{
// Use marker visibility system
foreach (var player in BasePlayer.activePlayerList)
{
player.SendConsoleCommand("createmarker", entity.net.ID, $"☠️ {config.Name}", config.Color, entity.transform.position);
}
}
}
private void DespawnAllMonsters()
{
foreach (var monster in activeMonsters)
{
if (monster.Entity != null && !monster.Entity.IsDestroyed)
{
monster.Entity.Kill();
}
}
activeMonsters.Clear();
}
private void OnEntityDeath(BaseCombatEntity entity, HitInfo info)
{
// Check if this is one of our monsters
var huntMonster = activeMonsters.FirstOrDefault(m => m.Combat == entity);
if (huntMonster == null) return;
huntMonster.IsAlive = false;
// Determine who killed it
BasePlayer killer = info?.InitiatorPlayer;
// Give rewards to killer and nearby players
List<BasePlayer> rewardedPlayers = new List<BasePlayer>();
if (killer != null && killer.IsConnected)
{
rewardedPlayers.Add(killer);
GiveReward(killer, huntMonster.Config);
}
// Also reward players within 50m
foreach (var player in BasePlayer.activePlayerList)
{
if (player != killer && Vector3.Distance(player.transform.position, huntMonster.Entity.transform.position) <= 50f)
{
rewardedPlayers.Add(player);
GiveReward(player, huntMonster.Config);
}
}
// Announce death
string killMessage = $"<color=red>💀 {huntMonster.Config.Name} has been defeated!</color>";
if (killer != null)
{
killMessage = $"<color=green>🏆 {killer.displayName} killed {huntMonster.Config.Name}!</color>";
}
Server.Broadcast(killMessage);
// Drop loot chest
DropLootCrate(huntMonster.Entity.transform.position, huntMonster.Config);
// Remove from active list
activeMonsters.Remove(huntMonster);
}
private void GiveReward(BasePlayer player, MonsterConfig monsterConfig)
{
Random rnd = new Random();
foreach (var reward in config.KillRewards)
{
int amount = reward.Amount;
if (reward.MinAmount > 0 && reward.MaxAmount > 0)
{
amount = rnd.Next(reward.MinAmount, reward.MaxAmount + 1);
}
ItemDefinition itemDef = ItemManager.FindItemDefinition(reward.ShortName);
if (itemDef != null)
{
Item item = ItemManager.Create(itemDef, amount);
if (item != null && player.inventory.GiveItem(item))
{
SendReply(player, $"<color=green>+{amount} {reward.DisplayName}</color> for killing {monsterConfig.Name}!");
}
}
}
// Also give some XP if XPLevels is installed
PlayerData(player.userID);
}
private void DropLootCrate(Vector3 position, MonsterConfig config)
{
// Spawn a loot crate at death position
var crate = GameManager.server.CreateEntity("assets/prefabs/misc/supply drop/supply_drop.prefab", position);
if (crate != null)
{
crate.Spawn();
var lootContainer = crate.GetComponent<LootContainer>();
if (lootContainer != null)
{
lootContainer.PopulateLoot();
// Add extra monster-specific loot
foreach (var reward in config.KillRewards)
{
// Additional logic for crate loot
}
}
}
}
private void PlayerData(ulong userId)
{
// Placeholder for XP integration
// This would connect to XPLevels plugin if installed
}
private void RemoveMarkersForMonster(HuntMonster monster)
{
foreach (var player in BasePlayer.activePlayerList)
{
player.SendConsoleCommand("removemarker", monster.Entity.net.ID);
}
}
#endregion
#region Commands
[ChatCommand("monsters")]
private void cmdMonsters(BasePlayer player, string command, string[] args)
{
if (activeMonsters.Count == 0)
{
SendReply(player, "<color=yellow>No active monsters on the map.</color>");
return;
}
SendReply(player, "<color=yellow>=== Active Monsters ===</color>");
foreach (var monster in activeMonsters)
{
float distance = Vector3.Distance(player.transform.position, monster.SpawnPosition);
SendReply(player, $"☠️ {monster.Config.Name} - {distance:F0}m away");
}
}
[ConsoleCommand("monsterhunt.spawn")]
private void cmdSpawnMonster(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "monsterhunt.admin"))
{
args.ReplyWith("No permission");
return;
}
SpawnRandomMonster();
args.ReplyWith("Monster spawned manually");
}
[ConsoleCommand("monsterhunt.clear")]
private void cmdClearMonsters(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "monsterhunt.admin"))
{
args.ReplyWith("No permission");
return;
}
DespawnAllMonsters();
args.ReplyWith($"Cleared {activeMonsters.Count} monsters");
}
[ConsoleCommand("monsterhunt.status")]
private void cmdStatus(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "monsterhunt.admin"))
{
args.ReplyWith("No permission");
return;
}
args.ReplyWith($"MonsterHunt: {activeMonsters.Count} active monsters, next spawn in ~{config.SpawnIntervalMinutes} minutes");
}
[ConsoleCommand("monsterhunt.reload")]
private void cmdReload(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "monsterhunt.admin"))
{
args.ReplyWith("No permission");
return;
}
LoadConfig();
args.ReplyWith("MonsterHunt config reloaded");
}
#endregion
}
}