Иконка ресурса

FREE TugboatPirates : 1.0.0 1.0.0

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Oxide.Plugins.TugboatPiratesExt;
using Rust;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using UnityEngine;
/**
 * 
 * VERSION HISTORY
 * 
 * V 1.1.0
 * - clean up default config
 * - add hooks for event start and end
 * - fix config spelling error
 * - fix path finding issues
 * - fix bumping noise of locked crate
 * - add config option for boat speed
 * - add support for notify
 * - make boat visible across the map by default
 * - add config option to remove npc corpses
 * - fix conflict with Loottable
 * - add config option for map markers
 * 
 * V 1.2.0
 * - prevent decay of event tugboats
 * - stop boat when it is destroyed
 * - kill npcs when boat is sinking
 * - add config option to prevent damage to boat
 * - display sinking time to players near the boat
 * - configurable sinking time after captain has been killed
 * - configurable sinking time after crate has been looted
 * - fix captain mounting issues
 * - prevent players from mounting boat
 * - fix loot conflicts with CustomLoot
 * - add config option for notify notification type
 * - add config option for crate timer
 * - support pvp zones with TruePVE
 * - fix boats remaining after event
 * 
 * V 1.2.1
 * - change chat commands to universal commands
 * - fix hook conflicts with TruePVE
 * 
 * V 1.2.2
 * - fix CanEntityTakeDamage hook conflict with raidable bases
 * - fix captain's note missing sometimes
 * 
 * V 1.2.3
 * - fix parenting issues with npcs
 * 
 * V 1.2.4
 * - fix rigidbody error message
 * 
 */
