C#:
using System;
using System.Collections.Generic;
using System.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Bounty Hunter", "0killrest", "1.0.0")]
[Description("Place bounties on players, hunt them down for rewards")]
internal class BountyHunter : RustPlugin
{
#region Configuration
private static ConfigData config;
private class ConfigData
{
public bool Enabled { get; set; } = true;
public int MinBountyAmount { get; set; } = 100;
public int MaxBountyAmount { get; set; } = 10000;
public int HunterRewardPercentage { get; set; } = 90; // 90% goes to hunter, 10% tax
public bool AnnounceNewBounty { get; set; } = true;
public bool ShowBountyOnMap { get; set; } = true;
public int BountyExpiryHours { get; set; } = 48;
public List<string> BannedFromBounty { get; set; }
public ConfigData()
{
BannedFromBounty = new List<string> { "admin", "vip" };
}
}
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/BountyHunter.json");
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
#endregion
#region Data Storage
private Dictionary<ulong, BountyData> activeBounties = new Dictionary<ulong, BountyData>();
private Dictionary<ulong, HunterStats> hunterStats = new Dictionary<ulong, HunterStats>();
private class BountyData
{
public ulong TargetId { get; set; }
public string TargetName { get; set; }
public ulong PlacedById { get; set; }
public string PlacedByName { get; set; }
public int Amount { get; set; }
public DateTime PlacedTime { get; set; }
public bool IsActive { get; set; } = true;
}
private class HunterStats
{
public int BountiesCollected { get; set; }
public int TotalEarned { get; set; }
public int CurrentStreak { get; set; }
public int BestStreak { get; set; }
}
private void LoadData()
{
activeBounties = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, BountyData>>("BountyHunter_Active") ?? new Dictionary<ulong, BountyData>();
hunterStats = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, HunterStats>>("BountyHunter_Stats") ?? new Dictionary<ulong, HunterStats>();
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject("BountyHunter_Active", activeBounties);
Interface.Oxide.DataFileSystem.WriteObject("BountyHunter_Stats", hunterStats);
}
#endregion
#region Hooks
private void Init()
{
LoadData();
permission.RegisterPermission("bountyhunter.admin", this);
permission.RegisterPermission("bountyhunter.immune", this);
permission.RegisterPermission("bountyhunter.place", this);
}
private void OnServerInitialized()
{
if (config.Enabled)
{
// Check for expired bounties every hour
timer.Repeat(3600f, 0, () => CheckExpiredBounties());
// Update map markers for bounties
if (config.ShowBountyOnMap)
{
timer.Repeat(60f, 0, () => UpdateBountyMarkers());
}
}
}
private void Unload()
{
SaveData();
}
private void OnServerSave() => SaveData();
private void OnPlayerDeath(BasePlayer victim, HitInfo info)
{
if (!config.Enabled) return;
if (victim == null) return;
BasePlayer killer = info?.InitiatorPlayer;
if (killer == null || killer == victim) return;
// Check if victim has a bounty
if (activeBounties.ContainsKey(victim.userID))
{
var bounty = activeBounties[victim.userID];
if (!bounty.IsActive) return;
// Check if killer is immune
if (permission.UserHasPermission(killer.UserIDString, "bountyhunter.immune"))
{
SendReply(killer, $"<color=yellow>{victim.displayName} has a bounty, but you are immune to bounty hunting!</color>");
return;
}
// Calculate reward
int reward = (int)(bounty.Amount * (config.HunterRewardPercentage / 100f));
int tax = bounty.Amount - reward;
// Give reward to killer
Item rewardScrap = ItemManager.Create(ItemManager.FindItemDefinition("scrap"), reward);
if (rewardScrap != null)
{
killer.inventory.GiveItem(rewardScrap);
}
// Update hunter stats
if (!hunterStats.ContainsKey(killer.userID))
{
hunterStats[killer.userID] = new HunterStats();
}
hunterStats[killer.userID].BountiesCollected++;
hunterStats[killer.userID].TotalEarned += reward;
hunterStats[killer.userID].CurrentStreak++;
if (hunterStats[killer.userID].CurrentStreak > hunterStats[killer.userID].BestStreak)
{
hunterStats[killer.userID].BestStreak = hunterStats[killer.userID].CurrentStreak;
}
// Reset streak for others? No, streak is personal
// Announce to server
Server.Broadcast($"<color=red>🔪 BOUNTY COLLECTED!</color> <color=green>{killer.displayName}</color> claimed <color=yellow>{reward} scrap</color> for eliminating <color=orange>{victim.displayName}</color>!");
// Notify the one who placed the bounty
var placer = BasePlayer.FindByID(bounty.PlacedById);
if (placer != null && placer.IsConnected)
{
SendReply(placer, $"<color=green>Your bounty on {victim.displayName} has been claimed by {killer.displayName}! {reward} scrap paid out.</color>");
}
// Remove bounty
activeBounties.Remove(victim.userID);
SaveData();
// Visual effect on kill
Effect.server.Run("assets/prefabs/locks/keylock/effects/lock.code.updated.prefab", victim.transform.position, Vector3.zero);
// Remove map marker
RemoveBountyMarker(victim.userID);
return;
}
// If killer killed someone without bounty, reset their streak
if (hunterStats.ContainsKey(killer.userID))
{
if (hunterStats[killer.userID].CurrentStreak > 0)
{
hunterStats[killer.userID].CurrentStreak = 0;
SendReply(killer, $"<color=yellow>Your bounty hunting streak has been reset!</color>");
}
}
}
private void CheckExpiredBounties()
{
var expired = new List<ulong>();
foreach (var bounty in activeBounties)
{
if ((DateTime.UtcNow - bounty.Value.PlacedTime).TotalHours > config.BountyExpiryHours)
{
expired.Add(bounty.Key);
// Notify placer
var placer = BasePlayer.FindByID(bounty.Value.PlacedById);
if (placer != null && placer.IsConnected)
{
SendReply(placer, $"<color=yellow>Your bounty on {bounty.Value.TargetName} has expired.You lost {bounty.Value.Amount} scrap.</color>");
}
}
}
foreach (var targetId in expired)
{
RemoveBountyMarker(targetId);
activeBounties.Remove(targetId);
}
if (expired.Count > 0)
{
SaveData();
Puts($"Expired {expired.Count} bounties");
}
}
private void UpdateBountyMarkers()
{
if (!config.ShowBountyOnMap) return;
foreach (var bounty in activeBounties)
{
var target = BasePlayer.FindByID(bounty.Key);
if (target != null && target.IsConnected && !target.IsDead())
{
AddOrUpdateBountyMarker(target);
}
}
}
private void AddOrUpdateBountyMarker(BasePlayer target)
{
// Remove old marker first
RemoveBountyMarker(target.userID);
// Add new marker
foreach (var player in BasePlayer.activePlayerList)
{
if (player != target) // Target doesn't see their own marker
{
player.SendConsoleCommand("createmarker",
$"bounty_{target.userID}",
$"🔪 BOUNTY: {activeBounties[target.userID].Amount} scrap",
"red",
target.transform.position);
}
}
}
private void RemoveBountyMarker(ulong targetId)
{
foreach (var player in BasePlayer.activePlayerList)
{
player.SendConsoleCommand("removemarker", $"bounty_{targetId}");
}
}
#endregion
#region Core Logic
private bool CanPlaceBounty(BasePlayer placer, BasePlayer target, int amount)
{
// Check if enabled
if (!config.Enabled)
{
SendReply(placer, "Bounty system is currently disabled.");
return false;
}
// Check self
if (placer.userID == target.userID)
{
SendReply(placer, "You cannot place a bounty on yourself!");
return false;
}
// Check if target has immunity
if (permission.UserHasPermission(target.UserIDString, "bountyhunter.immune"))
{
SendReply(placer, "This player is immune to bounties!");
return false;
}
// Check if target already has active bounty
if (activeBounties.ContainsKey(target.userID))
{
SendReply(placer, $"<color=red>{target.displayName} already has an active bounty of {activeBounties[target.userID].Amount} scrap!</color>");
return false;
}
// Check amount limits
if (amount < config.MinBountyAmount)
{
SendReply(placer, $"Minimum bounty amount is {config.MinBountyAmount} scrap");
return false;
}
if (amount > config.MaxBountyAmount)
{
SendReply(placer, $"Maximum bounty amount is {config.MaxBountyAmount} scrap");
return false;
}
// Check if placer has enough scrap
int playerScrap = placer.inventory.GetAmount(ItemManager.FindItemDefinition("scrap").itemid);
if (playerScrap < amount)
{
SendReply(placer, $"You don't have enough scrap! You have {playerScrap}, need {amount}");
return false;
}
// Check permission
if (!permission.UserHasPermission(placer.UserIDString, "bountyhunter.place"))
{
SendReply(placer, "You don't have permission to place bounties!");
return false;
}
return true;
}
private bool PlaceBounty(BasePlayer placer, BasePlayer target, int amount)
{
if (!CanPlaceBounty(placer, target, amount)) return false;
// Take scrap from placer
placer.inventory.Take(null, ItemManager.FindItemDefinition("scrap").itemid, amount);
// Create bounty
var bounty = new BountyData
{
TargetId = target.userID,
TargetName = target.displayName,
PlacedById = placer.userID,
PlacedByName = placer.displayName,
Amount = amount,
PlacedTime = DateTime.UtcNow,
IsActive = true
};
activeBounties[target.userID] = bounty;
SaveData();
// Add map marker
if (config.ShowBountyOnMap)
{
AddOrUpdateBountyMarker(target);
}
// Announce
if (config.AnnounceNewBounty)
{
string message = $"<color=red>⚠️ BOUNTY PLACED!</color> <color=orange>{target.displayName}</color> now has a <color=yellow>{amount} scrap</color> bounty on their head!";
Server.Broadcast(message);
}
// Visual effect
Effect.server.Run("assets/prefabs/locks/keylock/effects/lock.code.updated.prefab", target.transform.position, Vector3.zero);
// Notify target
SendReply(target, $"<color=red>⚠️ A bounty of {amount} scrap has been placed on your head by {placer.displayName}!</color>");
SendReply(target, $"<color=yellow>Survive {config.BountyExpiryHours} hours to remove it, or someone will claim it!</color>");
SendReply(placer, $"<color=green>✅ Bounty of {amount} scrap placed on {target.displayName}!</color>");
Puts($"Bounty placed: {target.displayName} ({target.userID}) for {amount} scrap by {placer.displayName}");
return true;
}
#endregion
#region Commands
[ChatCommand("bounty")]
private void cmdBounty(BasePlayer player, string command, string[] args)
{
if (args.Length == 0)
{
ShowBountyHelp(player);
return;
}
switch (args[0].ToLower())
{
case "place":
case "set":
if (args.Length < 3)
{
SendReply(player, "Usage: /bounty place <playerName> <amount>");
return;
}
var targetName = args[1];
if (!int.TryParse(args[2], out int amount))
{
SendReply(player, "Amount must be a number!");
return;
}
var target = BasePlayer.Find(targetName);
if (target == null)
{
SendReply(player, "Player not found!");
return;
}
PlaceBounty(player, target, amount);
break;
case "list":
ShowBountyList(player);
break;
case "top":
ShowTopHunters(player);
break;
case "stats":
ShowMyStats(player);
break;
case "remove":
if (args.Length < 2)
{
SendReply(player, "Usage: /bounty remove <playerName>");
return;
}
if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, "bountyhunter.admin"))
{
SendReply(player, "Only admins can remove bounties!");
return;
}
var removeTarget = BasePlayer.Find(args[1]);
if (removeTarget == null)
{
SendReply(player, "Player not found!");
return;
}
if (activeBounties.ContainsKey(removeTarget.userID))
{
RemoveBountyMarker(removeTarget.userID);
activeBounties.Remove(removeTarget.userID);
SaveData();
SendReply(player, $"Removed bounty on {removeTarget.displayName}");
}
else
{
SendReply(player, "That player has no active bounty");
}
break;
default:
ShowBountyHelp(player);
break;
}
}
private void ShowBountyHelp(BasePlayer player)
{
SendReply(player, "<color=yellow>=== Bounty Hunter System ===</color>");
SendReply(player, "/bounty place <name> <amount> - Place bounty on player");
SendReply(player, "/bounty list - Show active bounties");
SendReply(player, "/bounty top - Show top hunters");
SendReply(player, "/bounty stats - Show your hunting stats");
SendReply(player, $"Min bounty: {config.MinBountyAmount} | Max: {config.MaxBountyAmount}");
SendReply(player, $"Reward: {config.HunterRewardPercentage}% of bounty amount");
}
private void ShowBountyList(BasePlayer player)
{
if (activeBounties.Count == 0)
{
SendReply(player, "<color=yellow>No active bounties at the moment!</color>");
return;
}
SendReply(player, "<color=yellow>=== Active Bounties ===</color>");
foreach (var bounty in activeBounties)
{
var target = BasePlayer.FindByID(bounty.Key);
string status = target != null && target.IsConnected ? "🟢 Online" : "⚫ Offline";
SendReply(player, $"🔪 {bounty.Value.TargetName} - {bounty.Value.Amount} scrap - Placed by {bounty.Value.PlacedByName} - {status}");
}
}
private void ShowTopHunters(BasePlayer player)
{
var topHunters = hunterStats.OrderByDescending(h => h.Value.BountiesCollected).Take(10).ToList();
if (topHunters.Count == 0)
{
SendReply(player, "<color=yellow>No hunters yet! Be the first!</color>");
return;
}
SendReply(player, "<color=yellow>=== Top 10 Bounty Hunters ===</color>");
int rank = 1;
foreach (var hunter in topHunters)
{
var hunterPlayer = BasePlayer.FindByID(hunter.Key);
string name = hunterPlayer != null ? hunterPlayer.displayName : hunter.Key.ToString();
SendReply(player, $"#{rank} - {name} - {hunter.Value.BountiesCollected} kills - {hunter.Value.TotalEarned} scrap earned");
rank++;
}
}
private void ShowMyStats(BasePlayer player)
{
if (!hunterStats.ContainsKey(player.userID))
{
SendReply(player, "<color=yellow>You haven't collected any bounties yet!</color>");
return;
}
var stats = hunterStats[player.userID];
SendReply(player, "<color=yellow>=== Your Hunter Stats ===</color>");
SendReply(player, $"🔪 Bounties collected: {stats.BountiesCollected}");
SendReply(player, $"💰 Total earned: {stats.TotalEarned} scrap");
SendReply(player, $"🔥 Current streak: {stats.CurrentStreak}");
SendReply(player, $"🏆 Best streak: {stats.BestStreak}");
}
[ConsoleCommand("bounty.reload")]
private void cmdReload(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "bountyhunter.admin"))
{
args.ReplyWith("No permission");
return;
}
LoadConfig();
LoadData();
args.ReplyWith("BountyHunter reloaded");
Puts("BountyHunter configuration reloaded");
}
[ConsoleCommand("bounty.clearall")]
private void cmdClearAll(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "bountyhunter.admin"))
{
args.ReplyWith("No permission");
return;
}
foreach (var bounty in activeBounties)
{
RemoveBountyMarker(bounty.Key);
}
activeBounties.Clear();
SaveData();
args.ReplyWith("All bounties cleared");
}
#endregion
}
}