C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Network;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Rust;
using UnityEngine;
using Random = Oxide.Core.Random;
namespace Oxide.Plugins {
[Info("XDQuest", "https://topplugin.ru/ / https://discord.com/invite/5DPTsRmd3G", "4.3.3")]
[Description("Расширенная квест система для вашего сервера!")]
public class XDQuest : RustPlugin {
/// <summary>
/// Исправлен тип EntityKill(уничтожить) полностью переписан
/// Оптимизирован тип Deploy (Поставить что либо)
/// Добавлены 3 типа задания
/// Поправил формат времени в гуи
/// Исправлен конфиг
/// Переписана дата
/// Исправлен lang
/// Очень большая оптимизация
/// Исправлены многочисленные ошибки
/// Теперь предметы со скин ид будут забираться нормально!
/// -----------UPD 4.2.3
/// Fix nre
/// Fix Loot hack crate
/// Fix Quest type recycle
/// Fix Quest type swipe card
/// -----------UPD 4.3.0
/// Fix clans api
/// Fix quest type EntityKill (Now you can specify not only `scientist` but also` scientistnpc_oilrig` and others)
/// Minor fixes
/// </summary>
#region ReferencePlugins
[PluginReference]
Plugin CopyPaste, ImageLibrary, IQChat, Friends, Clans, Battles, Duel;
private void SendChat(BasePlayer player, string Message, ConVar.Chat.ChatChannel channel = ConVar.Chat.ChatChannel.Global) {
if (IQChat) IQChat?.Call("API_ALERT_PLAYER", player, Message, config.settingsIQChat.prifix, config.settingsIQChat.SteamID);
else player.SendConsoleCommand("chat.add", channel, 0, Message);
}
private bool IsFriends(ulong userID, ulong targetID) {
if (Friends) return (bool)Friends?.Call("HasFriend", userID, targetID);
else return false;
}
public bool IsClans(string userID, string targetID) {
if(Clans) {
var result = Clans.Call("HasFriend", userID, targetID);
return result is bool && (bool)result;
}
return false;
}
private bool IsDuel(ulong userID) {
if (Battles) return (bool)Battles?.Call("IsPlayerOnBattle", userID);
else if (Duel) return (bool)Duel?.Call("IsPlayerOnActiveDuel", BasePlayer.FindByID(userID));
else return false;
}
private string GetImage(string shortname, ulong skin = 0) => (string)ImageLibrary?.Call("GetImage", shortname, skin);
private bool AddImage(string url, string shortname, ulong skin = 0) => (bool)ImageLibrary?.Call("AddImage", url, shortname, skin);
private void SendImage(BasePlayer player, string imageName, ulong imageId = 0) => ImageLibrary?.Call("SendImage", player, imageName, imageId);
#endregion
#region Variables
private const string AuthorContact = "https://vk.com/rustnastroika";
private static XDQuest Instance;
private static String Key = "4362";
MonumentInfo monument;
private BasePlayer npc;
private List<BaseEntity> HouseNPC = new List<BaseEntity>();
private static Dictionary<int, Quest> QuestList = new Dictionary<int, Quest>();
private SafeZone safeZone = null;
private static Dictionary<ulong, PlayerData> playersInfo = new Dictionary<ulong, PlayerData>();
private class PlayerData {
public List<string> PlayerQuestsFinish = new List<string>();
public Dictionary<int, PlayerQuest> PlayerQuestsAll = new Dictionary<int, PlayerQuest>();
public Dictionary<string, double> PlayerQuestsCooldown = new Dictionary<string, double>();
}
private static Dictionary<string, string> ImageUI = new Dictionary<string, string>() {
{
"MAINFON", "https://ic.wampi.ru/2021/07/23/0000.png"
}
, {
"QUESTFON", "https://ic.wampi.ru/2021/07/23/1111.png"
}
, {
"DOWNBTN", "https://ic.wampi.ru/2021/07/23/222.png"
}
, {
"UPBTN", "https://ic.wampi.ru/2021/07/23/333.png"
}
, {
"BluePrint", "https://i.imgur.com/4GYca0x.png"
}
, {
"InProcces", "https://ic.wampi.ru/2021/07/23/555.png"
}
, {
"CloseUI", "https://ic.wampi.ru/2021/07/23/66669d36b2c8aa944c1f.png"
}
}
;
#endregion
#region Lang
protected override void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string, string> {
["QUEST_ACTIVE"]
= "Your active tasks: {0}", ["NOT_QUEST_ACTIVE"] = "You have no active tasks", ["QUEST_Insufficient_resources"] = "You are missing in your inventory what you needed to collect for this task!", ["QUEST_not_resources"] = "You don't have enough {0}, you need {1}", ["QUEST_ACTIVE_COMPLITE"] = "{0}\nQuests completed!\nDo not forget to hand it over to Sidorovich.", ["NOT_QUEST_ACTIVE_COMPLITE"] = "<size=9>{0}</size>\nLeft: {1}\n{2}", ["AVAILABLE_MISSIONS"] = "<b>AVAILABLE JOBS</b><size=14>({0})</size>", ["ACTIVE_MISSIONS"] = "<b>ACTIVE JOBS</B><size=14>({0})</size>", ["NOT_AVAILABLE_MISSIONS"] = "You have no tasks available :(", ["REWARD_FOR_QUESTIONS"] = "<b>REWARD FOR COMPLETING THE MISSION</b>", ["CAN"] = "can", ["CAN'T"] = "can't", ["absent's"] = "absent", ["QUEST_ACTIVE_LIMIT"] = "You have too much <color=#4286f4>unfinished</color> assignments!", ["QUEST_ACTIVE_COLDOWN"] = "You cannot take this quest at the moment", ["QUEST_took_tasks"] = "You already <color=#4286f4>have taken</color> this task!", ["QUEST_completed_tasks"] = "You already <color=#4286f4>performed</color> this task!", ["QUEST_completed_took"] = "You <color=#4286f4>successfully</color> took the task {0}", ["QUEST_tasks_completed"] = "Thanks, keep your <color=#4286f4>reward</color>!", ["QUEST_no_place"] = "Hey wait, you're everything <color=#4286f4>you won't take</color>, make room!", ["QUEST_did_not_cope"] = "Sorry that you <color=#4286f4>did not cope</color> with the task!\n" + $"Anyway, you can try again!", ["QUEST_done"] = "Performed!", ["QUEST_take"] = "TAKE", ["QUEST_turn"] = "Hand over", ["QUEST_REFUSE"] = "REFUSE", ["QUEST_Finished"] = "You have completed the task: <color=#4286f4>{0}</color>", ["QUEST_DONTREPEAT"] = "You have already completed this quest.!", ["QUEST_target"] = "Need to: {0}\n" + "Re-take {1}\n" + "CD to re-take : {2}", ["QUEST_targetrtho"] = "Need to: {0}\n" + "Progress: {1}/{2}\n" + "Re-take {3}\n" + "CD to re-take : {4}", ["QUEST_errorBuilding"] = "Error #1 \nThe plugin will not work, please contact the developer https://vk.com/rustnastroika", ["QUEST_errorOutPost"] = "You do not have an outpost, you can use a custom position for building", ["QUEST_errorSoundLoad"] = "Error loading sounds! The plugin will be reloaded", ["QUEST_errorSoundLoadFatal"] = "Error loading sounds!\ncontact the developer https://vk.com/rustnastroika", ["QUEST_SoundLoad"] = "Loading sounds for NPCs...", ["QUEST_SoundPlayError"] = "Sound error! Try to restart the plugin by deleting the files {0}\nin the date or contact the developer https://vk.com/rustnastroika", ["QUEST_BuildingLoad"] = "The construction file was not found!\nstarting to import...", ["QUEST_BuildingLoadError"] = "Error loading the building!\nlet's try to download it again", ["QUEST_QuestListError"] = "You are missing a file with tasks!! Create it on the website - https://xdquest.skyplugins.ru/ or use the standard!", ["QUEST_BuildingPasteSucc"] = "The building has been successfully spawned!", ["QUEST_BuildingPasteError"] = "Error loading the building! The details are in the log file!!\ncontact the developer + https://vk.com/rustnastroika", ["QUEST_CustomPosAdded"] = "You have successfully added a custom build position.\n(You need to reload the plugin)\nYou can turn it in the config!\nSo don’t forget to enable the option to spawn a building at a custom position in the config.", ["QUEST_KeyAuth"] = "if there are any problems write to us in the group\nhttps://vk.com/rustnastroika", ["QUEST_FileNotFound"] = "The build file was not found on the server.Contact the developer\nhttps://vk.com/rustnastroika", ["QUEST_ServerNotResponse"] = "The server does not respond.Try reloading the plugin or contact the developer\nhttps://vk.com/rustnastroika",
}
, this);
lang.RegisterMessages(new Dictionary<string, string> {
["QUEST_ACTIVE"]
= "Ваши активные задачи: {0}", ["NOT_QUEST_ACTIVE"] = "У вас нет активных задач", ["QUEST_Insufficient_resources"] = "У вас отсутствует в инвентаре то что вам нужно было собрать для этого задания!", ["QUEST_not_resources"] = "У вас не достаточно {0}, нужно {1}", ["QUEST_ACTIVE_COMPLITE"] = "{0}\nЗадания выполнено!\nНе забудьте сдать его сидоровичу.", ["NOT_QUEST_ACTIVE_COMPLITE"] = "<size=9>{0}</size>\nПрогресс: {1}/{2}\n{3}", ["AVAILABLE_MISSIONS"] = "<b>ДОСТУПНЫЕ ЗАДАНИЯ</b><size=14>({0})</size>", ["ACTIVE_MISSIONS"] = "<b>АКТИВНЫЕ ЗАДАНИЯ</B><size=14>({0})</size>", ["NOT_AVAILABLE_MISSIONS"] = "У вас нет доступных задач :(", ["REWARD_FOR_QUESTIONS"] = "<b>НАГРАДА ЗА ВЫПОЛНЕНИЯ ЗАДАНИЯ</b>", ["CAN"] = "можно", ["CAN'T"] = "нельзя", ["absent's"] = "отсутствует", ["QUEST_ACTIVE_LIMIT"] = "У тебя слишком много <color=#4286f4>не законченных</color> заданий!", ["QUEST_ACTIVE_COLDOWN"] = "В данный момент вы не можете взять этот квест", ["QUEST_took_tasks"] = "Вы уже <color=#4286f4>взяли</color> это задание!", ["QUEST_completed_tasks"] = "Вы уже <color=#4286f4>выполняли</color> это задание!", ["QUEST_completed_took"] = "Вы <color=#4286f4>успешно</color> взяли задание {0}", ["QUEST_tasks_completed"] = "Спасибо, держи свою <color=#4286f4>награду</color>!", ["QUEST_no_place"] = "Эй, погоди, ты всё <color=#4286f4>не унесёшь</color>, освободи место!", ["QUEST_did_not_cope"] = "Жаль что ты <color=#4286f4>не справился</color> с заданием!\n" + $"В любом случае, ты можешь попробовать ещё раз!", ["QUEST_done"] = "Выполнено!", ["QUEST_take"] = "ВЗЯТЬ", ["QUEST_turn"] = "СДАТЬ", ["QUEST_REFUSE"] = "ОТКАЗАТЬСЯ", ["QUEST_Finished"] = "Вы закончили задание: <color=#4286f4>{0}</color>", ["QUEST_DONTREPEAT"] = "Вы уже выполняли этот квест!", ["QUEST_target"] = "Нужно: {0}\n" + "Повторно брать {1}\n" + "Кд на повторное взятие: {2}", ["QUEST_targetrtho"] = "Нужно: {0}\n" + "Прогресс: {1}/{2}\n" + "Повторно брать {3}\n" + "Кд на повторное взятие: {4}", ["QUEST_errorBuilding"] = "Ошибка #1 \nПлагин не будет работать, Обратитесь к разработчику https://vk.com/rustnastroika", ["QUEST_errorOutPost"] = "У вас отсутствует outpost, вы можете использовать кастомную позицию для постройки", ["QUEST_errorSoundLoad"] = "Ошибка при загрузке звуков! Плагин будет перезагружен", ["QUEST_errorSoundLoadFatal"] = "Ошибка при загрузке звуков!\nОбратитесь к разработчику https://vk.com/rustnastroika", ["QUEST_SoundLoad"] = "Загрузка звуков для NPC...", ["QUEST_SoundPlayError"] = "Ошибка звуков! Попробуйте перезагрузить плагин удалив в дате файли {0}\nИли свяжитесь с разработчиком https://vk.com/rustnastroika", ["QUEST_BuildingLoad"] = "Файл постройки не найден!\nНачинаем импортировать...", ["QUEST_BuildingLoadError"] = "Ошибка при загрузке постройки!\nПробуем загрузить еще раз", ["QUEST_QuestListError"] = "У вас отсутсвует файл с заданиями!! Создайте его на сайте - https://xdquest.skyplugins.ru/ или используйте стандартный!", ["QUEST_BuildingPasteSucc"] = "Постройка успешно заспавнена!", ["QUEST_BuildingPasteError"] = "Ошибка при загрузке постройки! Подробности в лог файле!!\nОбратитесь к разработчику + https://vk.com/rustnastroika", ["QUEST_CustomPosAdded"] = "Вы успешно добавили кастомную позицию для постройки.\n(Вам нужно перезагрузить плагин)\nПовернуть ее можно в конфиге!\nТак же не забудъте включить в конфиге возможность спавнить постройку на кастомной позиции", ["QUEST_KeyAuth"] = "если возникнут какие-либо проблемы, напишите нам в группу\nhttps://vk.com/rustnastroika", ["QUEST_FileNotFound"] = "Файл постройки не найден на сервере.Свяжитесь с разработчиком\nhttps://vk.com/rustnastroika", ["QUEST_ServerNotResponse"] = "Сервер не отдает ответ.Попробуйте перезагрузить плагин или свяжитесь с разработчиком\nhttps://vk.com/rustnastroika",
}
, this, "ru");
}
#endregion
#region Configuration
private Configuration config;
private class Configuration {
public class itemsNpc {
[JsonProperty("ShortName")]
public String ShortName;
[JsonProperty("SkinId")]
public ulong SkinId;
}
public class Settings {
[JsonProperty("Максимальное колличевство единовременно взятых квестов | The maximum number of simultaneously taken quests")]
public Int32 questCount = 3;
[JsonProperty("Голосовое оповещение при выполнении задания | Voice notification when completing a task")]
public Boolean SoundEffect = true;
[JsonProperty("Эфект | Effect")]
public String Effect = "assets/prefabs/locks/keypad/effects/lock.code.lock.prefab";
[JsonProperty("Названия файла с квестами | Names of the file with quests")]
public String questListDataName = "Quest";
[JsonProperty("Названия файла с Аудио для NPC(Не менять!!!) | File names with Audio for NPC (Do not change!!!)")]
public String audioDataPath = "RUAudio";
//ENAudio/RUAudio
[JsonProperty("Команда для открытия квест листа с прогрессом | The command to open a quest list with progress")]
public String questListProgress = "qlist";
[JsonProperty("Радиус безопасной зоны (Как в городе нпс) | Safe Zone Radius (As in NPC City)")]
public float saveZoneRadius = 25;
[JsonProperty("Включить ли радио у нпс в здании ? | Should the radio be turned on for the NPCs in the building?")]
public Boolean useRadio = true;
[JsonProperty("Растояния обнаружения игрока для разговора | Player detection ranges for conversation")]
public float bpcdistanate = 4.7f;
[JsonProperty("Использовать метку на внутриигровой карте ? (Требуется https://umod.org/plugins/marker-manager) | Use a placemark on an in-game map ? (Required https://umod.org/plugins/marker-manager)")]
public Boolean mapUse = false;
[JsonProperty("Имя метки на карте | Name of the placemark on the map")]
public String nameMarkerMap = "QUEST ROOM";
[JsonProperty("Цвет маркера (без #) | Marker color (without #)")]
public String colorMarker = "f3ecad";
[JsonProperty("Цвет обводки (без #) | Outline color (without #)")]
public String colorOutline = "ff3535";
}
public class CustomPosition {
[JsonProperty("Использовать кастомную позицию постройки ? | Use a custom construction position ?")]
public Boolean useCustomPos = false;
[JsonProperty("Позиция постройки | Construction position")]
public Vector3 pos = Vector3.zero;
[JsonProperty("Поворот постройки (Этим параметром вы можете повернуть постройку) | Rotate the building (You can use this parameter to rotate the building)")]
public Int32 rotation = 0;
}
public class SettingsNpc {
[JsonProperty("Имя нпс | NPC name")]
public String Name = "Сидорович\n";
[JsonProperty("id npc (От его ид зависит его внешность) | npc id (His appearance depends on his ID)")]
public ulong userId = 21;
[JsonProperty("Одежда нпс | NPC Clothing")]
public List<itemsNpc> Wear = new List<itemsNpc>();
}
public class SettingsIQChat {
[JsonProperty("Префикс в чате | Prefix in the chat")]
public String prifix = "Сидорович:";
[JsonProperty("SteamID - Для аватарки из профиля стим | SteamID - For the avatar from the steam profile")]
public String SteamID = "21";
}
[JsonProperty("Настройка кастомной позиции постройки | Setting up a custom building position")]
public CustomPosition customPosition = new CustomPosition();
[JsonProperty("Настройки NPC | NPC Settings")]
public SettingsNpc settingsNpc = new SettingsNpc();
[JsonProperty("Настройки | Settings")]
public Settings settings = new Settings();
[JsonProperty("Настройки IQChat (Если есть) | Ichat Settings (If any)")]
public SettingsIQChat settingsIQChat = new SettingsIQChat();
}
protected override void LoadConfig() {
base.LoadConfig();
try {
config = Config.ReadObject<Configuration>();
if (config == null) throw new Exception();
SaveConfig();
}
catch {
for (var i = 0; i < 3; i++) PrintError("Configuration file is corrupt! Check your config file at https://jsonlint.com/");
LoadDefaultConfig();
}
path = "XDQuest/" + config.settings.audioDataPath;
ValidateConfig();
SaveConfig();
}
private void ValidateConfig() {
if (config.settingsNpc.Wear.Count == 0) {
config.settingsNpc.Wear = new List<Configuration.itemsNpc> {
new Configuration.itemsNpc {
ShortName = "pants", SkinId = 960252273,
}
, new Configuration.itemsNpc {
ShortName = "hoodie", SkinId = 959641236,
}
, new Configuration.itemsNpc {
ShortName = "shoes.boots", SkinId = 962503020,
}
}
;
}
}
protected override void SaveConfig() {
Config.WriteObject(config);
}
protected override void LoadDefaultConfig() {
config = new Configuration();
}
#endregion
#region QuestData
private class PlayerQuest {
public Quest parentQuest;
public ulong UserID;
public bool Finished;
public int Count;
public void AddCount(int amount = 1) {
Count += amount;
BasePlayer player = BasePlayer.FindByID(UserID);
if (parentQuest.Amount <= Count) {
if (player != null && player.IsConnected) {
if (Instance.config.settings.SoundEffect) Instance.RunEffect(player, Instance.config.settings.Effect);
Instance.SendChat(player, Instance.GetLang("QUEST_Finished", player.UserIDString, parentQuest.DisplayName));
Interface.CallHook("QuestCompleted", player, Instance.GetLang("QUEST_Finished", player.UserIDString, parentQuest.DisplayName));
}
Finished = true;
}
if (Instance.openQuestPlayers.Contains(UserID)) {
CuiHelper.DestroyUi(player, QuestListLAYER);
Instance.UI_QuestList(player);
}
}
public int LeftAmount() => parentQuest.Amount - Count;
}
public enum QuestType {
IQPlagueSkill, IQHeadReward, IQCases, OreBonus, XDChinookIvent, Gather, EntityKill, Craft, Research, Loot, Grade, Swipe, Deploy, PurchaseFromNpc, HackCrate, RecycleItem
}
public enum PrizeType {
Item, BluePrint, CustomItem, Command
}
private class Quest {
internal class Prize {
public string nameprize;
public PrizeType type;
public string ShortName;
public int Amount;
public string Name;
public ulong SkinID;
public string Command;
public string Url;
}
public string DisplayName;
public string Description;
public string Missions;
public QuestType QuestType;
public string Target;
public int Amount;
public bool UseRepeat;
public int Cooldown;
public List<Prize> PrizeList = new List<Prize>();
}
#endregion
#region MetodsBuildingAndNpc
private class Building {
public string name;
public float Deg2Rad;
public Vector3 pos;
}
private Dictionary<string, Building> BuildingList = new Dictionary<string, Building> {
["1"]
= new Building {
name = "QuestHouse", Deg2Rad = 5.38f, pos = new Vector3(-6.76f, 1.73f, 46.63f)
}
,
}
;
void GenerateBuilding() {
var options = new List<string> {
"stability", "true", "deployables", "true", "autoheight", "false", "entityowner", "true"
}
;
Vector3 resultVector = GetResultVector();
var success = CopyPaste.Call("TryPasteFromVector3", resultVector, config.customPosition.useCustomPos ? (Vector3.zero * Mathf.Deg2Rad).y - config.customPosition.rotation : (monument.transform.rotation.eulerAngles * Mathf.Deg2Rad).y - BuildingList["1"].Deg2Rad, BuildingList["1"].name, options.ToArray());
if (success is string) {
PrintWarning(lang.GetMessage("QUEST_errorBuilding", this));
return;
}
if (config.settings.mapUse) Interface.CallHook("API_CreateMarker", resultVector, "xdquest", 0, 3f, 0.2f, config.settings.nameMarkerMap, config.settings.colorMarker, config.settings.colorOutline);
if (config.customPosition.useCustomPos) {
safeZone = new GameObject().AddComponent<SafeZone>();
safeZone.Activate(resultVector, config.settings.saveZoneRadius);
}
timer.Once(5f, () => {
InitializeNPC(resultVector);
}
);
}
public void InitializeNPC(Vector3 pos) {
npc = GameManager.server.CreateEntity("assets/prefabs/player/player.prefab", pos) as BasePlayer;
if (npc == null) {
Interface.Oxide.LogError($"Initializing NPC failed! NPC Component == null #3");
return;
}
npc.userID = config.settingsNpc.userId;
npc.name = config.settingsNpc.Name;
npc.displayName = npc.name;
npc.Spawn();
npc.SendNetworkUpdate();
List<ComputerStation> chairs = new List<ComputerStation>();
Vis.Entities(npc.transform.position, 2f, chairs);
foreach (var chair in chairs.Distinct().ToList()) {
chair.MountPlayer(npc);
chair.SetFlag(BaseEntity.Flags.Busy, true);
break;
}
ZoneTrigger zone = new GameObject().AddComponent<ZoneTrigger>();
zone.Activate(pos, config.settings.bpcdistanate);
#region NpcWearStart
if (config.settingsNpc.Wear.Count > 0) for (int i = 0; i < config.settingsNpc.Wear.Count; i++) ItemManager.Create(ItemManager.FindItemDefinition(config.settingsNpc.Wear[i].ShortName), 1, config.settingsNpc.Wear[i].SkinId).MoveToContainer(npc.inventory.containerWear);
#endregion
}
private void ClearEnt() {
IEnumerable<BasePlayer> findplayer = FindMyBot(config.settingsNpc.userId);
foreach (var player in findplayer) if (player != null) player.KillMessage();
List<BaseEntity> obj = new List<BaseEntity>();
Vis.Entities(GetResultVector(), 10f, obj, LayerMask.GetMask("Construction", "Deployable", "Deployed", "Debris"));
foreach (BaseEntity item in obj.Where(x => x.OwnerID == 76561198283599982)) {
if (item == null) continue;
item.Kill();
}
timer.Once(5f, () => {
GenerateBuilding();
}
);
}
void OnPasteFinished(List<BaseEntity> pastedEntities, string fileName) {
if (fileName.ToLower() != BuildingList["1"].name.ToLower()) return;
try {
HouseNPC.AddRange(pastedEntities);
foreach (BaseEntity item in HouseNPC) {
if (item is DecayEntity) {
var decayEntety = item as DecayEntity;
decayEntety.decayVariance = 0;
decayEntety.decay = null;
}
if (item is DeployableBoomBox) {
if (config.settings.useRadio) {
var boomBox = item as DeployableBoomBox;
boomBox.BoxController.CurrentRadioIp = "http://radio.skyplugins.ru:8020/stalker.mp3";
boomBox.BoxController.baseEntity.ClientRPC(null, "OnRadioIPChanged", boomBox.BoxController.CurrentRadioIp);
boomBox.BoxController.ServerTogglePlay(true);
boomBox.BoxController.ConditionLossRate = 0;
}
}
if (item as BuildingBlock) {
var build = item as BuildingBlock;
build.SetFlag(BaseEntity.Flags.Reserved1, false);
build.SetFlag(BaseEntity.Flags.Reserved2, false);
}
if (item is BaseChair) continue;
if (item.prefabID == 1560881570) {
var box = item as BaseCombatEntity;
box.pickup.enabled = false;
var groundWatch = item?.GetComponent<GroundWatch>();
var groundMissing = item?.GetComponent<DestroyOnGroundMissing>();
if (groundMissing != null) UnityEngine.Object.Destroy(groundMissing);
if (groundWatch != null) UnityEngine.Object.Destroy(groundWatch);
continue;
}
if (item is Door) {
var ent = item as Door;
if (ent == null) continue;
ent.pickup.enabled = false;
ent.canTakeLock = false;
ent.canTakeCloser = false;
}
item.SetFlag(BaseEntity.Flags.Busy, true);
item.SetFlag(BaseEntity.Flags.Locked, true);
}
PrintWarning(lang.GetMessage("QUEST_BuildingPasteSucc", this));
}
catch (Exception ex) {
PrintError(lang.GetMessage("QUEST_BuildingPasteError", this));
Log(ex.InnerException.Message, "LogError");
}
}
public class SafeZone : MonoBehaviour {
private Vector3 Position;
private float Radius;
private void Awake() {
gameObject.layer = (int)Layer.Reserved1;
gameObject.name = "NpcZonesOrRadiation";
var rigidbody = gameObject.AddComponent<Rigidbody>();
rigidbody.useGravity = false;
rigidbody.isKinematic = true;
}
public void Activate(Vector3 pos, float radius) {
Position = pos;
Radius = radius;
transform.position = Position;
UpdateCollider();
gameObject.SetActive(true);
enabled = true;
var safeZone = gameObject.GetComponent<TriggerSafeZone>();
safeZone = safeZone ?? gameObject.AddComponent<TriggerSafeZone>();
safeZone.interestLayers = LayerMask.GetMask("Player (Server)");
safeZone.enabled = true;
}
private void OnDestroy() {
Destroy(gameObject);
}
public void Kill() {
Destroy(gameObject);
}
private void UpdateCollider() {
var sphereCollider = gameObject.GetComponent<SphereCollider>();
{
if (sphereCollider == null) {
sphereCollider = gameObject.AddComponent<SphereCollider>();
sphereCollider.isTrigger = true;
}
sphereCollider.radius = Radius;
}
}
}
#endregion
#region Hooks
#region QuestHook
#region Type Upgrade
object OnStructureUpgrade(BaseCombatEntity entity, BasePlayer player, BuildingGrade.Enum grade) {
QuestProgress(player, QuestType.Grade, ((int)grade).ToString());
return null;
}
#endregion
#region IQPlagueSkill
void StudySkill(BasePlayer player, string name) => QuestProgress(player, QuestType.IQPlagueSkill, name);
#endregion
#region HeadReward
void KillHead(BasePlayer player) => QuestProgress(player, QuestType.IQHeadReward);
#endregion
#region IqCase
void OpenCase(BasePlayer player, string name) => QuestProgress(player, QuestType.IQCases, name);
#endregion
#region OreBonus
void RadOreGive(BasePlayer player, Item item) => QuestProgress(player, QuestType.OreBonus, item.info.shortname, "", null, item.amount);
#endregion
#region Chinook
void LootHack(BasePlayer player) => QuestProgress(player, QuestType.XDChinookIvent);
#endregion
#region Gather
private void OnDispenserGather(ResourceDispenser dispenser, BaseEntity entity, Item item) {
NextTick(() => {
BasePlayer player = entity as BasePlayer;
;
if (player != null) QuestProgress(player, QuestType.Gather, item.info.shortname, "", null, item.amount);
}
);
}
void OnDispenserBonus(ResourceDispenser dispenser, BaseEntity entity, Item item) => OnDispenserGather(dispenser, entity, item);
void OnCollectiblePickup(Item item, BasePlayer player) => QuestProgress(player, QuestType.Gather, item.info.shortname, "", null, item.amount);
#endregion
#region Craft
void OnItemCraftFinished(ItemCraftTask task, Item item) => QuestProgress(task.owner, QuestType.Craft, task.blueprint.targetItem.shortname, "", null, item.amount);
#endregion
#region Research
void OnTechTreeNodeUnlock(Workbench workbench, TechTreeData.NodeInstance node, BasePlayer player) => QuestProgress(player, QuestType.Research, node.itemDef.shortname);
void OnItemResearch(ResearchTable table, Item targetItem, BasePlayer player) => QuestProgress(player, QuestType.Research, targetItem.info.shortname);
#endregion
#region Deploy
private void OnEntityBuilt(Planner plan, GameObject go) {
BasePlayer player = plan?.GetOwnerPlayer();
if (player == null || go == null || plan.GetItem() == null) return;
BaseEntity ent = go.ToBaseEntity();
if (ent == null || ent.skinID == 11543256361) return;
QuestProgress(player, QuestType.Deploy, plan.GetItem().info.shortname);
}
#endregion
#region Loot
private void OnLootEntity(BasePlayer player, LootContainer entity) {
if (entity.OwnerID == 133722822222222 || entity.OwnerID.IsSteamId() || entity == null) return;
QuestProgress(player, QuestType.Loot, "", "", entity.inventory.itemList);
entity.OwnerID = 133722822222222;
}
private void OnLootEntity(BasePlayer player, NPCPlayerCorpse entity) {
if (entity.OwnerID == 133722822222222 || entity.OwnerID.IsSteamId() || entity == null) return;
QuestProgress(player, QuestType.Loot, "", "", entity.containers[0].itemList);
entity.OwnerID = 133722822222222;
}
private void OnContainerDropItems(ItemContainer container) {
if (container == null) return;
BaseEntity entity = container.entityOwner;
if (entity == null) return;
if (!entity.ShortPrefabName.Contains("barrel")) return;
foreach (Item lootitem in container.itemList) lootitem.SetFlag(global : : Item.Flag.Placeholder, true);
}
object OnItemPickup(Item item, BasePlayer player) {
if (item == null) return null;
if (!item.HasFlag(global : : Item.Flag.Placeholder)) return null;
item.SetFlag(global : : Item.Flag.Placeholder, false);
QuestProgress(player, QuestType.Loot, item.info.shortname, item.skin.ToString());
return null;
}
#endregion
#region Swipe
private void OnCardSwipe(CardReader cardReader, Keycard card, BasePlayer player) {
global : : Item item = card.GetItem();
if (item != null && card.accessLevel == cardReader.accessLevel && item.conditionNormalized > 0f) QuestProgress(player, QuestType.Swipe, card.GetItem().info.shortname);
}
#endregion
#region EntityKill/взорвать/уничтожить что либо
void OnPlayerDeath(BasePlayer player, HitInfo info) {
if (player == null || info == null || player.userID < 2147483647) return;
BasePlayer attacker = info.InitiatorPlayer;
if (attacker == null) return;
if (IsFriends(player.userID, attacker.userID) || IsClans(player.UserIDString, attacker.UserIDString) || IsDuel(attacker.userID) || player.userID == attacker.userID) return;
QuestProgress(player, QuestType.EntityKill, "player");
}
void OnEntityDeath(BaseCombatEntity entity, HitInfo info) {
try {
if (entity == null || info == null) return;
string targetName = entity?.ShortPrefabName;
if (targetName.Contains("corpse") || targetName.Contains("servergibs") || targetName.Contains("player")) return;
if (targetName == "testridablehorse") {
targetName = "horse";
}
BasePlayer player = null;
if (info.InitiatorPlayer != null) player = info.InitiatorPlayer;
else if (entity.GetComponent<BaseHelicopter>() != null) {
PatrolHelicopterAI helicopterAI = entity?.GetComponent<PatrolHelicopterAI>();
if (helicopterAI == null) return;
player = helicopterAI._targetList[helicopterAI._targetList.Count - 1].ply;
}
if (player != null && !player.IsNpc && entity.ToPlayer() != player) {
QuestProgress(player, QuestType.EntityKill, targetName.ToLower());
}
}
catch (Exception ex) {
}
}
#endregion
#region (NEW) Покупки у НПС
void OnNpcGiveSoldItem(NPCVendingMachine machine, Item soldItem, BasePlayer buyer) => QuestProgress(buyer, QuestType.PurchaseFromNpc, soldItem.info.shortname, "", null, soldItem.amount);
#endregion
#region(NEW) Взлом ящика
void CanHackCrate(BasePlayer player, HackableLockedCrate crate) {
QuestProgress(player, QuestType.HackCrate, "", "", null);
}
#endregion
#region (NEW) RecycleItem (Игрок не должен выходить из интерфейса переработчика)
object OnRecycleItem(Recycler recycler, Item item) {
List<BasePlayer> list = Facepunch.Pool.GetList<BasePlayer>();
Vis.Entities(recycler.transform.position, 4f, list, 131072, QueryTriggerInteraction.Collide);
int num2 = 1;
if (item.amount > 1) num2 = Mathf.CeilToInt(Mathf.Min((float)item.amount, (float)item.info.stackable * 0.1f));
foreach (BasePlayer basePlayer in list) if (basePlayer.IsAlive() && !basePlayer.IsSleeping() && basePlayer.inventory.loot.entitySource == recycler) QuestProgress(basePlayer, QuestType.RecycleItem, item.info.shortname, "", null, num2);
Facepunch.Pool.FreeList(ref list);
return null;
}
#endregion
#endregion
object CanLootEntity(BasePlayer player, StorageContainer container) {
if (container.skinID == 1195832261) {
UI_DrawInterface(player);
return false;
}
return null;
}
object CanAffordUpgrade(BasePlayer player, BuildingBlock block, BuildingGrade.Enum grade) {
if (block.OwnerID == 76561198283599982) return false;
else return null;
}
void Init() {
LoadPlayerData();
LoadQuestData();
}
private void OnServerInitialized() {
monument = TerrainMeta.Path.Monuments.FirstOrDefault(p => p.name.ToLower().Contains("compound") && p.IsSafeZone == true);
Instance = this;
if (!CopyPaste) {
NextTick(() => {
PrintError("Check if you have the 'Copy Paste'plugin installed");
Interface.Oxide.UnloadPlugin(Name);
}
);
return;
}
else if (CopyPaste.Version < new VersionNumber(4, 1, 27)) {
NextTick(() => {
PrintError("You have an old version of Copy Paste!\nplease update the plugin to the latest version (4.1.27 or higher) - https://umod.org/plugins/copy-paste");
Interface.Oxide.UnloadPlugin(Name);
}
);
return;
}
if (monument == null && !config.customPosition.useCustomPos) {
NextTick(() => {
PrintError(lang.GetMessage("QUEST_errorOutPost", this));
Interface.Oxide.UnloadPlugin(Name);
}
);
return;
}
if (QuestList.Count == 0) {
NextTick(() => {
PrintError(lang.GetMessage("QUEST_QuestListError", this));
Interface.Oxide.UnloadPlugin(Name);
}
);
return;
}
cmd.AddChatCommand(config.settings.questListProgress, this, nameof(UI_QuestList));
ServerMgr.Instance.StartCoroutine(DownloadImages());
foreach (var player in BasePlayer.activePlayerList) OnPlayerConnected(player);
LoadDataCopyPaste();
LoadDataSound();
}
void OnPlayerConnected(BasePlayer player) {
if (!playersInfo.ContainsKey(player.userID)) {
playersInfo.Add(player.userID, new PlayerData());
}
else {
foreach (var item in playersInfo[player.userID].PlayerQuestsAll) {
var curentQuest = QuestList.FirstOrDefault(p => p.Value.DisplayName == item.Value.parentQuest.DisplayName);
if(curentQuest.Value == null) {
NextTick(() => {
playersInfo[player.userID].PlayerQuestsAll.Remove(item.Key);
}
);
}
}
}
foreach (var item in ImageUI) SendImage(player, item.Key);
PlayersTime.Add(player.userID, null);
}
private void OnPlayerDisconnected(BasePlayer d) {
if (openQuestPlayers.Contains(d.userID)) openQuestPlayers.Remove(d.userID);
if (PlayersTime.ContainsKey(d.userID)) {
if (PlayersTime[d.userID] != null) ServerMgr.Instance.StopCoroutine(PlayersTime[d.userID]);
PlayersTime.Remove(d.userID);
}
}
void Unload() {
Instance = null;
if (config.settings.mapUse) Interface.CallHook("API_RemoveMarker", "xdquest");
SaveData();
DestroyAll<ZoneTrigger>();
safeZone?.Kill();
for (int i = 0; i < HouseNPC.Count; i++) {
if (!HouseNPC[i].IsDestroyed) HouseNPC[i]?.Kill();
}
npc?.KillMessage();
foreach (BasePlayer p in BasePlayer.activePlayerList) {
if (PlayersTime.ContainsKey(p.userID)) {
if(PlayersTime[p.userID] != null) ServerMgr.Instance.StopCoroutine(PlayersTime[p.userID]);
PlayersTime.Remove(p.userID);
}
CuiHelper.DestroyUi(p, Layers);
CuiHelper.DestroyUi(p, QuestListLAYER);
}
ServerMgr.Instance.StopCoroutine(DownloadImages());
}
#endregion
#region TriggerNpc
private class ZoneTrigger : MonoBehaviour {
private float ZoneRadius;
private Vector3 Position;
private SphereCollider sphereCollider;
private void Awake() {
gameObject.layer = (int)Layer.Reserved1;
gameObject.name = "ZoneTrigger";
enabled = false;
}
public void Activate(Vector3 pos, float radius) {
Position = pos;
ZoneRadius = radius;
transform.position = Position;
transform.rotation = new Quaternion();
UpdateCollider();
gameObject.SetActive(true);
enabled = true;
}
private void OnTriggerEnter(Collider col) {
BasePlayer player = col.GetComponentInParent<BasePlayer>();
if (player != null) {
int mIndex = Random.Range(0, Instance._Data.Hey.Length);
Instance.API_NPC_SendToAll(Instance._Data.Hey[mIndex]);
}
}
private void OnTriggerExit(Collider col) {
BasePlayer player = col.GetComponentInParent<BasePlayer>();
if (player != null) {
int mIndex = Random.Range(0, Instance._Data.Bye.Length);
Instance.API_NPC_SendToAll(Instance._Data.Bye[mIndex]);
player.SendConsoleCommand("Close_Layer");
}
}
private void OnDestroy() {
Destroy(gameObject);
CancelInvoke();
}
private void UpdateCollider() {
sphereCollider = gameObject.GetComponent<SphereCollider>();
{
if (sphereCollider == null) {
sphereCollider = gameObject.AddComponent<SphereCollider>();
sphereCollider.isTrigger = true;
sphereCollider.name = "ZoneTrigger";
}
sphereCollider.radius = ZoneRadius;
}
}
}
#endregion
#region SoundNpc
public class AudioData {
public String Name;
public Single Length = 0;
public List<VoicePacket> VoicePacketList = new List<VoicePacket>();
public class VoicePacket {
public Single TimeOffset;
public Byte[] Stream;
}
}
public class BotSpeakerData {
public Dictionary<string, AudioData> AudioClips = new Dictionary<string, AudioData>();
public string[] Hey;
public string[] Bye;
}
public string path = string.Empty;
private BotSpeakerData _Data;
private DynamicConfigFile DataFile;
public HashSet<uint> BotAlerts = new HashSet<uint>();
void API_NPC_SendToAll(string clipName) {
try {
if (BotAlerts.Contains(npc.net.ID)) return;
else BotAlerts.Add(npc.net.ID);
AudioData audio;
if (_Data.AudioClips.TryGetValue(clipName, out audio)) {
var clip = _Data.AudioClips[clipName];
timer.Once(clip.Length, () => BotAlerts.Remove(npc.net.ID));
audio.VoicePacketList.ForEach(packet => {
timer.Once(packet.TimeOffset, () => {
if (Net.sv.write.Start()) {
Net.sv.write.PacketID(Message.Type.VoiceData);
Net.sv.write.UInt32(npc.net.ID);
Net.sv.write.BytesWithSize(packet.Stream);
var write = Net.sv.write;
var sendInfo = new SendInfo() {
connections = BasePlayer.activePlayerList.Select(player => player.Connection).ToList(), priority = Priority.Immediate
}
;
write.Send(sendInfo);
}
}
);
}
);
}
}
catch(NullReferenceException ex) {
PrintError(String.Format(lang.GetMessage("QUEST_SoundPlayError", this), config.settings.audioDataPath));
}
}
#endregion
#region ApiLoadData
private void LoadDataSound() {
if (String.IsNullOrWhiteSpace(path)) {
PrintError(lang.GetMessage("QUEST_errorSoundLoad", this));
return;
}
DataFile = Interface.Oxide.DataFileSystem.GetFile(path);
if (Interface.GetMod().DataFileSystem.ReadObject<BotSpeakerData>(path).AudioClips.Count == 0) {
PrintWarning(lang.GetMessage("QUEST_SoundLoad", this));
webrequest.Enqueue($"https://xdquest.skyplugins.ru/getfiles/{config.settings.audioDataPath}/{Key}", null, (code, response) => {
switch (code) {
case 200 : {
WriteToData(response);
break;
}
case 502 : {
PrintError(lang.GetMessage("QUEST_KeyAuth", this));
break;
}
case 404 : {
PrintError(lang.GetMessage("QUEST_FileNotFound", this));
break;
}
default : {
PrintError(lang.GetMessage("QUEST_errorSoundLoadFatal", this));
Log(code.ToString(), "LogError");
return;
}
}
}
, this, RequestMethod.GET);
}
_Data = DataFile.ReadObject<BotSpeak
// ... [Code preview truncated due to length. Full source in attached file] ...