C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
/*
* Copyright (c) 2023 Bazz3l
*
* Bank Heist cannot be copied, edited and/or (re)distributed without the express permission of Bazz3l.* Discord bazz3l
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.*
*/
// Reference: Unity.AI.Navigation
using System.Collections.Generic;
using System.Collections;
using System.Diagnostics;
using System.Text;
using System.Linq;
using System;
using Oxide.Plugins.BankHeistExtensionMethods;
using Oxide.Core.Plugins;
using Oxide.Core;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using Network;
using ProtoBuf;
using Rust;
using Unity.AI.Navigation;
using UnityEngine;
using UnityEngine.AI;
namespace Oxide.Plugins {
[Info("Bank Heist", "Bazz3l", "1.5.21")]
[Description("")]
internal class BankHeist : RustPlugin {
[PluginReference]
Plugin NpcSpawn, PersonalVaultDoor;
#region Fields
private const int RAYCAST_LAYERS = Layers.Solid | Layers.Deploy;
private const string WORKER_PERM = "bankheist.worker";
private const string MANAGE_PERM = "bankheist.manage";
private readonly HashSet<BaseEntity> _tempSpawned = new();
private readonly HashSet<ulong> _players = new();
private readonly object _falseObj = false;
private readonly object _trueObj = true;
private ConfigData _configData;
private Coroutine _coroutine;
private static BankHeist _instance;
#endregion
#region Local
protected override void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string, string> {
{
LangEntry.NoPermission, "You don't have permission to use this command."
}
, {
LangEntry.PrefabsToSpawn, "Prefab list:\n{0}"
}
, {
LangEntry.InvalidArgs, "Invalid arguments:"
}
, {
LangEntry.InvalidArgument, "Invalid argument: {0} must be a {1}"
}
, {
LangEntry.InvalidCmd, "Invalid command: /{0} {1} {2}"
}
, {
LangEntry.InvalidLookPoint, "Invalid look point."
}
, {
LangEntry.EntityNotFound, "No entity found at the look position."
}
, {
LangEntry.MonumentNotFound, "No monument found by that name."
}
, {
LangEntry.SpawnEntryNotFound, "No spawn entry found."
}
, {
LangEntry.BankAlreadyExists, "A bank already exists by that name."
}
, {
LangEntry.BankNotFound, "No bank found by that name."
}
, {
LangEntry.BankCreated, "Bank created successfully, if you wish to parent the event to a specific monument do so now and reload the plugin."
}
, {
LangEntry.BankRemoved, "Bank removed successfully!"
}
, {
LangEntry.BankUpdated, "Bank updated successfully!"
}
, {
LangEntry.BankParent, "Bank parent updated successfully, please reload the plugin."
}
, {
LangEntry.BankNotSpawned, "No bank spawned by that name."
}
, {
LangEntry.BankAttacked, "<color=#ca094c>WARNING!</color>!\n<color=#2ec8d0>{0}</color> is being attacked please send help!"
}
, {
LangEntry.ProfileAlreadyExists, "A profile already exists by that name."
}
, {
LangEntry.ProfileNotFound, "No profile found by that name."
}
, {
LangEntry.ProfileCreated, "Profile created successfully!"
}
, {
LangEntry.ProfileRemoved, "Profile removed successfully!"
}
, {
LangEntry.ProfileUpdated, "Profile updated successfully!"
}
, {
LangEntry.DebugInvalidSyntax, "<color=#2ec8d0>Debug</color>: invalid syntax, /bhm debug <bank-name> <door|loot|guard|info|everything>."
}
, {
LangEntry.DebugToggled, "<color=#2ec8d0>Debug</color>: has now been enabled for (<color=#2ec8d0>30</color>) seconds."
}
, {
LangEntry.DebugInvalidOption, "<color=#2ec8d0>Debug</color>: invalid option specified."
}
, {
LangEntry.EliminateGuards, "You must first eliminate all guards."
}
, {
LangEntry.OnUnauthorizedAccess, "You don't have access to this door, blowup the door to gain access."
}
, {
LangEntry.OnCratesLooted, "<color=#2ec8d0>{0}</color> has been robbed of all loot."
}
, {
LangEntry.OnGuardDeath, "<color=#2ec8d0>{0}</color>: security guard down <color=#2ec8d0>{1}</color>/<color=#2ec8d0>{2}</color> remaining!"
}
, {
LangEntry.OnDoorDeath, "<color=#2ec8d0>{0}</color>: security door breached!"
}
, {
LangEntry.OnEnterZone, "You have entered <color=#2ec8d0>{0}</color>"
}
, {
LangEntry.OnLeaveZone, "You have left <color=#2ec8d0>{0}</color>"
}
, {
LangEntry.OnCommandBlocked, "You cannot use this command from this location!"
}
, {
LangEntry.OnTeleportBlocked, "You cannot teleport from this location!"
}
, {
LangEntry.OnWorkerPreventLoot, "You cannot loot this container."
}
, {
LangEntry.OnBankReset, "<color=#2ec8d0>{0}</color> is now available."
}
,
}
, this);
}
private static class LangEntry {
public static readonly string NoPermission = "NoPermission";
public static readonly string PrefabsToSpawn = "PrefabsToSpawn";
public static readonly string InvalidArgs = "InvalidArgs";
public static readonly string InvalidArgument = "InvalidArgument";
public static readonly string InvalidCmd = "InvalidCmd";
public static readonly string InvalidLookPoint = "InvalidLookPoint";
public static readonly string MonumentNotFound = "MonumentNotFound";
public static readonly string EntityNotFound = "EntityNotFound";
public static readonly string SpawnEntryNotFound = "SpawnEntryNotFound";
public static readonly string BankAlreadyExists = "BankAlreadyExists";
public static readonly string BankNotFound = "BankNotFound";
public static readonly string BankCreated = "BankCreated";
public static readonly string BankRemoved = "BankRemoved";
public static readonly string BankUpdated = "BankUpdated";
public static readonly string BankParent = "BankParent";
public static readonly string BankAttacked = "BankAttacked";
public static readonly string BankNotSpawned = "BankNotSpawned";
public static readonly string ProfileAlreadyExists = "ProfileAlreadyExists";
public static readonly string ProfileNotFound = "ProfileNotFound";
public static readonly string ProfileCreated = "ProfileCreated";
public static readonly string ProfileRemoved = "ProfileRemoved";
public static readonly string ProfileUpdated = "ProfileUpdated";
public static readonly string DebugInvalidSyntax = "DebugInvalidSyntax";
public static readonly string DebugInvalidOption = "DebugInvalidOption";
public static readonly string DebugToggled = "DebugToggled";
public static readonly string EliminateGuards = "EliminateGuards";
public static readonly string OnUnauthorizedAccess = "OnUnauthorizedAccess";
public static readonly string OnCratesLooted = "OnCratesLooted";
public static readonly string OnGuardDeath = "OnGuardDeath";
public static readonly string OnDoorDeath = "OnDoorDeath";
public static readonly string OnEnterZone = "OnEnterZone";
public static readonly string OnLeaveZone = "OnLeaveZone";
public static readonly string OnCommandBlocked = "OnCommandBlocked";
public static readonly string OnTeleportBlocked = "OnTeleportBlocked";
public static readonly string OnWorkerPreventLoot = "OnWorkerPreventLoot";
public static readonly string OnBankReset = "OnBankBankReset";
}
private string GetMessage(string langEntry, string userID = null, params object[] args) {
return args?.Length > 0 ? string.Format(lang.GetMessage(langEntry, this, userID), args) : lang.GetMessage(langEntry, this, userID);
}
private void MessagePlayer(BasePlayer player, string langEntry, params object[] args) {
if (player == null || !player.IsConnected) return;
player.ChatMessage(GetMessage(langEntry, player.UserIDString, args));
}
private void MessagePlayers(string langEntry, params object[] args) {
ConsoleNetwork.BroadcastToAllClients("chat.add", 2, 0, GetMessage(langEntry, null, args));
}
#endregion
#region Config
protected override void LoadDefaultConfig() => _configData = ConfigData.DefaultConfig();
protected override void LoadConfig() {
base.LoadConfig();
try {
_configData = Config.ReadObject<ConfigData>();
if (_configData == null) throw new Exception();
if (_configData.SpawnablePrefabs == null) {
_configData.SpawnablePrefabs = ConfigData.DefaultConfig().SpawnablePrefabs;
SaveConfig();
}
}
catch (Exception e) {
PrintWarning("Loaded default config: {0}", e.Message);
LoadDefaultConfig();
}
}
protected override void SaveConfig() => Config.WriteObject(_configData, true);
private class ConfigData {
[JsonProperty("enable notifications")]
public bool EnableNotifications;
[JsonProperty("enable visible bubble")]
public bool EnableVisibleBubble;
[JsonProperty("enable visible marker")]
public bool EnableVisibleMarker;
[JsonProperty("enable alarm trigger")]
public bool EnableAlarmTrigger;
[JsonProperty("enable eliminate guards")]
public bool MustEliminateGuards;
[JsonProperty("enable zone messages")]
public bool EnableZoneMessages;
[JsonProperty("prevent worker looting bank")]
public bool PreventWorkerLootingBank;
[JsonProperty("prevent teleport from bank")]
public bool PreventTeleportFromBank;
[JsonProperty("blocked zone commands")]
public List<string> BlockedCommands;
[JsonProperty("spawnable prefabs")]
public Dictionary<string, string> SpawnablePrefabs;
public static ConfigData DefaultConfig() {
return new ConfigData {
EnableNotifications = true, EnableVisibleBubble = true, EnableVisibleMarker = true, EnableAlarmTrigger = true, EnableZoneMessages = true, MustEliminateGuards = true, PreventTeleportFromBank = false, BlockedCommands = new List<string>(), SpawnablePrefabs = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {
{
"garage-door", "assets/prefabs/building/wall.frame.garagedoor/wall.frame.garagedoor.prefab"
}
, {
"vault-door", "assets/bundled/prefabs/modding/asset_store/bankheist_package/bankheist_vol03/prefabs/door.vault.static.prefab"
}
, {
"toptier-door", "assets/prefabs/building/door.hinged/door.hinged.toptier.prefab"
}
, {
"toptier-d-door", "assets/prefabs/building/door.double.hinged/door.double.hinged.toptier.prefab"
}
, {
"hackable-crate", "assets/prefabs/deployable/chinooklockedcrate/codelockedhackablecrate.prefab"
}
, {
"elite-crate", "assets/bundled/prefabs/radtown/crate_elite.prefab"
}
, {
"crate-normal", "assets/bundled/prefabs/radtown/crate_normal.prefab"
}
, {
"crate-normal_2", "assets/bundled/prefabs/radtown/crate_normal_2.prefab"
}
}
}
;
}
}
#endregion
#region Bank Storage
private class BaseStorage<T> where T : BaseStorage<T>, new() {
public static T Data;
public static string Filename;
public static void LoadData(string filename) {
Filename = filename;
try {
Data = Interface.Oxide.DataFileSystem.ReadObject<T>(Filename);
if (Data == null) throw new Exception();
}
catch {
Interface.Oxide.LogDebug("Failed to load {0}; loaded default data.", Filename);
Data = new T();
}
}
public static void SaveData() {
if (Data == null) return;
Interface.Oxide.DataFileSystem.WriteObject(Filename, Data);
}
public static void OnUnload() {
Data = null;
Filename = null;
}
}
private class BankStorage : BaseStorage<BankStorage> {
public List<BankEntry> BankEntries = new();
public bool GetBankByName(string displayName, out BankEntry foundEntry) {
for (int i = 0; i < BankEntries.Count; i++) {
BankEntry bankEntry = BankEntries[i];
if (bankEntry != null && bankEntry.DisplayName.Equals(displayName, StringComparison.OrdinalIgnoreCase)) {
foundEntry = bankEntry;
return true;
}
}
foundEntry = null;
return false;
}
}
private class BankEntry {
[JsonProperty("bank display name")]
public string DisplayName;
[JsonProperty("parent monument name")]
public string MonumentParentName;
[JsonProperty("time between resets")]
public float TimeBetweenResets;
[JsonProperty("alarm disable when all crates are looted")]
public bool AlarmClear;
[JsonProperty("alarm timeout seconds")]
public float AlarmTimeoutSeconds;
[JsonProperty("spawn position")]
public Vector3 SpawnPosition;
[JsonProperty("spawn rotation")]
public Vector3 SpawnRotation;
[JsonProperty("player detection origin")]
public Vector3 PlayerDetectionOrigin;
[JsonProperty("player detection radius")]
public float PlayerDetectionRadius;
[JsonProperty("map marker origin")]
public Vector3 MapMarkerOrigin;
[JsonProperty("map marker radius")]
public float MapMarkerRadius;
[JsonProperty("map marker opacity")]
public float MapMarkerOpacity;
[JsonProperty("map marker ready color")]
public string MapMarkerReadyColor;
[JsonProperty("map marker reset color")]
public string MapMarkerResetColor;
[JsonProperty("map marker outline color")]
public string MapMarkerOuterColor;
[JsonProperty("loot spawn group entries")]
public List<SpawnEntry> LootSpawnGroupEntries;
[JsonProperty("door spawn group entries")]
public List<SpawnEntry> DoorSpawnGroupEntries;
[JsonProperty("npcs spawn group entries")]
public List<SpawnEntry> NpcSpawnGroupEntries;
public BankEntry() {
}
public BankEntry(string displayName, Transform transform) {
DisplayName = displayName;
TimeBetweenResets = 1800f;
SpawnPosition = transform.position;
SpawnRotation = new Vector3(0.0f, transform.rotation.eulerAngles.y, 0.0f);
PlayerDetectionOrigin = Vector3.zero;
PlayerDetectionRadius = 100f;
MapMarkerOrigin = Vector3.zero;
MapMarkerRadius = 100f;
MapMarkerOpacity = 0.6f;
MapMarkerReadyColor = "#2eff74";
MapMarkerResetColor = "#ff2e2e";
MapMarkerOuterColor = "#ffffff";
LootSpawnGroupEntries = new List<SpawnEntry>();
DoorSpawnGroupEntries = new List<SpawnEntry>();
NpcSpawnGroupEntries = new List<SpawnEntry>();
}
public IEnumerator SpawnBankInstance() {
Vector3 spawnPosition = SpawnPosition;
Vector3 spawnRotation = SpawnRotation;
if (!string.IsNullOrEmpty(MonumentParentName)) {
Transform transform = MonumentCache.FindMonumentByName(MonumentParentName)?.GetTransform();
;
if (transform == null) {
Interface.Oxide.LogDebug("Failed to perform setup: missing parent transform for ({0}) assigned to ({1}).", MonumentParentName, DisplayName);
yield break;
}
spawnPosition = transform.position;
spawnRotation = transform.rotation.eulerAngles;
}
if (LootSpawnGroupEntries == null || DoorSpawnGroupEntries == null || NpcSpawnGroupEntries == null) {
Interface.Oxide.LogDebug("Failed to perform setup: missing spawn group found in data file for ({0}).", DisplayName);
yield break;
}
// Create bank component
BankHeistInstance component = CustomUtils.CreateObjectWithComponent<BankHeistInstance>(spawnPosition, Quaternion.Euler(spawnRotation), "BANK_CONTROLLER_NAME");
// Cache instance
BankHeistInstance.CacheHeistInstance(component);
// General settings
component.requiresReset = true;
component.displayName = DisplayName;
component.monumentName = MonumentParentName;
component.timeBetweenResets = TimeBetweenResets;
component.alarmTime = AlarmTimeoutSeconds;
component.clearAlarm = AlarmClear;
// Player detection origin and radius
component.playerDetectionOrigin = component.transform.TransformPoint(PlayerDetectionOrigin);
component.playerDetectionRadius = PlayerDetectionRadius / 2;
// Player detection sphere collider
SphereCollider sphereCollider = component.gameObject.AddComponent<SphereCollider>();
sphereCollider.isTrigger = true;
sphereCollider.radius = PlayerDetectionRadius / 2;
sphereCollider.center = PlayerDetectionOrigin;
// Map marker origin and radius
component.markerOrigin = component.transform.TransformPoint(MapMarkerOrigin);
component.markerReadyColor = ColorUtility.TryParseHtmlString(MapMarkerReadyColor, out Color readyColor) ? readyColor : Color.green;
component.markerResetColor = ColorUtility.TryParseHtmlString(MapMarkerResetColor, out Color resetColor) ? resetColor : Color.red;
component.markerOuterColor = ColorUtility.TryParseHtmlString(MapMarkerOuterColor, out Color outerColor) ? outerColor : Color.white;
component.markerRadius = MapMarkerRadius / 2;
component.markerOpacity = MapMarkerOpacity;
// Loot spawn group
component.lootSpawnGroup = component.gameObject.AddComponent<LootSpawnGroup>();
component.lootSpawnGroup.spawnPointEntries.AddRange(LootSpawnGroupEntries ?? new List<SpawnEntry>());
component.lootSpawnGroup.bankHeistInstance = component;
// Door spawn group
component.doorSpawnGroup = component.gameObject.AddComponent<DoorSpawnGroup>();
component.doorSpawnGroup.spawnPointEntries.AddRange(DoorSpawnGroupEntries ?? new List<SpawnEntry>());
component.doorSpawnGroup.bankHeistInstance = component;
// Guard spawn group
component.guardSpawnGroup = component.gameObject.AddComponent<GuardSpawnGroup>();
component.guardSpawnGroup.spawnPointEntries.AddRange(NpcSpawnGroupEntries ?? new List<SpawnEntry>());
component.guardSpawnGroup.bankHeistInstance = component;
// Initialize event
yield return component.Initialize();
}
}
private class SpawnEntry {
public int Id;
public string Prefab;
public string Profile;
public Vector3 Position;
public Vector3 Rotation;
public SpawnEntry() {
}
public SpawnEntry(int id, string prefab, string profile, Vector3 position, Vector3 rotation, ulong skinID = 0UL) {
Id = id;
Prefab = prefab;
Profile = profile;
Position = position;
Rotation = rotation;
}
public T CreateEntity<T>(Transform transform) where T : BaseEntity {
Vector3 position = transform.TransformPoint(Position);
Vector3 rotation = Rotation;
rotation.y = transform.rotation.eulerAngles.y + rotation.y;
return (T)GameManager.server.CreateEntity(Prefab, position, Quaternion.Euler(rotation), false);
}
public T CreateGuard<T>(Transform transform, GuardProfileEntry profileEntry) where T : BaseEntity {
Vector3 position = transform.TransformPoint(Position);
if (profileEntry.Parsed == null) profileEntry.CacheConfig();
return (T)_instance?.NpcSpawn?.Call("SpawnNpc", position, profileEntry.Parsed);
}
}
#endregion
#region Guard Profile Storage
private class GuardProfileStorage : BaseStorage<GuardProfileStorage> {
public Dictionary<string, GuardProfileEntry> ProfileEntries = new(StringComparer.OrdinalIgnoreCase);
public bool GetProfileByName(string profileName, out GuardProfileEntry profileEntry) {
if (string.IsNullOrEmpty(profileName)) {
profileEntry = null;
return false;
}
return ProfileEntries.TryGetValue(profileName, out profileEntry);
}
public static GuardProfileEntry DefaultProfile() {
return new GuardProfileEntry {
Name = "Bank Guard", WearItems = new List<GuardProfileEntry.WearEntry> {
new() {
ShortName = "hazmatsuit_scientist_peacekeeper", SkinID = 0UL
}
}
, BeltItems = new List<GuardProfileEntry.BeltEntry> {
new() {
ShortName = "smg.mp5", Amount = 1, SkinID = 0UL, Ammo = null, Mods = new List<string>()
}
, new() {
ShortName = "syringe.medical", Amount = 10, SkinID = 0UL, Ammo = null, Mods = new List<string>()
}
,
}
, Kit = "", Health = 250f, RoamRange = 5f, ChaseRange = 25f, SenseRange = 75f, ListenRange = 75f / 2, AttackRangeMultiplier = 8f, CheckVisionCone = false, VisionCone = 180f, DamageScale = 1f, TurretDamageScale = 0.25f, AimConeScale = 0.35f, DisableRadio = false, CanRunAwayWater = true, CanSleep = false, Speed = 8.5f, HomePosition = string.Empty, MemoryDuration = 30f, Stationary = false, UseUnderGround = false
}
;
}
}
private class GuardProfileEntry {
public string Name;
public List<WearEntry> WearItems;
public List<BeltEntry> BeltItems;
public string Kit;
public float Health;
public float RoamRange;
public float ChaseRange;
public float SenseRange;
public float ListenRange;
public float AttackRangeMultiplier;
public bool CheckVisionCone;
public float VisionCone;
public bool HostileTargetsOnly;
public float DamageScale;
public float TurretDamageScale;
public float AimConeScale;
public bool DisableRadio;
public bool CanRunAwayWater;
public bool CanSleep;
public float SleepDistance;
public float Speed;
public int AreaMask;
public int AgentTypeID;
[JsonIgnore]
public string HomePosition;
public float MemoryDuration;
public bool Stationary;
public bool UseUnderGround;
public HashSet<string> States;
[JsonIgnore]
public JObject Parsed;
public class BeltEntry {
public string ShortName;
public int Amount;
public ulong SkinID;
public List<string> Mods;
public string Ammo;
public static BeltEntry SerializeItem(Item item) {
BeltEntry wearEntry = new BeltEntry {
ShortName = item.info.shortname, SkinID = item.skin, Amount = item.amount, Mods = new List<string>()
}
;
if (item.GetHeldEntity() is BaseProjectile projectile && projectile.primaryMagazine?.ammoType != null) wearEntry.Ammo = projectile.primaryMagazine.ammoType.shortname;
if (item.contents?.itemList == null) return wearEntry;
foreach (Item itemContent in item.contents.itemList) wearEntry.Mods.Add(itemContent.info.shortname);
return wearEntry;
}
}
public class WearEntry {
public string ShortName;
public ulong SkinID;
public static WearEntry SerializeItem(Item item) {
return new WearEntry {
ShortName = item.info.shortname, SkinID = item.skin
}
;
}
}
public void CacheConfig() {
Parsed = new JObject {
["Name"]
= Name, ["WearItems"] = new JArray {
WearItems.Select(x => new JObject {
["ShortName"]
= x.ShortName, ["SkinID"] = x.SkinID
}
)
}
, ["BeltItems"] = new JArray {
BeltItems.Select(x => new JObject {
["ShortName"]
= x.ShortName, ["Amount"] = x.Amount, ["SkinID"] = x.SkinID, ["Mods"] = new JArray {
x.Mods
}
, ["Ammo"] = x.Ammo
}
)
}
, ["Kit"] = Kit, ["Health"] = Health, ["RoamRange"] = RoamRange, ["ChaseRange"] = ChaseRange, ["SenseRange"] = SenseRange, ["ListenRange"] = SenseRange / 2f, ["AttackRangeMultiplier"] = AttackRangeMultiplier, ["CheckVisionCone"] = CheckVisionCone, ["VisionCone"] = VisionCone, ["HostileTargetsOnly"] = HostileTargetsOnly, ["DamageScale"] = DamageScale, ["TurretDamageScale"] = TurretDamageScale, ["AimConeScale"] = AimConeScale, ["DisableRadio"] = DisableRadio, ["CanRunAwayWater"] = CanRunAwayWater, ["CanSleep"] = CanSleep, ["SleepDistance"] = SleepDistance, ["Speed"] = Speed, ["AreaMask"] = !UseUnderGround ? 1 : 25, ["AgentTypeID"] = !UseUnderGround ? -1372625422 : 0, ["HomePosition"] = string.Empty, ["MemoryDuration"] = MemoryDuration, ["States"] = new JArray {
Stationary ? new HashSet<string> {
"IdleState", "CombatStationaryState"
}
: new HashSet<string> {
"RoamState", "ChaseState", "CombatState", "RaidState"
}
}
}
;
}
}
#endregion
#region Loot Profile Storage
private class LootProfileStorage : BaseStorage<LootProfileStorage> {
public Dictionary<string, LootProfileEntry> ProfileEntries = new(StringComparer.OrdinalIgnoreCase);
public bool GetProfileByName(string profileName, out LootProfileEntry foundEntry) {
if (string.IsNullOrEmpty(profileName)) {
foundEntry = null;
return false;
}
return ProfileEntries.TryGetValue(profileName, out foundEntry);
}
public static LootProfileEntry DefaultProfile() {
return new LootProfileEntry {
MinItems = 1, MaxItems = 1, SkinId = 0UL, HackableSeconds = 300f, LootItems = new List<LootProfileEntry.LootItem>()
}
;
}
}
private class LootProfileEntry {
public int MinItems = 1;
public int MaxItems = 1;
public ulong SkinId = 0UL;
public float HackableSeconds = 300f;
public List<LootItem> LootItems = new();
public class LootItem {
public string Shortname;
public string ItemName = string.Empty;
public ulong SkinID;
public int MinAmount = 1;
public int MaxAmount = 1;
public bool IsBlueprint;
[JsonIgnore]
private int _itemId = -1;
[JsonIgnore]
public int ItemID {
get {
if (_itemId == -1) _itemId = ItemManager.FindItemDefinition(Shortname)?.itemid ?? -1;
return _itemId;
}
}
public LootItem() {
}
public LootItem(Item item) {
Shortname = item.info.shortname;
ItemName = item.name;
SkinID = item.skin;
MinAmount = item.amount;
MaxAmount = item.amount;
IsBlueprint = item.blueprintTarget != 0;
}
public void CreateItem(LootContainer container) {
if (ItemID == -1) {
_instance?.Puts($"[BankHeist] - Failed to find ItemDefinition for {Shortname}!");
return;
}
Item item;
if (IsBlueprint) {
item = ItemManager.CreateByItemID(-996920608, UnityEngine.Random.Range(MinAmount, MaxAmount), SkinID);
item.blueprintTarget = ItemID;
}
else {
item = ItemManager.CreateByItemID(ItemID, UnityEngine.Random.Range(MinAmount, MaxAmount), SkinID);
item.name = ItemName;
}
if (!item.MoveToContainer(container.inventory)) item.Remove();
}
}
public void GenerateContainerItems(ref List<LootItem> items, int maxItemCount) {
for (int i = 0; i < 100; i++) {
LootItem lootItem = LootItems[UnityEngine.Random.Range(0, LootItems.Count)];
if (!items.Contains(lootItem)) items.Add(lootItem);
if (items.Count >= maxItemCount) break;
}
}
public void PopulateContainer(LootContainer container, int itemCount) {
if (LootItems == null || LootItems.Count == 0) return;
List<LootItem> items = Facepunch.Pool.Get<List<LootItem>>();
try {
container.inventory.SafeClear();
GenerateContainerItems(ref items, Mathf.Clamp(itemCount, 1, 24));
container.inventory.capacity = items.Count;
foreach (LootItem lootItem in items) lootItem.CreateItem(container);
}
catch (Exception e) {
//
}
Facepunch.Pool.FreeUnmanaged<LootItem>(ref items);
}
}
#endregion
#region Door Profile Storage
private class DoorProfileStorage : BaseStorage<DoorProfileStorage> {
public Dictionary<string, DoorProfileEntry> ProfileEntries = new(StringComparer.OrdinalIgnoreCase);
public bool GetProfileByName(string profileName, out DoorProfileEntry foundEntry) {
if (string.IsNullOrEmpty(profileName)) {
foundEntry = null;
return false;
}
return ProfileEntries.TryGetValue(profileName, out foundEntry);
}
public static DoorProfileEntry DefaultProfile() {
return new DoorProfileEntry {
Health = 1000f, SkinId = 0UL,
}
;
}
}
private class DoorProfileEntry {
public float Health;
public ulong SkinId;
}
#endregion
#region Oxide Hooks
private void OnServerInitialized() {
cmd.AddChatCommand("blp", this, nameof(LootProfileCommand));
cmd.AddChatCommand("bdp", this, nameof(DoorProfileCommand));
cmd.AddChatCommand("bgp", this, nameof(GuardProfileCommand));
cmd.AddChatCommand("bse", this, nameof(SpawnEntityCommand));
cmd.AddChatCommand("bhm", this, nameof(BankManageCommand));
EntitiesCache.Initialize();
MonumentCache.Initialize();
PerformSetupRoutine();
if (_configData.BlockedCommands.Count > 0) _hooks.Add("OnServerCommand");
SubscribeToHooks(true);
}
private void Init() {
permission.RegisterPermission(WORKER_PERM, this);
permission.RegisterPermission(MANAGE_PERM, this);
_instance = this;
GuardProfileStorage.LoadData("BankHeist/GuardProfileStorage");
LootProfileStorage.LoadData("BankHeist/LootProfileStorage");
DoorProfileStorage.LoadData("BankHeist/DoorProfileStorage");
BankStorage.LoadData("BankHeist/BankStorage");
SubscribeToHooks();
}
private void Unload() {
DestroySetupRoutine();
BankHeistInstance.OnUnload();
MonumentCache.OnUnload();
EntitiesCache.OnUnload();
BankStorage.OnUnload();
GuardProfileStorage.OnUnload();
LootProfileStorage.OnUnload();
DoorProfileStorage.OnUnload();
}
private object OnServerCommand(ConsoleSystem.Arg arg) {
BasePlayer player = arg.Player();
if (player != null) {
foreach (string blockedCommand in _configData.BlockedCommands) {
if (arg.FullString.EndsWith(blockedCommand, StringComparison.OrdinalIgnoreCase)) {
MessagePlayer(player, LangEntry.OnCommandBlocked);
return true;
}
}
}
return null;
}
private void OnEntityTakeDamage(ScientistNPC npc, HitInfo info) {
if (npc == null || info?.InitiatorPlayer == null || !info.InitiatorPlayer.IsPlayer()) return;
EntitiesCache.GetBankByEntity(npc.net.ID)?.OnBankAttacked(info.InitiatorPlayer);
}
private void OnEntityTakeDamage(Door door, HitInfo info) {
if (door == null || door.isSecurityDoor || info?.InitiatorPlayer == null || !info.InitiatorPlayer.IsPlayer()) return;
if (!info.IsMajorityDamage(DamageType.Explosion)) return;
EntitiesCache.GetBankByEntity(door.net.ID)?.OnBankAttacked(info.InitiatorPlayer);
}
private void OnEntityDeath(BasePlayer player, HitInfo info) {
if (player == null || !player.IsPlayer()) return;
_players.Remove(player.userID);
}
private void OnEntityDeath(ScientistNPC npc, HitInfo info) {
if (npc == null) return;
EntitiesCache.GetBankByEntity(npc.net.ID)?.OnEntityDeath(npc, info?.InitiatorPlayer);
}
private void OnEntityKill(ScientistNPC npc) {
if (npc == null) return;
OnEntityDeath(npc, null);
}
private void OnEntityDeath(Door door, HitInfo info) {
if (door == null) return;
EntitiesCache.GetBankByEntity(door.net.ID)?.OnEntityDeath(door, info?.InitiatorPlayer);
}
private void OnEntityKill(Door door) {
if (door == null) return;
OnEntityDeath(door, null);
}
private void OnEntityKill(LootContainer container) {
if (container == null) return;
EntitiesCache.GetBankByEntity(container.net.ID)?.OnEntityDeath(container);
}
private void OnLootEntityEnd(BasePlayer player, LootContainer container) {
if (container == null) return;
EntitiesCache.GetBankByEntity(container.net.ID)?.OnLootEntityEnd(player, container);
}
private object CanLootEntity(BasePlayer player, LootContainer container) {
return EntitiesCache.GetBankByEntity(container.net.ID) ?.CanLootEntity(player);
}
private object CanHackCrate(BasePlayer player, HackableLockedCrate crate) {
return EntitiesCache.GetBankByEntity(crate.net.ID) ?.CanHackCrate(player, crate);
}
private bool? CanUseLockedEntity(BasePlayer player, CodeLock codeLock) {
if (codeLock.GetParentEntity() is Door entity) {
BankHeistInstance instance = EntitiesCache.GetBankByEntity(entity.net.ID);
if (instance != null && instance.CanUseLockedEntity(player, codeLock)) return true;
return null;
}
return null;
}
#endregion
#region Bank Heist Instance
private void PerformSetupRoutine() {
if (_coroutine != null) return;
_coroutine = ServerMgr.Instance.StartCoroutine(SpawnBankInstances());
}
private void DestroySetupRoutine() {
if (_coroutine != null) ServerMgr.Instance.StopCoroutine(_coroutine);
_coroutine = null;
}
private IEnumerator SpawnBankInstances() {
yield return CoroutineEx.waitForSeconds(10f);
Puts("Initializing banks...");
Stopwatch stopwatch = Stopwatch.StartNew();
foreach (BankEntry bankEntry in BankStorage.Data.BankEntries) yield return bankEntry.SpawnBankInstance();
stopwatch.Stop();
Puts($"Finished spawning banks: {stopwatch.ElapsedMilliseconds}ms.");
_coroutine = null;
}
private class BankHeistInstance : FacepunchBehaviour {
public static readonly string DebugText = "<size=24>Bank Information</size>\n----------------------------------\n<size=16>Display Name: {0}\nMonument Parent Name: {1}\nTime Between Resets: {2}\nCrates: {3} | Doors: {4} | Guards: {5}</size>";
public static List<BankHeistInstance> BankHeistInstances = new();
private List<SphereEntity> _sphereInstances = new();
private MapMarkerGenericRadius _markerInstance;
private IOEntity _alarmInstance;
public GuardSpawnGroup guardSpawnGroup;
public LootSpawnGroup lootSpawnGroup;
public DoorSpawnGroup doorSpawnGroup;
public string displayName;
public string monumentName;
public Vector3 playerDetectionOrigin;
public float playerDetectionRadius;
public Vector3 markerOrigin;
public Color markerReadyColor;
public Color markerResetColor;
public Color markerOuterColor;
public float markerRadius;
public float markerOpacity;
public float timeBetweenResets = 3600.0f;
public float resetTickTime = 10.0f;
public float resetTimeElapsed;
public float alarmTime;
public bool clearAlarm;
public bool requiresReset;
public bool lootedEvery;
public float lastCooldownTime = 60f;
public float lastAttackedTime;
public float SecondsSinceAttacked => lastAttackedTime - Time.realtimeSinceStartup;
public static void OnUnload() {
if (Rust.Application.isQuitting) return;
for (int i = BankHeistInstances.Count - 1; i >= 0; i--) BankHeistInstances[i]?.Dispose();
BankHeistInstances.Clear();
}
public static bool FindBankByName(string displayName, out BankHeistInstance heistInstance) {
heistInstance = BankHeistInstances.Find(x => x.displayName.Equals(displayName, StringComparison.OrdinalIgnoreCase));
return heistInstance != null;
}
public static void CacheHeistInstance(BankHeistInstance bankHeistInstance) => BankHeistInstances.Add(bankHeistInstance);
public static void RemoveHeistInstance(BankHeistInstance bankHeistInstance) {
BankHeistInstances.Remove(bankHeistInstance);
UnityEngine.GameObject.Destroy(bankHeistInstance.gameObject);
}
#region Unity
private void OnTriggerEnter(Collider other) {
BasePlayer player = other?.GetComponentInParent<BasePlayer>();
if (player == null || !player.userID.IsSteamId()) return;
_instance._players.Add(player.userID);
if (_instance._configData.EnableZoneMessages) _instance.MessagePlayer(player, LangEntry.OnEnterZone, displayName);
}
private void OnTriggerExit(Collider other) {
BasePlayer player = other?.GetComponentInParent<BasePlayer>();
if (player == null || !player.userID.IsSteamId()) return;
_instance._players.Remove(player.userID);
if (_instance._configData.EnableZoneMessages) _instance?.MessagePlayer(player, LangEntry.OnLeaveZone, displayName);
}
#endregion
#region Setup / Destroy
public IEnumerator Initialize() {
CreateSpheres();
CreateMarkers();
CreateAlarm();
ResetTimer();
ResetEvent();
DebugInfo(DebugMode.Info);
yield return CoroutineEx.waitForEndOfFrame;
}
public void Dispose() {
StopAllCoroutines();
CancelInvoke();
DestroySpheres();
DestroyMarkers();
DestroyAlarm();
DestroySpawnGroups();
BankHeistInstance.RemoveHeistInstance(this);
}
#endregion
#region Event Management
private void ResetTick() {
bool hasNearbyPlayers = ActiveNearbyPlayers();
if (!hasNearbyPlayers) resetTimeElapsed += resetTickTime;
if (resetTimeElapsed <= timeBetweenResets) return;
if (hasNearbyPlayers) return;
resetTimeElapsed = 0.0f;
ResetEvent();
}
public void ResetTimer() {
resetTimeElapsed = 0.0f;
CancelInvoke(ResetTick);
InvokeRandomized(ResetTick, UnityEngine.Random.Range(0.0f, 1f), resetTickTime, 0.5f);
}
public void ResetEvent() {
if (!requiresReset) return;
requiresReset = false;
lootedEvery = false;
RemoveNearbySleepers();
StopAlarm();
UpdateMarker();
RefillSpawnGroups();
_instance?.MessagePlayers(LangEntry.OnBankReset, displayName);
Interface.Oxide.CallHook("OnBankHeistReset", displayName);
}
private bool HasGuardsRemaining() {
return guardSpawnGroup != null && guardSpawnGroup.spawnInstances.Count > 0;
}
private bool ActiveNearbyPlayers() {
if (BasePlayer.activePlayerList != null && BasePlayer.activePlayerList.Count > 0) {
for (int i = BasePlayer.activePlayerList.Count - 1; i >= 0; --i) {
BasePlayer player = BasePlayer.activePlayerList[i];
if (player.IsValid() && player.IsWithinRadius(playerDetectionOrigin, playerDetectionRadius) && !player.HasP
// ... [Code preview truncated due to length. Full source in attached file] ...