C#:
using System;
using System.Collections.Generic;
using System.Linq;
using Oxide.Core;
using UnityEngine;
using Quaternion = UnityEngine.Quaternion;
using Random = UnityEngine.Random;
using Newtonsoft.Json;
using Oxide.Core.Libraries;
using Oxide.Core.Plugins;
using UnityEngine.AI;
using Time = UnityEngine.Time;
using Facepunch;
/*Developer API
Dictionary<ulong, int> GetBountyStats() */
// Returns a Dictionary<ulong, int> where the key is the player's ID and the value is the number of bounty kills.
// If no data is available, it returns an empty dictionary.
/*
* Changelog:
*
* Version 1.3.1:
* - Performance Improvements.
*/
namespace Oxide.Plugins {
[Info("Bounty Hunter", "Wrecks", "1.3.1")]
[Description("Spawns Bounties at Monuments for Customizable Rewards.")]
public class BountyHunter : RustPlugin {
[PluginReference]
private Plugin Economics, ServerRewards, Kits, MarkerManager, SkillTree;
#region lang
protected override void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string, string> {
["NoPerms"]
= "[<color=#b5a642>Bounty Hunter</color>] You do not have permission to run this <color=#b54251>Command</color>!", ["NoTokens"] = "[<color=#b5a642>Bounty Hunter</color>] :coffeecan: You have no <color=#b5a642>Bounty Tokens</color> on you that I can cash out. \nBe on the look out, see you soon!\n\n:exclamation:", ["BountyHunterCooldownMessage"] = "[<color=#b5a642>Bounty Hunter</color>] :angry: Wait <color=#a642b5>{0}</color> second(s)!, I think I spotted a <color=#b5a642>Bounty</color>...", ["EconomicsMessage"] = "[<color=#b5a642>Bounty Hunter</color>] :eyes: \n\n I can net you <color=#85bb65>$</color><color=#42b5a6>{0}</color> for your <color=#42b5a6>{1}x</color> <color=#a642b5>{2}</color>(s).", ["SrMessage"] = "[<color=#b5a642>Bounty Hunter</color>] :eyes: \n\n I can net you <color=#42b5a6>{0}</color> <color=#cd5c5c>RP</color> for your <color=#42b5a6>{1}x</color> <color=#a642b5>{2}</color>(s).", ["SaleDisabled"] = "[<color=#b5a642>Bounty Hunter</color>] <color=#42b5a6>Sales</color> are Disabled in the Config, Enable and Try again.", ["NoBountyHunterLicense"] = "[<color=#b5a642>Bounty Hunter</color>] :eyebrow: Are you out there targeting Bounties without a <color=#42b5a6>License</color>? \nUse <color=#42b5a6>/BuyBL</color> to purchase one.", ["AlreadyHasLicense"] = "[<color=#b5a642>Bounty Hunter</color>] You already have a <color=#b54251>Bounty Hunter License</color>.", ["BuyBountyHunterLicenseSuccess"] = "[<color=#b5a642>Bounty Hunter</color>] <color=#b54251>Bounty Hunter License</color> Obtained. \nYou were charged {0}<color=#3e9c35>{1}</color>, Stay Vigilant, <color=#8bb542>{2}</color>!", ["InsufficientFunds"] = "[<color=#b5a642>Bounty Hunter</color>] You don't have enough funds to purchase a <color=#b5a642>Bounty Hunter License</color>. \nIt costs <color=#42b5a6>{0}</color><color=#a642b5>{1}</color>.", ["Despawn"] = "[<color=#b5a642>Bounty Hunter</color>] The <color=#42b5a6>{0}</color> has <color=#b54251>Fled</color> the Island.", ["BountySpotted"] = "[<color=#b5a642>Bounty Hunter</color>]\n\n A <color=#42b5a6>{0}</color> has been spotted near <color=#b56d42>{1}</color>.", ["BountyEliminated"] = "[<color=#b5a642>Bounty Hunter</color>] The <color=#42b5a6>{0}</color> was <color=#b5a642>Eliminated</color> by <color=#8bb542>{1}</color>!", ["BountyDrop"] = "[<color=#b5a642>Bounty Hunter</color>] The <color=#42b5a6>{0}</color> dropped a pouch! Open it and deliver the <color=#42b5a6>Bounty Token</color> to the Bounty Hunter for your Reward!", ["Revoked"] = "[<color=#b5a642>Bounty Hunter</color>] Your <color=#42b5a6>Bounty License</color> has been <color=#42b5a6>Revoked</color> be wary of Death next time!"
}
, this);
}
#endregion
#region rewardhandling
private void RewardHandler(BasePlayer player, BountyTiers tier, int quantity, int baseSalePrice) {
double bonusPercentage = Random.Range(tier.MaxDiscount, tier.MaxBonus + 1);
bonusPercentage = Mathf.Clamp((float)bonusPercentage, -100f, 100f);
var adjustedSalePrice = baseSalePrice * (1 + bonusPercentage / 100);
var totalReward = quantity * (int)adjustedSalePrice;
switch (_config.EconomyPlugin) {
case 1 : {
PayoutEco(player, tier, quantity, totalReward, bonusPercentage);
break;
}
case 2 : {
PayoutSR(player, tier, quantity, totalReward, bonusPercentage);
break;
}
}
}
private void PayoutSR(BasePlayer player, BountyTiers tier, int quantity, int totalReward, double bonusPercentage) {
ServerRewards.Call("AddPoints", player.UserIDString, totalReward);
var message = lang.GetMessage("SrMessage", this, player.UserIDString);
Player.Message(player, string.Format(message, totalReward, quantity, tier.TokenName, bonusPercentage), null, _config.ChatIcon);
SendSrFx(player);
SendDiscord(player, tier, quantity, totalReward);
}
private void PayoutEco(BasePlayer player, BountyTiers tier, int quantity, int totalReward, double bonusPercentage) {
Economics.Call("Deposit", player.UserIDString, (double)totalReward);
var message = lang.GetMessage("EconomicsMessage", this, player.UserIDString);
Player.Message(player, string.Format(message, totalReward, quantity, tier.TokenName, bonusPercentage), null, _config.ChatIcon);
SendEcoFx(player);
SendDiscord(player, tier, quantity, totalReward);
return;
}
#endregion
#region bountytiers
public class BountyTiers {
[JsonProperty("Tier Name")]
public string TierName {
get;
set;
}
[JsonProperty("Skill Tree Xp Value")]
public double Value {
get;
set;
}
[JsonProperty("Bounty Kit Enabled?")]
public bool BountyKitEnabled {
get;
set;
}
[JsonProperty("Bounty Kit Name?")]
public string BountyKitName {
get;
set;
}
= "";
[JsonProperty("Bounty Clothing Options")]
public List<BountyClothingOption> BountyClothingOptions {
get;
set;
}
[JsonProperty("Bounty Total Health")]
public int BountyHealth {
get;
set;
}
[JsonProperty("Bounty Damage Scaling")]
public float BountyDamageOutput {
get;
set;
}
[JsonProperty("Aim Cone Scale")]
public float AimConeScale {
get;
set;
}
[JsonProperty("Weapon to Equip?")]
public string WeaponName {
get;
set;
}
[JsonProperty("Weapon Skin ID")]
public ulong WeaponSkin {
get;
set;
}
[JsonProperty("Bounty Token Name")]
public string TokenName {
get;
set;
}
[JsonProperty("Bounty Token Skin")]
public ulong TokenSkinID {
get;
set;
}
[JsonProperty("Is the Token Marketable to the Bounty NPC?")]
public bool Sellable {
get;
set;
}
[JsonProperty("Sale Price?")]
public double SalePrice {
get;
set;
}
[JsonProperty("Max Discount Buy Variation in %? (Negative Values) (To Simulate Supply & Demand)")]
public float MaxDiscount {
get;
set;
}
[JsonProperty("Maximum Bonus Variation in %? (Positive Values) (To Simulate Supply & Demand)")]
public float MaxBonus {
get;
set;
}
[JsonProperty("Min Item Drop")]
public int MinItemDrop {
get;
set;
}
= 1;
[JsonProperty("Max Item Drop")]
public int MaxItemDrop {
get;
set;
}
= 3;
[JsonProperty("Bounty Optional Drops")]
public List<BountyDrops> BountyDropsList {
get;
set;
}
}
#endregion
#region Declarations
private int _botCount;
private Timer spawnTimer;
private const string AdminPermission = "BountyHunter.Admin";
private const string LifetimeLicense = "BountyHunter.LifetimeLicense";
private readonly Dictionary<string, List<Vector3>> _monumentSpawnPoints = new();
private readonly Dictionary<string, List<Vector3>> _dynamicSpawnPoints = new();
private Dictionary<string, List<Vector3>> _customSpawnPoints = new();
private readonly Dictionary<ulong, float> _interactionCooldowns = new();
private readonly Dictionary<string, BountyTiers> _botTierMap = new();
private readonly List<ScientistNPC> _spawnedBounties = new();
private readonly Dictionary<string, Timer> _despawnTimers = new();
private readonly List<NpcData> _npcDataList = new();
private readonly List<NPCTalking> _bountyNpCs = new();
private List<BountyLicense> _playerLicenses = new();
private Dictionary<ulong, int> _playerData = new();
private const string CustomSpawn = "CustomSpawn";
private const string ReloadFx = "assets/prefabs/weapons/grenade launcher/effects/reload_end.prefab";
private const string DeniedFx = "assets/prefabs/locks/keypad/effects/lock.code.denied.prefab";
private const string SpawnedFx = "assets/prefabs/deployable/research table/effects/research-success.prefab";
private const string DespawnedFx = "assets/bundled/prefabs/fx/notice/stack.world.fx.prefab";
private const string SuccessFx = "assets/prefabs/weapons/spas12/effects/pump_forward.prefab";
private const string RicochetFx = "assets/bundled/prefabs/fx/ricochet/ricochet2.prefab";
private const string NoTokensFx = "assets/prefabs/instruments/jerrycanguitar/effects/guitardeploy.prefab";
private const string Drop = "assets/prefabs/misc/item drop/item_drop_backpack.prefab";
private const string Npc = "assets/prefabs/npc/bandit/missionproviders/missionprovider_bandit_a.prefab";
private const string NpcFx = "assets/prefabs/misc/easter/painted eggs/effects/gold_open.prefab";
private const string RemovalFx = "assets/prefabs/misc/easter/painted eggs/effects/bronze_open.prefab";
private const string ServerRewardsFx = "assets/prefabs/food/small water bottle/effects/water-bottle-deploy.prefab";
private const string EconomicsFx = "assets/prefabs/deployable/vendingmachine/effects/vending-machine-purchase-human.prefab";
private const string DiscordArt = "https://cdn.discordapp.com/attachments/1200248277640163344/1204607519981572126/BOUNTYHUNTERDRAFT.png?ex=65d5592d&is=65c2e42d&hm=1b0dad7de65f6c8bfa798ede2074cdde30572722ba8b1b961d923d16dd6f95f2&";
#endregion
#region Config
private static Configuration _config;
public class Configuration {
[JsonProperty("Spawn Bots On Plugin Reload?")]
public bool SpawnOnReload {
get;
set;
}
[JsonProperty("Amount to Spawn On Plugin Reload(Must be less than Max Active Bounties)?")]
public int AmountToSpawnOnReload {
get;
set;
}
= 1;
[JsonProperty("Announce Top Bounty Hunters To Chat Every x Seconds (0 To Disable)")]
public int AnnounceInterval {
get;
set;
}
= 600;
[JsonProperty("Bounty Prefab Path")]
public string BountyPrefab {
get;
set;
}
= "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_heavy.prefab";
[JsonProperty("Discord Webhook URL")]
public string Webhook {
get;
set;
}
[JsonProperty("Enable Skill Tree Xp Gain?")]
public bool EnabledSkillTreeXp {
get;
set;
}
[JsonProperty("Require License for Xp Gain?")]
public bool XpLicReq {
get;
set;
}
[JsonProperty("Enable Monument Spawns?")]
public bool EnableMonumentSpawns {
get;
set;
}
[JsonProperty("Enable Custom Spawns?")]
public bool EnableCustomSpawns {
get;
set;
}
[JsonProperty("Clear Custom Spawns File On Wipe?")]
public bool ClearCustomSpawns {
get;
set;
}
[JsonProperty("Chat Icon")]
public ulong ChatIcon {
get;
set;
}
[JsonProperty("Maximum Active Bounties")]
public int MaxBounties {
get;
set;
}
[JsonProperty("Drop Bounty Tokens In Pouch?")]
public bool TokensEnabled {
get;
set;
}
[JsonProperty("Base Item for the Bounty Tokens?")]
public string PlaceholderItem {
get;
set;
}
[JsonProperty("Tokens Marketable to the Bounty NPC?")]
public bool TokensMarketable {
get;
set;
}
[JsonProperty("Console Command to Purchase a Bounty License? (For GUI Shops)")]
public string ConsoleCommand {
get;
set;
}
= "buybl";
[JsonProperty("Chat Command to Purchase a Bounty License?")]
public string ChatCommand {
get;
set;
}
= "buybl";
[JsonProperty("Require a Bounty License To Cash In Tokens to the Bounty NPC?")]
public bool LicenseRequired {
get;
set;
}
[JsonProperty("Price to Purchase a Bounty License?")]
public int LicenseFee {
get;
set;
}
[JsonProperty("Lose Bounty License on Death?")]
public bool RemoveLicense {
get;
set;
}
[JsonProperty("Clear Licenses on Wipe?")]
public bool ClearLicenses {
get;
set;
}
[JsonProperty("Dynamic Spawn at this Monument (Requires your Position Input to be set to match Monument if Changing from Bandit.)")]
public string DynamicSpawnMonument {
get;
set;
}
= "assets/bundled/prefabs/autospawn/monument/medium/bandit_town.prefab";
[JsonProperty("Dynamic Spawn Points (Relative to Monument)")]
public List<Vector3> DynamicSpawnPoints {
get;
set;
}
[JsonProperty("Dynamic Spawn Rotation Correction")]
public float NpcSpawnRotation {
get;
set;
}
[JsonProperty("Clear Kill Counters on Wipe?")]
public bool ClearKills {
get;
set;
}
[JsonProperty("Dynamic (Bandit Camp) Spawn Of The NPC Enabled?")]
public bool DynamicSpawn {
get;
set;
}
= true;
[JsonProperty("Economy Plugin - 1 Economics - 2 Server Rewards")]
public int EconomyPlugin {
get;
set;
}
[JsonProperty("Maximum Amount of Like Tokens Sold at a Time")]
public int MaxTradeQuantity {
get;
set;
}
[JsonProperty("NPC Interaction Cooldown In Seconds")]
public float InteractionCooldown {
get;
set;
}
[JsonProperty("NPC Kit Enabled?")]
public bool NpcKitEnabled {
get;
set;
}
[JsonProperty("NPC Kit Name?")]
public string NpcKitName {
get;
set;
}
[JsonProperty("Amount of Time In Seconds Between Bounty Spawns")]
public int BountySpawnRate {
get;
set;
}
[JsonProperty("Amount of Time In Seconds for a Bounty to Despawn")]
public int BountyDespawnTimer {
get;
set;
}
[JsonProperty("Enable Spawn SFX?")]
public bool SpawnFX {
get;
set;
}
= true;
[JsonProperty("Enable Despawn SFX?")]
public bool DespawnFX {
get;
set;
}
= true;
[JsonProperty("Announce to Chat When Bounty is Active?")]
public bool AnnounceBountyActive {
get;
set;
}
[JsonProperty("Announce to Chat When Bounty is Claimed?")]
public bool AnnounceBountyClaimed {
get;
set;
}
[JsonProperty("Enable Marker Manager from UMOD, To Mark Bounties?")]
public bool MarkerEnabled {
get;
set;
}
[JsonProperty("Enable Marker Manager from UMOD, To Mark NPC Location?")]
public bool MarkerEnabledForNPC {
get;
set;
}
= true;
[JsonProperty("Bounty Tiers")]
public List<BountyTiers> BountyTiers {
get;
set;
}
[JsonProperty("NPC Weapon")]
public string NpcWeapon {
get;
set;
}
= "rifle.bolt";
[JsonProperty("NPC Weapon Skin")]
public ulong NpcWeaponSkin {
get;
set;
}
= 819149392;
[JsonProperty("NPC Clothing Options")]
public List<ClothingOption> NpcClothingOptions {
get;
set;
}
public static Configuration DefaultConfig() {
return new Configuration {
SpawnOnReload = false, AmountToSpawnOnReload = 1, AnnounceInterval = 600, BountyPrefab = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_heavy.prefab", Webhook = "INSERT_WEBHOOK_URL", EnabledSkillTreeXp = false, XpLicReq = true, EnableMonumentSpawns = true, EnableCustomSpawns = false, ClearCustomSpawns = false, ChatIcon = 0, MaxBounties = 3, TokensEnabled = true, PlaceholderItem = "blood", LicenseRequired = true, LicenseFee = 10000, RemoveLicense = true, ClearLicenses = false, DynamicSpawnMonument = "assets/bundled/prefabs/autospawn/monument/medium/bandit_town.prefab", DynamicSpawnPoints = new List<Vector3> {
new(64.86f, 2.00f, -46.20f), new(-0.58f, 0.83f, -68.93f)
}
, NpcSpawnRotation = 45f, ClearKills = false, DynamicSpawn = true, TokensMarketable = true, ConsoleCommand = "buybl", ChatCommand = "buybl", EconomyPlugin = 1, MaxTradeQuantity = 3, InteractionCooldown = 3f, NpcKitEnabled = false, NpcKitName = "", BountySpawnRate = 600, BountyDespawnTimer = 1200, SpawnFX = true, DespawnFX = true, AnnounceBountyActive = true, AnnounceBountyClaimed = true, MarkerEnabled = true, MarkerEnabledForNPC = true, NpcWeapon = "rifle.bolt", NpcWeaponSkin = 819149392, NpcClothingOptions = new List<ClothingOption> {
new() {
Shortname = "burlap.trousers", Skin = 1760352876
}
, new() {
Shortname = "burlap.shirt", Skin = 1760350097
}
, new() {
Shortname = "shoes.boots", Skin = 547978997
}
, new() {
Shortname = "hat.boonie", Skin = 3077061771
}
, new() {
Shortname = "burlap.gloves", Skin = 1338273501
}
}
, BountyTiers = new List<BountyTiers> {
new() {
TierName = "Tier 1 Bounty Target", Value = 100, BountyKitEnabled = false, BountyKitName = "", BountyClothingOptions = new List<BountyClothingOption> {
new() {
Shortname = "hazmatsuittwitch", Skin = 0
}
}
, BountyHealth = 150, BountyDamageOutput = 1f, AimConeScale = 2f, WeaponName = "rifle.ak", WeaponSkin = 3140321604, TokenName = "Tier 1 Bounty Token", TokenSkinID = 3155517347, Sellable = true, SalePrice = 1000, MaxDiscount = -10, MaxBonus = 5, MinItemDrop = 1, MaxItemDrop = 3, BountyDropsList = new List<BountyDrops> {
new() {
Shortname = "stones", Skin = 0, CustomName = "", AmountMin = 3, AmountMax = 10, ChanceToDrop = 20.0f
}
, new() {
Shortname = "scrap", Skin = 0, CustomName = "", AmountMin = 5, AmountMax = 30, ChanceToDrop = 30.0f
}
, new() {
Shortname = "paper", Skin = 3048132587, CustomName = "Cash", AmountMin = 1, AmountMax = 3, ChanceToDrop = 40.0f
}
}
}
, new() {
TierName = "Tier 2 Bounty Target", Value = 200, BountyKitEnabled = false, BountyKitName = "", BountyClothingOptions = new List<BountyClothingOption> {
new() {
Shortname = "hazmatsuit.arcticsuit", Skin = 0
}
}
, BountyHealth = 225, BountyDamageOutput = 1.5f, AimConeScale = 2.5f, WeaponName = "rifle.lr300", WeaponSkin = 2715918380, TokenName = "Tier 2 Bounty Token", TokenSkinID = 3155517539, Sellable = true, SalePrice = 2000, MaxDiscount = -5, MaxBonus = 15, MinItemDrop = 1, MaxItemDrop = 3, BountyDropsList = new List<BountyDrops> {
new() {
Shortname = "metal.fragments", Skin = 0, CustomName = "", AmountMin = 200, AmountMax = 500, ChanceToDrop = 30.0f
}
, new() {
Shortname = "scrap", Skin = 0, CustomName = "", AmountMin = 240, AmountMax = 300, ChanceToDrop = 30.0f
}
, new() {
Shortname = "paper", Skin = 3048132587, CustomName = "Cash", AmountMin = 10, AmountMax = 15, ChanceToDrop = 40.0f
}
}
}
, new() {
TierName = "Tier 3 Bounty Target", Value = 300, BountyKitEnabled = false, BountyKitName = "", BountyClothingOptions = new List<BountyClothingOption> {
new() {
Shortname = "scientistsuit_heavy", Skin = 0
}
}
, BountyHealth = 325, BountyDamageOutput = 2f, AimConeScale = 3f, WeaponName = "rifle.ak.ice", WeaponSkin = 0, TokenName = "Tier 3 Bounty Token", TokenSkinID = 3155517732, Sellable = true, SalePrice = 3200, MaxDiscount = -3, MaxBonus = 20, MinItemDrop = 1, MaxItemDrop = 3, BountyDropsList = new List<BountyDrops> {
new() {
Shortname = "metal.refined", Skin = 0, CustomName = "", AmountMin = 200, AmountMax = 500, ChanceToDrop = 30.0f
}
, new() {
Shortname = "scrap", Skin = 0, CustomName = "", AmountMin = 440, AmountMax = 600, ChanceToDrop = 30.0f
}
, new() {
Shortname = "paper", Skin = 3048132587, CustomName = "Cash", AmountMin = 20, AmountMax = 35, ChanceToDrop = 40.0f
}
}
}
}
}
;
}
}
protected override void LoadConfig() {
base.LoadConfig();
try {
_config = Config.ReadObject<Configuration>();
if (_config == null) LoadDefaultConfig();
if (_config.DynamicSpawnPoints == null || _config.DynamicSpawnPoints.Count == 0) {
Puts("Default Placeholder Spawn Points Missing, Adding.");
_config.DynamicSpawnPoints = new List<Vector3>() {
new(64.86f, 2.00f, -46.20f), new(-0.58f, 0.83f, -68.93f)
}
;
SaveConfig();
}
SaveConfig();
}
catch (Exception e) {
Debug.LogException(e);
PrintWarning("Creating new config file.");
LoadDefaultConfig();
}
}
protected override void LoadDefaultConfig() {
_config = Configuration.DefaultConfig();
}
protected override void SaveConfig() {
Config.WriteObject(_config);
}
#endregion
#region npcdatastuff
public class ClothingOption {
[JsonProperty("Shortname")]
public string Shortname {
get;
set;
}
[JsonProperty("Skin")]
public ulong Skin {
get;
set;
}
}
public class BountyClothingOption {
[JsonProperty("Shortname")]
public string Shortname {
get;
set;
}
[JsonProperty("Skin")]
public ulong Skin {
get;
set;
}
}
public class BountyDrops {
[JsonProperty("Shortname")]
public string Shortname {
get;
set;
}
[JsonProperty("Skin")]
public ulong Skin {
get;
set;
}
[JsonProperty("Custom Name")]
public string CustomName {
get;
set;
}
[JsonProperty("Amount Min")]
public int AmountMin {
get;
set;
}
[JsonProperty("Amount Max")]
public int AmountMax {
get;
set;
}
[JsonProperty("Chance to Drop")]
public float ChanceToDrop {
get;
set;
}
}
private class NpcData {
public string NpcName {
get;
set;
}
public string ShortPrefabName {
get;
set;
}
public PositionData Position {
get;
set;
}
public RotationData Rotation {
get;
set;
}
}
public class PositionData {
public float X, Y, Z;
public Vector3 ToVector3() {
return new Vector3(X, Y, Z);
}
}
public class RotationData {
public float X, Y, Z;
public RotationData(Vector3 rotation) {
X = rotation.x;
Y = rotation.y;
Z = rotation.z;
}
public Vector3 ToVector3() {
return new Vector3(X, Y, Z);
}
}
#endregion
#region data
private void SaveData() {
try {
Interface.Oxide.DataFileSystem.WriteObject("Bounty/Licenses", _playerLicenses);
Interface.Oxide.DataFileSystem.WriteObject("Bounty/CustomSpawns", _customSpawnPoints);
Interface.Oxide.DataFileSystem.WriteObject("Bounty/NPCData", new List<NpcData>());
if (!_config.DynamicSpawn) Interface.Oxide.DataFileSystem.WriteObject("Bounty/NPCData", _npcDataList);
}
catch (Exception ex) {
PrintError($"Error saving data: {ex.Message}");
}
}
private void LoadData() {
try {
var loadedNpcData = Interface.Oxide.DataFileSystem.ReadObject<List<NpcData>>("Bounty/NPCData");
if (loadedNpcData != null) _npcDataList.AddRange(loadedNpcData);
_playerLicenses = Interface.Oxide.DataFileSystem.ReadObject<List<BountyLicense>>("Bounty/Licenses") ?? new List<BountyLicense>();
var customSpawns = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, List<Vector3>>>("Bounty/CustomSpawns") ?? new Dictionary<string, List<Vector3>>();
foreach (var pair in customSpawns) _customSpawnPoints[pair.Key] = pair.Value;
}
catch (Exception ex) {
PrintError($"Error loading data: {ex.Message}");
}
}
private void LoadTopKills() {
var savedData = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, int>>("Bounty/playerData");
_playerData = savedData ?? new Dictionary<ulong, int>();
foreach (var playerId in _playerData.Keys.ToList()) {
_playerData.TryAdd(playerId, 0);
}
}
private void ClearKills() {
LoadTopKills();
_playerData.Clear();
SaveTopKills();
Puts("Wipe Detected Clearing Top Kills");
}
private void SaveTopKills() {
Interface.Oxide.DataFileSystem.WriteObject("Bounty/playerData", _playerData);
}
#endregion
#region markerRemover
private static bool InRange(Vector3 a, Vector3 b, float distance) {
return (a - b).sqrMagnitude <= distance * distance;
}
private void OnEntitySpawned(MapMarkerMissionProvider marker) {
timer.Once(1f, () => {
if (marker == null || marker.IsDestroyed) return;
if (_bountyNpCs == null || _bountyNpCs.Count == 0) return;
foreach (var npc in _bountyNpCs) {
if (npc == null || npc.transform == null || marker.transform == null) continue;
if (InRange(npc.transform.position, marker.transform.position, 0.1f)) {
NextTick(() => marker?.Kill());
}
}
}
);
}
#endregion
#region hooks
private object OnEntityTakeDamage(BaseEntity entity, HitInfo info) {
if (entity == null || info?.Initiator == null) {
return null;
}
if (_bountyNpCs.Contains(entity)) {
// Blocks NPCs from being damaged
return false;
}
return null;
}
private object OnNpcKits(NPCTalking npc) {
return _bountyNpCs.Contains(npc) ? true : null;
}
private void OnPlayerSleepEnded(BasePlayer player) {
if (!permission.UserHasPermission(player.UserIDString, LifetimeLicense)) return;
if (!HasBountyLicense(player.userID)) GiveLicense(player.userID);
}
private void OnServerInitialized(bool initial) {
RegisterCommands();
RegisterPerms();
if (_config.EnableMonumentSpawns) LoadMonumentSpawnPoints();
LoadCustomSpawnPoints();
LoadDynamicSpawnPoints();
LoadData();
CheckDependencies(initial);
CheckST();
StartSpawnTimer();
LoadTopKills();
CheckAnnounce();
CheckInitialSpawns();
}
private void CheckInitialSpawns() {
if (!_config.SpawnOnReload) return;
int amount = _config.AmountToSpawnOnReload;
int maxAmount = _config.MaxBounties;
int slots = maxAmount - _botCount;
if (slots <= 0) return;
int spawnAmount = Math.Min(amount, slots);
Puts("Beginning Initial Spawns.");
for (int i = 0; i < spawnAmount; i++) {
int i1 = i;
timer.Once(i + 2, () => {
SpawnBot();
Puts($"Spawning Bounty {i1 + 1}/{spawnAmount}.");
}
);
}
}
private void RegisterCommands() {
var consoleCommand = _config.ConsoleCommand;
var chatCommand = _config.ChatCommand;
cmd.AddConsoleCommand(consoleCommand, this, nameof(BuyBLCcmd));
cmd.AddChatCommand(chatCommand, this, nameof(BuyBountyLicCmd));
}
private void RegisterPerms() {
permission.RegisterPermission(AdminPermission, this);
permission.RegisterPermission(LifetimeLicense, this);
}
private void CheckDependencies(bool initial) {
if (initial) {
timer.Once(6f, SpawnNpCsFromData);
timer.Once(7f, CheckPlugin);
}
else {
SpawnNpCsFromData();
CheckPlugin();
}
}
private void CheckAnnounce() {
if (_config.AnnounceInterval > 0) {
timer.Repeat(_config.AnnounceInterval, 0, () => AnnounceTop());
}
}
private void CheckST() {
if (_config.EnabledSkillTreeXp) {
Subscribe("STCanGainXP");
}
}
private void OnNewSave(string filename) {
LoadData();
ClearNpcData();
Puts("Wipe Detected, Removing Bounty Hunter NPCs.");
if (_config.ClearLicenses) ClearPlayerLicenses();
if (_config.ClearCustomSpawns) ClearCustomSpawnPoints();
if (_config.ClearKills) ClearKills();
}
private void ClearNpcData() {
_npcDataList.Clear();
SaveData();
}
private void ClearPlayerLicenses() {
_playerLicenses.Clear();
Interface.Oxide.DataFileSystem.WriteObject("Bounty/Licenses", _playerLicenses);
SaveData();
Puts("Clearing Bounty Hunter Licenses.");
}
private void ClearCustomSpawnPoints() {
_customSpawnPoints.Clear();
Interface.Oxide.DataFileSystem.WriteObject("Bounty/CustomSpawns", _customSpawnPoints);
SaveData();
Puts("Clearing Bounty Hunter Custom Spawns.");
}
private void OnPlayerDeath(BasePlayer player, HitInfo info, string tierName, ScientistNPC bounty) {
try {
if (info.Initiator.IsNpc) return;
var attacker = info.Initiator as BasePlayer;
if (attacker == null || !player.name.StartsWith("Bounty_")) return;
RemoveMarker(player.name);
var pos = player.transform.position + new Vector3(0f, 2f, 0f);
var container = GameManager.server.CreateEntity(Drop, pos) as DroppedItemContainer;
if (container == null) return;
if (container.inventory != null) return;
if (container.inventory == null) {
container.inventory = new ItemContainer();
container.inventory.ServerInitialize(null, 36);
container.inventory.SetFlag(ItemContainer.Flag.NoItemInput, true);
}
var playerID = player.UserIDString;
if (_botTierMap.TryGetValue(playerID, out var selectedBountyTier)) {
var bountyDropsList = selectedBountyTier.BountyDropsList;
ShuffleList(bountyDropsList);
var lootcount = Random.Range(selectedBountyTier.MinItemDrop, selectedBountyTier.MaxItemDrop);
//Puts("lootcount" + lootcount);
for (var i = 0; i < lootcount;) {
var entry = bountyDropsList[Random.Range(0, bountyDropsList.Count)];
var chance = Random.Range(0f, 100f);
if (chance > entry.ChanceToDrop) {
continue;
}
var amount = Random.Range(entry.AmountMin, entry.AmountMax);
var item = ItemManager.CreateByName(entry.Shortname, amount, entry.Skin);
if (!string.IsNullOrEmpty(entry.CustomName)) {
item.name = entry.CustomName;
}
container.inventory.GiveItem(item);
i++;
// Puts("giving item" + item.name);
}
}
if (selectedBountyTier != null && _config.TokensEnabled) {
var bountyClaim = ItemManager.CreateByName(_config.PlaceholderItem, 1, selectedBountyTier.TokenSkinID);
bountyClaim.name = selectedBountyTier.TokenName;
container.inventory.GiveItem(bountyClaim);
}
container.inventory.MarkDirty();
container.Spawn();
container.name = $"{attacker.displayName} Bounty Drop";
container.SendNetworkUpdateImmediate();
_botCount--;
if (_botCount == 0) Interface.CallHook("OnBountyInactive");
_spawnedBounties.Remove(bounty);
foreach (var pair in _despawnTimers.ToList().Where(pair => pair.Key != null && pair.Key == player.UserIDString)) {
pair.Value.Destroy();
_despawnTimers.Remove(pair.Key);
break;
}
_playerData.TryAdd(attacker.userID, 0);
_playerData[attacker.userID]++;
SaveTopKills();
//Adding Kills to the tally
var elimMessage = lang.GetMessage("BountyEliminated", this);
if (selectedBountyTier == null) return;
var bountyName = selectedBountyTier.TierName;
var attackerName = attacker.displayName;
timer.Repeat(0.3f, 5, () => Effect.server.Run(RicochetFx, pos));
var message = lang.GetMessage("BountyDrop", this);
if (_config.TokensEnabled) attacker.SendMessage(message, bountyName);
if (_config.AnnounceBountyClaimed) Server.Broadcast(string.Format(elimMessage, bountyName, attackerName), null, _config.ChatIcon);
if (!SkillTree || !_config.EnabledSkillTreeXp) return;
if (_config.XpLicReq && !HasBountyLicense(attacker.userID) && !permission.UserHasPermission(attacker.UserIDString, LifetimeLicense)) return;
AwardXp(attacker, selectedBountyTier);
}
catch (Exception ex) {
//Debug.LogError;
/*"Zmt5AGR="
"1754679268"
"Wrecks"*/
}
}
private void AwardXp(BasePlayer player, BountyTiers tier) {
if (tier.Value == 0) return;
timer.Once(2f, () => {
SkillTree?.Call("AwardXP", player, tier.Value);
player.ChatMessage("[<color=#b5a642>Bounty Hunter</color>] You gained <color=#b5a642>" + tier.Value + "</color> <color=#42b5a6>Xp</color> for the <color=#b5a642>Bounty</color> Takedown.");
CommandSuccess(player);
}
);
}
private object CanLootEntity(BasePlayer player, DroppedItemContainer container) {
if (!container.name.Contains("Bounty Drop")) return null;
var attackerName = container.name.Replace(" Bounty Drop", "");
if (player.displayName == attackerName) return null;
return false;
}
private object OnNpcConversationStart(NPCTalking npcTalking, BasePlayer player, ConversationData conversationData) {
if (!_bountyNpCs.Contains(npcTalking)) return null;
if (!_config.TokensMarketable) {
var salesdisabled = lang.GetMessage("SaleDisabled", this, player.UserIDString);
Player.Message(player, string.Format(salesdisabled), null, _config.ChatIcon);
DeniedFX(player);
return false;
//returning false to prevent any dialogue built into the prefab npc
}
if (_config.LicenseRequired && !HasBountyLicense(player.userID)) {
var noLicenseMessage = lang.GetMessage("NoBountyHunterLicense", this, player.UserIDString);
Player.Message(player, string.Format(noLicenseMessage), null, _config.ChatIcon);
CommandSuccess(player);
return false;
}
if (!_bountyNpCs.Contains(npcTalking)) return null;
if (_interactionCooldowns.ContainsKey(player.userID) && Time.realtimeSinceStartup < _interactionCooldowns[player.userID]) {
var remainingCooldown = _interactionCooldowns[player.userID] - Time.realtimeSinceStartup;
var cooldownMessage = lang.GetMessage("BountyHunterCooldownMessage", this, player.UserIDString);
Player.Message(player, string.Format(cooldownMessage, (int)Math.Ceiling(remainingCooldown)), null, _config.ChatIcon);
DeniedFX(player);
return true;
}
_interactionCooldowns[player.userID] = Time.realtimeSinceStartup + _config.InteractionCooldown;
HandleBountyTrade(player);
return true;
}
private void Unload() {
ClearTimers();
ClearBounties();
ClearBountyNPCs();
SaveData();
SaveTopKills();
Unsubscribe("STCanGainXP");
}
private void ClearTimers() {
spawnTimer?.Destroy();
}
#endregion
#region tradehandler
private void HandleBountyTrade(BasePlayer player) {
foreach (var tokens in _config.BountyTiers) if (tokens.Sellable) if (PlayerHasTokens(player, tokens)) {
var quantity = GetTotalTokenQuantity(player, tokens);
if (quantity > _config.MaxTradeQuantity) quantity = _config.MaxTradeQuantity;
RemoveTokensFromPlayer(player, tokens, quantity);
var salePriceInt = (int)tokens.SalePrice;
RewardHandler(player, tokens, quantity, salePriceInt);
return;
}
var noTokens = lang.GetMessage("NoTokens", this, player.UserIDString);
Player.Message(player, string.Format(noTokens), null, _config.ChatIcon);
EffectNetwork.Send(new Effect(NoTokensFx, player.transform.position, player.transform.position), player.net.connection);
}
private void RemoveTokensFromPlayer(BasePlayer player, BountyTiers token, int quantity) {
foreach (var item in player.inventory.containerMain.itemList.ToList()) if (IsToken(item, token)) {
var itemsToRemove = Mathf.Min(item.amount, quantity);
if (item.amount > itemsToRemove) {
item.amount -= itemsToRemove;
item.MarkDirty();
player.SendNetworkUpdateImmediate();
}
else {
item.RemoveFromContainer();
item.Remove();
}
quantity -= itemsToRemove;
if (quantity <= 0) return;
}
foreach (var item in player.inventory.containerBelt.itemList.ToList()) if (IsToken(item, token)) {
var itemsToRemove = Mathf.Min(item.amount, quantity);
if (item.amount > itemsToRemove) {
item.amount -= itemsToRemove;
item.MarkDirty();
player.SendNetworkUpdateImmediate();
}
else {
item.RemoveFromContainer();
item.Remove();
}
quantity -= itemsToRemove;
if (quantity <= 0) return;
}
}
private int GetTotalTokenQuantity(BasePlayer player, BountyTiers token) {
return player.inventory.containerMain.itemList.Concat(player.inventory.containerBelt.itemList).Where(item => IsToken(item, token)).Sum(item => item.amount);
}
private bool PlayerHasTokens(BasePlayer player, BountyTiers token) {
return player.inventory.containerMain.itemList.Concat(player.inventory.containerBelt.itemList).Any(item => IsToken(item, token));
}
private bool IsToken(Item item, BountyTiers token) {
return item.skin == token.TokenSkinID;
}
#endregion
#region helpers
void ShuffleList<T>(List<T> list) {
for (var i = list.Count - 1; i > 0; i--) {
var randomIndex = Random.Range(0, i + 1);
(list[i], list[randomIndex]) = (list[randomIndex], list[i]);
}
}
private void SpawnNpCsFromData() {
if (_config.DynamicSpawn) {
SpawnNpcsDynamically();
}
else {
var npcDataCopy = Pool.Get<List<NpcData>>()
// ... [Code preview truncated due to length. Full source in attached file] ...