namespace Oxide.Plugins {
    [Info(nameof(TugboatPirates), "The_Kiiiing", "1.2.4")]
    internal class TugboatPirates : RustPlugin {
#region Fields
        private const string PERM_ADMIN = "tugboatpirates.admin";
        private const string GUI_CONTAINER = "tugboatpirates.ui";
        // Change at your own risk
        private const float MAP_SIZE_SCALE = 0.55f;
        private const float NODE_TOLERANCE = 80f;
        [PluginReference]
        private Plugin NpcSpawn, Notify;
        private static TugboatPirates _instance;
        private Timer nextEventTimer;
        private TugboatController currentBoat;
        private readonly Dictionary<ulong, ValueTuple<string, bool>> npcProfiles = new Dictionary<ulong, ValueTuple<string, bool>>();
        private readonly Dictionary<ulong, string> captainNoteTexts = new Dictionary<ulong, string>();
        private static BasePlayer startPlayer;
        private readonly IReadOnlyDictionary<string, Vector3> npcSpawnPoints = new Dictionary<string, Vector3> {
            // Back low
            ["back_right"]
            = new Vector3(2f, 2.69f, -8.8f), ["back_left"] = new Vector3(-2f, 2.69f, -8.8f),  // Upper front
            ["upper_front_right"]
            = new Vector3(2f, 4.6f, 5f), ["upper_front_left"] = new Vector3(-2f, 4.6f, 5f),  // Upper back
            ["upper_back_right"]
            = new Vector3(2.8f, 4.6f, -5f), ["upper_back_left"] = new Vector3(-2.8f, 4.6f, -5f),  // Back roof
            ["roof_back"]
            = new Vector3(0, 7.2f, -1.3f),  // Entrance
            ["entrance_right"]
            = new Vector3(2.7f, 5.7f, 1.4f), ["entrance_left"] = new Vector3(-2.7f, 5.7f, 1.4f),  // Roof
            ["roof_right"]
            = new Vector3(1.4f, 8.6f, 3.2f), ["roof_left"] = new Vector3(-1.4f, 8.6f, 3.2f),  // Front
            ["front"]
            = new Vector3(0f, 2f, 9.4f),  // Sides
            ["right"]
            = new Vector3(3.6f, 2f, 0f), ["left"] = new Vector3(-3.6f, 2f, 0f),
        }
        ;
#endregion
#region Configuration
        private static Configuration _config;
        private class Configuration {
            [JsonProperty("Time between events (minutes, set to -1 to disable scheduled events)")]
            public int eventDelay = 60;
            [JsonProperty("Event duration (seconds)")]
            public int eventDuration = 3600;
            [JsonProperty("Show toast when event starts")]
            public bool showToast = true;
            [JsonProperty("Announce event in chat")]
            public bool announceChat = true;
            [JsonProperty("Use Notify")]
            public bool notifyEnabled = false;
            [JsonProperty("Notify notification type")]
            public int notificationType = 0;
            [JsonProperty("Boat leave time before despawning (seconds, boat will return if value is too big)")]
            public int leaveTime = 90;
            [JsonProperty("Time before boat sinks after captain is killed (seconds)")]
            public int timeBeforeSinkingCaptain = 1200;
            [JsonProperty("Time before boat sinks after crate has been looted (seconds)")]
            public int timeBeforeSinkingLooted = 300;
            [JsonProperty("Time before boat is destroyed after sinking (seconds)")]
            public int destroyTime = 60;
            [JsonProperty("Render boat across the map (might impact client performance)")]
            public bool boatGlobalBroadcast = true;
            [JsonProperty("Create PVP zone around boat (requires TruePVE)")]
            public bool enablePvp = false;
            [JsonProperty("Zone radius (smaller than 25 not recommended)")]
            public float zoneRadius = 25f;
            [JsonProperty("Zone darkness (0 = invisible, higher value = darker)")]
            public int zoneDarkness = 0;
            [JsonProperty("Disable damage to pirate tugboat")]
            public bool disableDamage = true;
            [JsonProperty("Crate hack time (seconds, -1 for default time)")]
            public int crateTimerOverride = -1;
            [JsonProperty("Boat configuration", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public BoatConfig boatConfig = new BoatConfig {
                captainNpcProfile = "captain", npcSpawnProfiles = new Dictionary<string, string> {
                    ["back_right"]
                    = "pirate_lr", ["back_left"] = "pirate_lr", ["upper_front_right"] = "pirate_lr", ["upper_front_left"] = "pirate_lr", ["upper_back_right"] = "pirate_lr", ["upper_back_left"] = "pirate_lr", ["roof_back"] = "pirate_lr", ["entrance_right"] = "pirate_lr", ["entrance_left"] = "pirate_lr", ["roof_right"] = "pirate_lr", ["roof_left"] = "pirate_lr", ["front"] = "pirate_lr", ["right"] = "pirate_mp5", ["left"] = "pirate_mp5",
                }
                , interior = new List<InteriorObject> {
                    new InteriorObject {
                        prefabPath = "assets/prefabs/deployable/chinooklockedcrate/codelockedhackablecrate.prefab",  //prefabPath = "assets/prefabs/deployable/large wood storage/box.wooden.large.prefab",
                        localPosition = new Vector3(0, 2f, 4.2f), rotationY = 180f, lootProfile = "", skinId = 1394363785
                    }
                    , new InteriorObject {
                        prefabPath = "assets/prefabs/deployable/research table/researchtable_deployed.prefab", localPosition = new Vector3(2.1f, 2f, -4f), rotationY = -90f
                    }
                    , new InteriorObject {
                        prefabPath = "assets/prefabs/deployable/sofa/sofa.deployed.prefab", localPosition = new Vector3(-1.9f, 2, -2.2f), rotationY = 90f
                    }
                    , new InteriorObject {
                        prefabPath = "assets/prefabs/deployable/large wood storage/box.wooden.large.prefab", localPosition = new Vector3(-1.9f, 2, -4.2f), rotationY = 90f, lootProfile = "crate_2", skinId = 811157743ul
                    }
                    , new InteriorObject {
                        prefabPath = "assets/prefabs/deployable/bed/bed_deployed.prefab", localPosition = new Vector3(2f, 2f, -1.8f), rotationY = -90f
                    }
                    , new InteriorObject {
                        prefabPath = "assets/bundled/prefabs/radtown/crate_normal.prefab", localPosition = new Vector3(-1.9f, 2f, 1f), rotationY = 90f, lootProfile = "crate_2"
                    }
                    , new InteriorObject {
                        prefabPath = "assets/prefabs/deployable/table/table.deployed.prefab", localPosition = new Vector3(0.75f, 2f, 0.62f), rotationY = 225f
                    }
                    , new InteriorObject {
                        prefabPath = "assets/bundled/prefabs/radtown/crate_basic.prefab", localPosition = new Vector3(0.3f, 2f, 0.92f), rotationY = 200f, lootProfile = "crate_2"
                    }
                    , new InteriorObject {
                        prefabPath = "assets/prefabs/deployable/secretlab chair/secretlabchair.deployed.prefab", localPosition = new Vector3(1.8f, 2f, 1.9f), rotationY = 210f
                    }
                    , new InteriorObject {
                        prefabPath = "assets/prefabs/building/door.hinged/door.hinged.toptier.prefab", localPosition = new Vector3(0.00f, 2.03f, -5.34f), rotationY = 270f, codelocked = true, skinId = 850289896
                    }
                    , new InteriorObject {
                        prefabPath = "assets/prefabs/misc/permstore/factorydoor/door.hinged.industrial.d.prefab", localPosition = new Vector3(2.00f, 5.75f, 1.39f), rotationY = 180f,
                    }
                    , new InteriorObject {
                        prefabPath = "assets/prefabs/misc/permstore/factorydoor/door.hinged.industrial.d.prefab", localPosition = new Vector3(-2.00f, 5.75f, 1.39f), rotationY = 0f,
                    }
                    , new InteriorObject {
                        prefabPath = "assets/content/structures/excavator/prefabs/diesel_collectable.prefab", localPosition = new Vector3(-1.8f, 2f, 3f), rotationY = 110f,
                    }
                }
            }
            ;
            [JsonProperty("Npc profiles", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public Dictionary<string, NpcConfig> npcProfiles = new Dictionary<string, NpcConfig> {
                ["pirate_lr"]
                = new NpcConfig {
                    name = "Pirate", health = 200f, enableRadio = true, senseRange = 50f, memoryDuration = 60f, visionCone = 135f, damageScale = 1f, clothing = new List<LootItem> {
                        new LootItem {
                            shortName = "hat.boonie", skinId = 965553937ul
                        }
                        , new LootItem {
                            shortName = "hoodie", skinId = 2984978438ul
                        }
                        , new LootItem {
                            shortName = "pants", skinId = 2984977257ul
                        }
                        , new LootItem {
                            shortName = "attire.hide.boots", skinId = 861468674ul
                        }
                        ,
                    }
                    , belt = new List<LootItem> {
                        new LootItem {
                            shortName = "rifle.lr300"
                        }
                    }
                    , kit = string.Empty, lootProfile = "pirate"
                }
                , ["pirate_mp5"] = new NpcConfig {
                    name = "Pirate", health = 150f, enableRadio = true, senseRange = 50f, memoryDuration = 60f, visionCone = 135f, damageScale = 1f, clothing = new List<LootItem> {
                        new LootItem {
                            shortName = "hat.boonie", skinId = 965553937ul
                        }
                        , new LootItem {
                            shortName = "hoodie", skinId = 2984978438ul
                        }
                        , new LootItem {
                            shortName = "pants", skinId = 2984977257ul
                        }
                        , new LootItem {
                            shortName = "attire.hide.boots", skinId = 861468674ul
                        }
                        ,
                    }
                    , belt = new List<LootItem> {
                        new LootItem {
                            shortName = "smg.mp5"
                        }
                    }
                    , kit = string.Empty, lootProfile = "pirate"
                }
                , ["captain"] = new NpcConfig {
                    name = "Captain", health = 100f, enableRadio = false, senseRange = 0f, memoryDuration = 0f, visionCone = 0f, damageScale = 1f, clothing = new List<LootItem> {
                        new LootItem {
                            shortName = "hat.boonie", skinId = 965553937ul
                        }
                        , new LootItem {
                            shortName = "tshirt", skinId = 811762477ul
                        }
                        , new LootItem {
                            shortName = "pants.shorts", skinId = 849256923ul
                        }
                        , new LootItem {
                            shortName = "attire.hide.boots", skinId = 861468674ul
                        }
                        ,
                    }
                    , belt = new List<LootItem> {
                        new LootItem {
                            shortName = "mace.baseballbat"
                        }
                    }
                    , kit = string.Empty, lootProfile = "pirate"
                }
            }
            ;
            [JsonProperty("Loot profiles", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public Dictionary<string, List<LootManager.LootItem>> lootProfiles = new Dictionary<string, List<LootManager.LootItem>> {
                ["crate_main"]
                = new List<LootManager.LootItem> {
                    new LootManager.LootItem("scrap", 8, 20, 1f), new LootManager.LootItem("metal.refined", 10, 25, 0.7f), new LootManager.LootItem("lmg.m249", 1, 1, 0.05f), new LootManager.LootItem("rifle.ak.diver", 1, 1, 0.1f), new LootManager.LootItem("rifle.bolt", 1, 1, 0.1f), new LootManager.LootItem("smg.mp5", 1, 1, 0.2f), new LootManager.LootItem("smg.thompson", 1, 1, 0.2f), new LootManager.LootItem("ammo.shotgun", 4, 8, 0.2f), new LootManager.LootItem("ammo.shotgun.fire", 4, 8, 0.2f), new LootManager.LootItem("ammo.shotgun.slug", 4, 8, 0.2f), new LootManager.LootItem("ammo.pistol", 15, 30, 0.2f), new LootManager.LootItem("ammo.pistol.hv", 15, 30, 0.2f), new LootManager.LootItem("ammo.pistol.fire", 15, 30, 0.2f), new LootManager.LootItem("ammo.rifle", 12, 24, 0.2f), new LootManager.LootItem("ammo.rifle.hv", 12, 24, 0.2f), new LootManager.LootItem("ammo.rifle.incendiary", 12, 24, 0.2f),
                }
                , ["crate_2"] = new List<LootManager.LootItem> {
                    new LootManager.LootItem("scrap", 2, 20, 1f), new LootManager.LootItem("metal.refined", 4, 8, 0.5f), new LootManager.LootItem("gears", 1, 3, 0.2f), new LootManager.LootItem("sewingkit", 1, 3, 0.2f), new LootManager.LootItem("rope", 1, 3, 0.2f), new LootManager.LootItem("sheetmetal", 1, 2, 0.2f), new LootManager.LootItem("grenade.molotov", 1, 2, 0.1f), new LootManager.LootItem("grenade.f1", 1, 4, 0.1f), new LootManager.LootItem("telephone", 1, 1, 0.1f), new LootManager.LootItem("multiplegrenadelauncher", 1, 1, 0.1f),
                }
                , ["pirate"] = new List<LootManager.LootItem> {
                    new LootManager.LootItem("scrap", 2, 6, 1f), new LootManager.LootItem("bottle.vodka", 1, 1, 0.7f), new LootManager.LootItem("pistol.eoka", 1, 1, 0.2f), new LootManager.LootItem("ammo.handmade.shell", 5, 10, 0.2f), new LootManager.LootItem("rope", 1, 3, 0.3f), new LootManager.LootItem("sewingkit", 1, 2, 0.3f),
                }
                ,
            }
            ;
            [JsonProperty("Priate quotes (included in captain's note)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> pirateQuotes = new List<string> {
                "If rum can’t fix it, ye are not using enough rum.", "But… why is the rum gone?", "Be who you arrrr...", "All for rum and rum for all!", "Land was created to provide a place for boats to visit.", "If ye can read this ye be stupid."
            }
            ;
            public void RemoveUnsupportedItems() {
                boatConfig.interior.RemoveAll(x => x.prefabPath == "assets/bundled/prefabs/radtown/oil_barrel.prefab" || x.prefabPath == "assets/prefabs/deployable/waterpurifier/waterpurifier.deployed.prefab");
            }
        }
        private class BoatConfig {
            [JsonProperty("Npc profile for captain (must be a valid profile)")]
            public string captainNpcProfile;
            [JsonProperty("Boat speed multiplier")]
            public float speedMultiplier = 1f;
            [JsonProperty("Enable map marker")]
            public bool useMapMarker = true;
            [JsonProperty("Map marker color (hex format)")]
            public string markerColor = "#2480FB";
            [JsonProperty("Map marker name")]
            public string markerName = "Pirate Ship";
            [JsonProperty("Npc spawn locations and profiles", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public Dictionary<string, string> npcSpawnProfiles = new Dictionary<string, string>();
            [JsonProperty("Interior objects (crates, decoration, etc.)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<InteriorObject> interior = new List<InteriorObject>();
        }
        private class InteriorObject {
            [JsonProperty("Prefab path")]
            public string prefabPath;
            [JsonProperty("Rotation")]
            public float rotationY;
            [JsonProperty("Position on boat")]
            public Vector3 localPosition;
            [JsonProperty("Skin id")]
            public ulong skinId;
            [JsonProperty("Loot profile (only for crates, leave empty for default loot)", NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
            public string lootProfile = string.Empty;
            [JsonProperty("Add code lock (only for doors)", NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
            public bool? codelocked = null;
        }
        private class NpcConfig {
            public string name = "Pirate";
            public float health = 150f;
            public bool enableRadio = true;
            public float senseRange = 50f;
            public float visionCone = 135f;
            public float damageScale = 1f;
            public float memoryDuration = 60f;
            public bool removeCorpseAfterDeath = false;
            public string lootProfile = string.Empty;
            public string kit = string.Empty;
            [JsonProperty("Clothing items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<LootItem> clothing = new List<LootItem>();
            [JsonProperty("Belt items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<LootItem> belt = new List<LootItem>();
        }
        private class LootItem {
            public string shortName;
            public int amount = 1;
            public ulong skinId;
        }
        protected override void LoadConfig() {
            base.LoadConfig();
            try {
                _config = Config.ReadObject<Configuration>();
                if (_config == null) {
                    throw new Exception();
                }
                _config.RemoveUnsupportedItems();
                SaveConfig();
            }
            catch {
                PrintError("Your configuration file contains an error.Using default configuration values.");
                LoadDefaultConfig();
            }
        }
        protected override void SaveConfig() => Config.WriteObject(_config);
        protected override void LoadDefaultConfig() => _config = new Configuration();
#endregion
#region Loot Manager
        private static class LootManager {
            public static void FillWithLoot(ItemContainer container, List<LootItem> lootTable) {
                ClearContainer(container);
                int amt = 0;
                foreach (var itm in lootTable) {
                    if (UnityEngine.Random.Range(0f, 1f) <= itm.chance) {
                        var item = itm.CreateItem();
                        if (item == null) continue;
                        if (!item.MoveToContainer(container)) item.Remove();
                        amt++;
                    }
                    if (amt >= container.capacity) break;
                }
            }
            public static void FillWithLoot(StorageContainer container, List<LootItem> lootTable) => FillWithLoot(container.inventory, lootTable);
            private static void ClearContainer(ItemContainer container) {
                container.itemList.Clear();
                container.OnChanged();
                ItemManager.DoRemoves();
            }
            public class LootItem {
                [JsonProperty("Short name")]
                public string shortname;
                [JsonProperty("Min amount")]
                public int min;
                [JsonProperty("Max amount")]
                public int max;
                [JsonProperty("Chance")]
                public float chance;
                [JsonProperty("Skin id")]
                public ulong skin = 0;
                [JsonIgnore]
                public string text;
                [JsonIgnore]
                public ItemDefinition ItemDefinition => ItemManager.FindItemDefinition(shortname);
                public LootItem() {
                    shortname = "scrap";
                    min = 5;
                    max = 10;
                    chance = 1f;
                    skin = 0;
                }
                public LootItem(string shortname, int min, int max, float chance) {
                    this.shortname = shortname;
                    this.min = min;
                    this.max = max;
                    this.chance = chance;
                }
                public LootItem(string shortname, int min, int max, float chance, ulong skin) {
                    this.shortname = shortname;
                    this.min = min;
                    this.max = max;
                    this.chance = chance;
                    this.skin = skin;
                }
                public Item CreateItem() {
                    var itm = ItemManager.Create(ItemDefinition, UnityEngine.Random.Range(min, max + 1), skin);
                    if (text != null) {
                        itm.text = text;
                    }
                    itm?.OnVirginSpawn();
                    return itm;
                }
            }
        }
#endregion
#region Commands
        void CmdStart(IPlayer iplayer) {
            if (!permission.UserHasPermission(iplayer.Id, PERM_ADMIN)) {
                iplayer.Reply("You don't have permission to do that");
                return;
            }
            StartEvent(iplayer.Object as BasePlayer, true);
        }
        void CmdStop(IPlayer iplayer) {
            if (!permission.UserHasPermission(iplayer.Id, PERM_ADMIN)) {
                iplayer.Reply("You don't have permission to do that");
                return;
            }
            EndEvent(true);
            iplayer.Reply("The event has ended");
        }
#endregion
#region Hooks
        void Init() {
            _instance = this;
            permission.RegisterPermission(PERM_ADMIN, this);
            AddCovalenceCommand("tugboatstart", nameof(CmdStart));
            AddCovalenceCommand("tugboatstop", nameof(CmdStop));
            Unsubscribe();
        }
        void OnServerInitialized() {
            TugboatPathFinder.GeneratePatrolPath();
            if (NpcSpawn == null) {
                Cerr("NpcSpawn is required to spawn NPCs.You can download it here: https://codefling.com/extensions/npc-spawn");
            }
            ScheduleEvent(0.5f);
        }
        void Unload() {
            currentBoat?.Destroy();
            nextEventTimer?.Destroy();
            _instance = null;
        }
#region Event Hooks
        void OnCrateHack(HackableLockedCrate crate) {
            if (_config.crateTimerOverride >= 0 && currentBoat?.crates.Contains(crate) == true) {
                crate.hackSeconds = HackableLockedCrate.requiredHackSeconds - _config.crateTimerOverride;
            }
        }
        object CanMountEntity(BasePlayer player, BaseMountable entity) {
            if (!player.IsNpc && entity.GetParentEntity()?.net.ID == currentBoat?.Boat.net.ID) {
                return false;
            }
            return null;
        }
        void OnLootEntityEnd(BasePlayer player, HackableLockedCrate crate) {
            currentBoat?.OnCrateLooted(crate);
        }
        object OnEntityTakeDamage(Tugboat boat, HitInfo info) {
            if (_config.disableDamage && boat.net.ID == currentBoat?.Boat.net.ID) {
                return true;
            }
            return null;
        }
        void OnCorpsePopulate(ScientistNPC npc, NPCPlayerCorpse corpse) {
            if (npc == null || corpse == null) {
                return;
            }
            ulong netId = npc.net.ID.Value;
            ValueTuple<string, bool> profile;
            if (npcProfiles.TryGetValue(netId, out profile)) {
                string captainNoteText = captainNoteTexts.GetValueOrDefault(netId, null);
                var lootTable = GetLootProfile(profile.Item1, captainNoteText);
                if (lootTable != null) {
                    _instance.NextFrame(() => {
                        if (!corpse.IsValid()) {
                            Cerr($"Failed to populate NPC corpse ({npc?.displayName}) - invalid corpse");
                            return;
                        }
                        LootManager.FillWithLoot(corpse.containers.First(), lootTable);
                        // Drop bag
                        if (profile.Item2 && !corpse.IsDestroyed) {
                            corpse.Kill();
                        }
                    }
                    );
                }
                npcProfiles.Remove(netId);
                captainNoteTexts.Remove(netId);
            }
        }
        // Loottable
        object OnCorpsePopulate(LootableCorpse corpse) {
            var scientistNet = corpse.parentEnt.net;
            if (scientistNet != null && npcProfiles.ContainsKey(scientistNet.ID.Value)) {
                Dprint("prevent populate corpse (Loottable)");
                return false;
            }
            return null;
        }
        // CustomLoot
        object OnCustomLootNPC(ulong netId) {
            if (npcProfiles.ContainsKey(netId)) {
                Dprint("prevent populate corpse (Custom Loot)");
                return false;
            }
            return null;
        }
        // TruePVE
        object CanEntityTakeDamage(BasePlayer target, HitInfo info) {
            if (!_config.enablePvp || currentBoat == null) {
                return null;
            }
            BasePlayer attacker = info?.InitiatorPlayer;
            if (attacker?.IsNpc == false && target?.IsNpc == false) {
                return (currentBoat.Zone.Players.Contains(attacker) && currentBoat.Zone.Players.Contains(target)) ? true : null;
            }
            return null;
        }
#endregion
        void OnTugboatPiratesEnded() {
            Unsubscribe();
            ScheduleEvent();
        }
#if DEBUG
        object OnHammerHit(BasePlayer player, HitInfo info) {
            var entity = info.HitEntity;
            var parent = entity?.GetParentEntity();
            if (entity != null && parent != null) {
                Dprint($"Local pos {entity.transform.localPosition} local rot {entity.transform.localEulerAngles.y:N0}");
                Dprint($"Calculated local pos {entity.transform.position - parent.transform.position}");
                Dprint($"new InteriorObject\r\n                        {{\r\n                            prefabPath = \"{entity.PrefabName}\",\r\n                            localPosition = new Vector3({entity.transform.localPosition.x:N2}f, {entity.transform.localPosition.y:N2}f, {entity.transform.localPosition.z:N2}f),\r\n                            rotationY = {entity.transform.localEulerAngles.y:N0}f,\r\n                            lootProfile = \"crate_main\"\r\n                        }},");
            }
            return null;
        }
#endif
        private void Subscribe() {
            Dprint("Subscribe");
            if (_config.disableDamage) {
                Subscribe(nameof(OnEntityTakeDamage));
            }
            if (_config.crateTimerOverride >= 0) {
                Subscribe(nameof(OnCrateHack));
            }
            if (_config.enablePvp) {
                Subscribe(nameof(CanEntityTakeDamage));
            }
            Subscribe(nameof(OnLootEntityEnd));
            Subscribe(nameof(CanMountEntity));
            Subscribe(nameof(CanMountEntity));
            Subscribe(nameof(OnCorpsePopulate));
            Subscribe(nameof(OnCustomLootNPC));
        }
        private void Unsubscribe() {
            Dprint("Unsubscribe");
            Unsubscribe(nameof(OnEntityTakeDamage));
            Unsubscribe(nameof(OnCrateHack));
            Unsubscribe(nameof(OnLootEntityEnd));
            Unsubscribe(nameof(CanMountEntity));
            Unsubscribe(nameof(CanMountEntity));
            Unsubscribe(nameof(OnCorpsePopulate));
            Unsubscribe(nameof(OnCustomLootNPC));
            Unsubscribe(nameof(CanEntityTakeDamage));
        }
#endregion
#region Path Finder
        private class TugboatPathFinder {
            private int targetNodeIndex = -1;
            private readonly float sqrVisitDistance;
            private int skip;
            private readonly List<Vector3> nodes;
            private bool leave;
            private Vector3 finalDestination;
            public TugboatPathFinder(List<Vector3> path, float visitDistance) {
                sqrVisitDistance = visitDistance * visitDistance;
                nodes = path;
                skip = 1;
            }
            public Vector3 GetCurrentNode() {
                return nodes[targetNodeIndex];
            }
            public Vector3 GetNextNode() {
                int idx = targetNodeIndex + skip;
                if (idx >= nodes.Count) {
                    idx = 0;
                }
                return nodes[idx];
            }
            public Vector3 GetRandomStartPosition(float height = 0) {
                float outside = TerrainMeta.Size.x * MAP_SIZE_SCALE;
                float x = UnityEngine.Random.Range(-outside, outside);
                float y = UnityEngine.Random.Range(-outside, outside);
                if (UnityEngine.Random.Range(0, 2) == 1) {
                    x = outside * Mathf.Sign(x);
                }
                else {
                    y = outside * Mathf.Sign(y);
                }
                return new Vector3(x, height, y);
            }
            public void SetFinalDestination() {
                finalDestination = GetDespawnPosition(GetCurrentNode());
                leave = true;
            }
            public Vector3 GetCurrentTarget(Vector3 currentPosition) {
                if (leave) {
                    return finalDestination;
                }
                if (targetNodeIndex < 0) {
                    targetNodeIndex = GetClosestNode(currentPosition);
                }
                Vector3 currentTarget = GetCurrentNode();
                if ((currentPosition - currentTarget).sqrMagnitude < sqrVisitDistance) {
                    targetNodeIndex += skip;
                }
                ValidateNodeIndex();
                return GetCurrentNode();
            }
            public void Reverse() {
                skip *= -1;
                targetNodeIndex += skip;
                ValidateNodeIndex();
            }
            private void ValidateNodeIndex() {
                if (targetNodeIndex >= nodes.Count) {
                    targetNodeIndex = 0;
                }
                else if (targetNodeIndex < 0) {
                    targetNodeIndex = nodes.Count - 1;
                }
            }
            private Vector3 GetDespawnPosition(Vector3 lastNode, float height = 0) {
                lastNode.y = 0;
                lastNode.Normalize();
                lastNode *= TerrainMeta.Size.x;
                lastNode.y = height;
                return lastNode;
            }
            private int GetClosestNode(Vector3 position) {
                int result = 0;
                float num = float.PositiveInfinity;
                for (int i = 0; i < nodes.Count; i++) {
                    Vector3 b = nodes[i];
                    float num2 = Vector3.Distance(position, b);
                    if (num2 < num) {
                        result = i;
                        num = num2;
                    }
                }
                return result;
            }
            public static void GeneratePatrolPath(bool force = false) {
                if (TerrainMeta.Path.OceanPatrolClose == null || TerrainMeta.Path.OceanPatrolClose.Count < 1 || force) {
                    var sw = new Stopwatch();
                    Cwarn("Generating tugboat patrol path (expect some lag)");
                    sw.Start();
                    TerrainMeta.Path.OceanPatrolClose = BaseBoat.GenerateOceanPatrolPath(NODE_TOLERANCE * 1.1f);
                    sw.Stop();
                    Cprint($"Patrol path generated in {sw.Elapsed.TotalSeconds:N2}s");
                }
            }
        }
#endregion
#region Functions
        private void ScheduleEvent(float delayMpl = 1f) {
            if (_config.eventDelay < 1) {
                return;
            }
            Cprint($"Schedule next event in {(_config.eventDelay * delayMpl):N0} min");
            if (nextEventTimer != null && !nextEventTimer.Destroyed) {
                nextEventTimer.Destroy();
            }
            nextEventTimer = timer.In(_config.eventDelay * 60 * delayMpl, () => StartEvent(null));
        }
        private void StartEvent(BasePlayer player, bool force = false) {
            if (!EndEvent(force)) {
                player?.ChatMessage("Failed to start event, another event is still running");
                Cprint("Failed to start event, another event is still running");
                return;
            }
            var pathFinder = new TugboatPathFinder(TerrainMeta.Path.OceanPatrolClose, NODE_TOLERANCE);
#if DEBUG
            startPlayer = player;
            Vector3 startPosition = startPlayer?.transform.position ?? pathFinder.GetRandomStartPosition();
#else
            Vector3 startPosition = pathFinder.GetRandomStartPosition();
#endif
            Subscribe();
            var rotation = Quaternion.LookRotation(startPosition - pathFinder.GetCurrentTarget(startPosition)) * Quaternion.Euler(Vector3.up * 180);
            var boat = GameManager.server.CreateEntity("assets/content/vehicles/boats/tugboat/tugboat.prefab", startPosition, rotation) as Tugboat;
            if (boat == null) {
                Cerr("Failed to spawn boat - boat is null");
                Unsubscribe();
                return;
            }
            boat.Spawn();
            boat.EnableGlobalBroadcast(_config.boatGlobalBroadcast);
            boat.EnableSaving(false);
            // Prevent OnEntityTakeDamage hook conflict with NpcSpawn
            boat.skinID = 14922524;
            currentBoat = boat.gameObject.AddComponent<TugboatController>();
            currentBoat.PathFinder = pathFinder;
            currentBoat.Spawn(_config.boatConfig);
        }
        private bool EndEvent(bool force) {
            startPlayer = null;
            if (currentBoat == null) {
                return true;
            }
            if (currentBoat.State == EventState.ENDED || force) {
                currentBoat?.Destroy();
                return true;
            }
            return false;
        }
#endregion
#region Boat Controller
        public enum EventState {
            PREPARING = 1, RUNNING = 2, CAPTAIN_DEAD = 4, LOOTED = 8, LEAVING = 16, ENDED = 32,
        }
        private class TugboatController : FacepunchBehaviour {
            public EventState State {
                get;
                private set;
            }
            private VendingMachineMapMarker vendingMarker;
            private MapMarkerGenericRadius colorMarker;
            public readonly HashSet<ulong> npcs = new HashSet<ulong>();
            private readonly List<BaseEntity> entities = new List<BaseEntity>();
            public readonly HashSet<HackableLockedCrate> crates = new HashSet<HackableLockedCrate>();
            public Tugboat Boat {
                get;
                private set;
            }
            private BasePlayer captain;
            public TugboatPathFinder PathFinder {
                get;
                set;
            }
            public float speedMultiplier;
            public bool useMarker;
            public string markerColor;
            public string markerName;
            private bool boatDestroyed;
            private bool captainDead;
            private Vector3 target;
            private float turnScale;
            private float currentTurnSpeed;
            private float currentThrottle;
            private Vector3 currentVelocity;
            private string lockCode;
            private int timeRemaining;
            public Zone Zone {
                get;
                private set;
            }
            private bool calledEnd;
#region Mono
            void Awake() {
                enabled = false;
                State = EventState.PREPARING;
                Boat = GetComponent<Tugboat>();
                if (Boat == null) {
                    Cerr("Tugboat is null");
                    Destroy(this);
                    return;
                }
                Boat.GetTriggerParent().ParentNPCPlayers = true;
                InvokeRepeating(NetworkUpdate, 1f, 5f);
#if DEBUG
                InvokeRepeating(DebugPath, 1f, 1f);
#endif
                CancelInvoke(Boat.BoatDecay);
                CreateZone();
            }
            void Update() {
                if (Boat.IsDying && !boatDestroyed) {
                    Dprint("ship destroyed");
                    boatDestroyed = true;
                    if (_config.announceChat) {
                        BroadcastLang("ship_destroyed");
                    }
                    StartSinking();
                }
                if (captain != null && captain.IsDead() && !captainDead && !boatDestroyed) {
                    Dprint("captain is dead");
                    captainDead = true;
                    OnCaptainKilled();
                }
            }
            void FixedUpdate() {
                if (!boatDestroyed && !Boat.EngineOn()) {
                    Boat.SetFlag(BaseEntity.Flags.Reserved1, true);
                }
                if ((!captainDead && !boatDestroyed) || currentThrottle > 0.001f) {
                    UpdateMovement();
                }
            }
            void OnDestroy() {
                try {
                    EndEvent();
                    StopAllCoroutines();
                    CancelInvoke();
                    Boat.DismountAllPlayers();
                    Zone.Destroy();
                    foreach (var ent in entities) {
                        if (ent != null && !ent.IsDestroyed) {
                            ent.Kill();
                        }
                    }
                    if (!Boat.IsDestroyed) {
                        Boat.Kill();
                    }
                }
                catch(Exception ex) {
                    Cerr("Error in OnDestroy " + ex.Message);
                }
            }
            void NetworkUpdate() {
                foreach(var ent in crates) {
                    ent.SendNetworkUpdate();
                }
            }
            public void Destroy() {
                if (Boat?.IsDestroyed == false) {
                    Boat.Kill(BaseNetworkable.DestroyMode.Gib);
                }
                else {
                    Destroy(this);
                }
            }
            void StartSinking() {
                Dprint("start sinking");
                EndEvent();
                if (!Boat.IsDying) {
                    Boat.SetFlag(BaseEntity.Flags.Broken, b : true);
                    Boat.repair.enabled = false;
                    boatDestroyed = true;
                }
                CancelInvoke(WaterCheck);
                Invoke(WaterCheck, 10f);
                CancelInvoke(Destroy);
                Invoke(Destroy, _config.destroyTime);
            }
            void WaterCheck() {
                Dprint("Water check");
                Boat.SetFlag(BaseEntity.Flags.Reserved1, false);
                foreach (var ent in entities.ToArray()) {
                    var npc = ent as ScientistNPC;
                    if (npc != null) {
                        Dprint($"Water factor {npc.WaterFactor():N2}");
                        if (npc.WaterFactor() > 0.8f) {
                            npc.Invoke(() => npc.Die(), UnityEngine.Random.Range(0.1f, 2f));
                            entities.Remove(ent);
                        }
                    }
                }
            }
#endregion
#region Event
            private void OnCaptainKilled() {
                if (_config.announceChat) {
                    BroadcastLang("captain_killed", _config.timeBeforeSinkingCaptain.ToString());
                }
                State = EventState.CAPTAIN_DEAD;
                timeRemaining = _config.timeBeforeSinkingCaptain;
            }
            public void OnCrateLooted(HackableLockedCrate crate) {
                if (State == EventState.LOOTED) {
                    return;
                }
                if (crates.Contains(crate)) {
                    crates.Remove(crate);
                }
                if (crates.Count < 1) {
                    if (_config.announceChat) {
                        BroadcastLang("ship_looted", _config.timeBeforeSinkingLooted.ToString());
                    }
                    State = EventState.LOOTED;
                    timeRemaining = _config.timeBeforeSinkingLooted;
                }
            }
            private void StartEvent() {
                State = EventState.RUNNING;
                enabled = true;
                timeRemaining = _config.eventDuration;
                StartCoroutine(EventUpdateCoro());
                Dprint("OnTugboatPiratesStarted");
                Interface.CallHook("OnTugboatPiratesStarted");
            }
            private void EndEvent() {
                if (calledEnd) {
                    return;
                }
                calledEnd = true;
                State = EventState.ENDED;
                foreach (var player in Zone.Players) {
                    DestroyGui(player);
                }
                Dprint("OnTugboatPiratesEnded");
                Interface.CallHook("OnTugboatPiratesEnded");
            }
            private IEnumerator EventUpdateCoro() {
                bool announcedStart = false;
                bool announcedLeave5m = false;
                bool announcedLeave = false;
                while (timeRemaining > 0 && State != EventState.ENDED) {
                    if (State == EventState.RUNNING) {
                        if (!announcedStart) {
                            announcedStart = true;
                            if (_config.announceChat) {
                                BroadcastLang("ship_start", PhoneController.PositionToGridCoord(PathFinder.GetCurrentNode()));
                            }
                            if (_config.notifyEnabled) {
                                BroadcastNotify("ship_start", PhoneController.PositionToGridCoord(PathFinder.GetCurrentNode()));
                            }
                            if (_config.showToast) {
                                ShowToastLang("ship_start_toast");
                            }
                        }
                        if (!announcedLeave5m && timeRemaining <= 300 + _config.leaveTime) {
                            announcedLeave5m = true;
                            if (_config.announceChat) {
                                BroadcastLang("ship_leave_5m");
                            }
                            if (_config.notifyEnabled) {
                                BroadcastNotify("ship_leave_5m");
                            }
                        }
                        if (!announcedLeave && timeRemaining <= _config.leaveTime) {
                            announcedLeave = true;
                            if (_config.announceChat) {
                                BroadcastLang("ship_leave");
                            }
                            if (_config.notifyEnabled) {
                                BroadcastNotify("ship_leave");
                            }
                            State = EventState.LEAVING;
                            speedMultiplier *= 1.6f;
                            PathFinder.SetFinalDestination();
                        }
                    }
                    UpdateGui();
                    yield return new WaitForSecondsRealtime(1f);
                    timeRemaining--;
                }
                if (State != EventState.ENDED) {
                    StartSinking();
                }
            }
#endregion
#region Coroutines
            public void Spawn(BoatConfig config) {
                if (PathFinder == null) {
                    Cerr("Cannot spawn tugboat - path finder is null");
                    Destroy();
                    return;
                }
                Cprint($"Spawning event at {Boat.transform.position}");
                lockCode = UnityEngine.Random.Range(1111, 10000).ToString();
                StartCoroutine(SpawnCoro(config));
            }
            private IEnumerator SpawnCoro(BoatConfig config) {
                speedMultiplier = config.speedMultiplier;
                useMarker = config.useMapMarker;
                markerColor = config.markerColor;
                markerName = config.markerName;
                CreateMapMarker();
                yield return new WaitForEndOfFrame();
                SpawnCaptain(_instance.GetNpcConfig(config.captainNpcProfile));
                yield return new WaitForEndOfFr
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху