C#:
using Oxide.Core.Libraries;
using Oxide.Core;
using System.Collections.Generic;
using UnityEngine.Networking;
using System;
using System.Text;
using Facepunch;
using Newtonsoft.Json;
using UnityEngine;
using Oxide.Game.Rust.Cui;
using Oxide.Core.Plugins;
using Random = Oxide.Core.Random;
using System.Linq;
using Rust;
using Newtonsoft.Json.Linq;
using Oxide.Plugins.XDQuestExtensionMethods;
using System.Collections;
namespace Oxide.Plugins.XDQuestExtensionMethods
{
public static class ExtensionMethods
{
private static readonly Lang Lang = Interface.Oxide.GetLibrary<Lang>();
public static string GetAdaptedMessage(this string langKey, in string userID, params object[] args)
{
string message = Lang.GetMessage(langKey, XDQuest.Instance, userID);
StringBuilder stringBuilder = Pool.Get<StringBuilder>();
try
{
return stringBuilder.AppendFormat(message, args).ToString();
}
finally
{
stringBuilder.Clear();
Pool.FreeUnmanaged(ref stringBuilder);
}
}
public static string GetAdaptedMessage(this string langKey, in string userID = null)
{
return Lang.GetMessage(langKey, XDQuest.Instance, userID);
}
public static IEnumerable<T> Page<T>(this List<T> source, int page, int pageSize)
{
int start = page * pageSize;
int end = start + pageSize;
for (int i = start; i < end && i < source.Count; i++)
{
yield return source[i];
}
}
}
}
namespace Oxide.Plugins
{
[Info("XDQuest", "DezLife", "8.6.7")]
[Description("Расширенная квест система для вашего сервера!")]
public class XDQuest : RustPlugin
{
private void LoadQuestData()
{
List<Quest> questList = LoadQuestList();
if (questList != null)
{
HashSet<long> currentQuestIds = new();
foreach (Quest quest in questList)
{
currentQuestIds.Add(quest.QuestID);
_questList.Add(quest.QuestID, quest);
_questStatistics.TaskExecutionCounts.TryAdd(quest.QuestID, 0);
}
List<long> keysToRemove = new();
foreach (long taskId in _questStatistics.TaskExecutionCounts.Keys)
if (!currentQuestIds.Contains(taskId))
keysToRemove.Add(taskId);
foreach (long key in keysToRemove)
_questStatistics.TaskExecutionCounts.Remove(key);
SaveData();
}
else
{
_questList = new Dictionary<long, Quest>();
}
if (_questList.Count > 0)
{
foreach (Quest quest in _questList.Values)
{
if (!string.IsNullOrEmpty(quest.QuestPermission) && !permission.PermissionExists($"{Name}." + quest.QuestPermission, this))
{
permission.RegisterPermission($"{Name}." + quest.QuestPermission, this);
}
if (quest.QuestType == QuestType.EntityKill && !string.IsNullOrWhiteSpace(quest.Target) && quest.Target.Contains(","))
{
quest.IsMoreTarget = true;
quest.Targets = quest.Target
.Split(',')
.Select(s => s.Trim())
.Where(s => s.Length > 0)
.ToArray();
}
}
}
}
private void OpenMQL_CMD(BasePlayer player)
{
UIMiniQuestList(player);
}
private enum QuestType
{
IQPlagueSkill,
IQHeadReward,
IQCases,
OreBonus,
XDChinookIvent,
Gather,
EntityKill,
Craft,
Research,
Loot,
Grade,
Swipe,
Deploy,
PurchaseFromNpc,
HackCrate,
RecycleItem,
Growseedlings,
RaidableBases,
Fishing,
BossMonster,
HarborEvent,
SatelliteDishEvent,
Sputnik,
AbandonedBases,
Delivery,
IQDronePatrol,
GasStationEvent,
Triangulation,
FerryTerminalEvent,
Convoy,
Caravan,
IQDefenderSupply
}
private void OnRecyclerToggle(Recycler recycler, BasePlayer player)
{
if (!recycler.IsOn())
{
if (!_recyclePlayer.TryAdd(recycler.net.ID.Value, player))
{
_recyclePlayer.Remove(recycler.net.ID.Value);
_recyclePlayer.Add(recycler.net.ID.Value, player);
}
}
else if (_recyclePlayer.ContainsKey(recycler.net.ID.Value))
{
_recyclePlayer.Remove(recycler.net.ID.Value);
}
}
private static bool IsObjectNull(object obj) => ReferenceEquals(obj, null);
private void InitializeNpc(Vector3 pos, Quaternion rot)
{
_npc = GameManager.server.CreateEntity(MISSIONPROVIDER_TEST, pos, rot) as NPCMissionProvider;
if (_npc == null)
{
Debug.LogError($"Initializing NPC failed! NPCMissionProvider Component == null");
return;
}
_npc.userID = 21;
_npc.UserIDString = _npc.userID.ToString();
_npc.displayName = Name;
_npc.Spawn();
_npc.inventory.containerWear.Clear();
_npc.SendNetworkUpdate();
if (_config.settingsNpc.Wear.Count > 0)
foreach (Configuration.SettingsNpc.NPCOutfit t in _config.settingsNpc.Wear)
{
Item newItem = ItemManager.CreateByName(t.ShortName, 1, t.SkinId);
if (newItem == null)
{
Debug.LogError($"Failed to create item! ({t.ShortName})");
continue;
}
if (!newItem.MoveToContainer(_npc.inventory.containerWear))
{
newItem.Remove();
}
}
if (_config.settingsNpc.soundUse)
{
InitializeBoomBox(_npc.transform);
}
}
private void OnCollectiblePickedup(CollectibleEntity collectible, BasePlayer player, Item item)
{
if (player == null || item == null)
return;
QuestProgress(player.userID, QuestType.Gather, item.info.shortname, "", null, item.amount);
}
private void LoadDataCopyPaste()
{
string filePath = $"copypaste/{QUEST_BUILDING_NAME}";
if (!Interface.Oxide.DataFileSystem.ExistsDatafile(filePath))
{
PrintError("XDQUEST_FileNotLoad".GetAdaptedMessage(null, QUEST_BUILDING_NAME));
NextTick(() => { Interface.Oxide.UnloadPlugin(Name); });
return;
}
ClearEnt();
}
private Configuration _config;
private void OnSkillTreeHandleDispenser(BasePlayer player, BaseEntity entity, Item item)
{
if (player == null || item == null || item.info == null) return;
QuestProgress(player.userID, QuestType.Gather, item.info.shortname, "", null, item.amount);
}
private bool IsClans(string userID, string targetID)
{
if (Clans)
{
string tagUserID = (string)Clans?.Call("GetClanOf", userID);
string tagTargetID = (string)Clans?.Call("GetClanOf", targetID);
if (tagUserID == null && tagTargetID == null)
{
return false;
}
return tagUserID == tagTargetID;
}
else
{
return false;
}
}
private const string LAYERS = "UI_QuestMain";
private string[] _gatherHooks =
{
"OnCollectiblePickedup",
"OnDispenserGathered",
"OnDispenserBonusReceived",
};
public static Timer QuestCooldownsTimer;
private const bool RU = true;
private readonly Dictionary<string, string> _headersDiscord = new()
{
{"Content-Type", "application/json"}
};
void OnNpcGiveSoldItem(NPCVendingMachine machine, Item soldItem, BasePlayer buyer)
{
if (CustomVendingSetup?.Call("API_IsCustomized", machine) is true)
return;
QuestProgress(buyer.userID, QuestType.PurchaseFromNpc, soldItem.info.shortname, "", null, soldItem.amount);
}
private void OnCrateHack(HackableLockedCrate crate)
{
if (crate.originalHackerPlayerId.IsSteamId())
{
QuestProgress(crate.originalHackerPlayerId, QuestType.HackCrate);
}
}
private bool IsFriends(ulong userID, ulong targetID)
{
if (Friends is not null)
return Friends.Call("HasFriend", userID, targetID) is true;
return RelationshipManager.ServerInstance.playerToTeam.TryGetValue(userID, out RelationshipManager.PlayerTeam team) && team.members.Contains(targetID);
}
private void AddPageButton(string direction, string parentId, string command, CuiElementContainer container)
{
string buttonName = direction == "UP" ? "UPBTN" : "DOWNBTN";
string imageName = direction == "UP" ? "3" : "4";
string offsetMin = direction == "UP" ? "182.89 87.565" : "139.598 87.568";
string offsetMax = direction == "UP" ? "221.51 114.635" : "178.326 114.632";
container.Add(new CuiElement
{
Parent = parentId,
Name = buttonName,
Components =
{
new CuiRawImageComponent { Png = _imageUI.GetImage(imageName), Color = "1 1 1 1" },
new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = offsetMin, OffsetMax = offsetMax }
}
});
container.Add(new CuiButton
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Button = { Color = "0 0 0 0", Command = command },
Text = { Text = "" }
}, buttonName);
}
private void OnItemRecycle(Item item, Recycler recycler)
{
BasePlayer value;
if (_recyclePlayer.TryGetValue(recycler.net.ID.Value, out value))
{
int num2 = 1;
if (item.amount > 1)
{
num2 = Mathf.CeilToInt(Mathf.Min(item.amount, item.info.stackable * 0.1f));
}
QuestProgress(value.userID, QuestType.RecycleItem, item.info.shortname, "", null, num2);
}
}
private class SoundFileConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(byte[]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken value = JToken.Load(reader);
return Convert.FromBase64String(value.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
}
private void OnHarborEventWinner(ulong winnerId)
{
QuestProgress(winnerId, QuestType.HarborEvent);
}
private void OnEntityDestroy(BaseEntity entity)
{
if (entity == null) return;
ulong net = entity.net?.ID.Value ?? 0;
if (Looted.Contains(net))
Looted.Remove(net);
}
private void ClearPlayersData()
{
foreach (BasePlayer p in BasePlayer.activePlayerList)
{
CuiHelper.DestroyUi(p, MINI_QUEST_LIST);
CuiHelper.DestroyUi(p, LAYERS);
}
}
private readonly Dictionary<BasePlayer, Timer> _playerTimer = new Dictionary<BasePlayer, Timer>();
private enum SpawnMode
{
Construction,
NPC
}
private List<Quest> GetQuestsByCategory(UICategory category, ulong playerId)
{
List<Quest> result = new List<Quest>();
PlayerData playerData = _playersInfo[playerId];
if (playerData == null)
{
return result;
}
switch (category)
{
case UICategory.Available:
foreach (Quest quest in _questList.Values)
{
if (!string.IsNullOrEmpty(quest.QuestPermission) && !permission.UserHasPermission(playerId.ToString(), $"{Name}." + quest.QuestPermission)) continue;
bool isQuestAlreadyTaken = playerData.CurrentPlayerQuests.Exists(pq => pq.ParentQuestID == quest.QuestID);
bool isQuestCd = playerData.PlayerQuestCooldowns.ContainsKey(quest.QuestID);
bool isQuestAlreadyFinish = playerData.CompletedQuestIds.Contains(quest.QuestID);
if (!isQuestAlreadyTaken && !isQuestAlreadyFinish && !isQuestCd)
{
result.Add(quest);
}
}
break;
case UICategory.Taken:
foreach (PlayerQuest playerQuest in playerData.CurrentPlayerQuests)
{
Quest value;
if (_questList.TryGetValue(playerQuest.ParentQuestID, out value))
{
result.Add(value);
}
}
foreach (long questId in playerData.PlayerQuestCooldowns.Keys)
{
Quest value;
if (_questList.TryGetValue(questId, out value))
{
result.Add(value);
}
}
break;
}
return result;
}
private void OnDroneKilled(BasePlayer player, Drone drone, string KeyDrone)
{
if (player == null || drone == null)
return;
QuestProgress(player.userID, QuestType.IQDronePatrol, KeyDrone, "", null);
}
private Vector3 GetResultVector()
{
if (_config.spawnSettings.UseCustomPosition)
{
return _config.spawnSettings.CustomPosition.Position;
}
Transform transform = _monument.transform;
return transform.position + transform.rotation * _config.spawnSettings.MonumentPosition.Position;
}
private List<Quest> LoadQuestList()
{
return Interface.Oxide.DataFileSystem.ExistsDatafile($"{Name}/{_config.settings.questListDataName}")
? Interface.Oxide.DataFileSystem.ReadObject<List<Quest>>($"{Name}/{_config.settings.questListDataName}")
: null;
}
private List<BaseEntity> _houseNpc = new();
private void MainUi(BasePlayer player)
{
CuiElementContainer container = new CuiElementContainer
{
{
new CuiPanel
{
CursorEnabled = true,
Image = { Color = "1 1 1 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
},
"OverlayNonScaled",
LAYERS
},
new CuiElement
{
Name = LAYER_MAIN_BACKGROUND,
Parent = LAYERS,
Components =
{
new CuiRawImageComponent { Color = "1 1 1 1", Png = _imageUI.GetImage("1") },
new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1" }
}
},
new CuiElement
{
Name = "CloseUIImage",
Parent = LAYER_MAIN_BACKGROUND,
Components =
{
new CuiRawImageComponent { Color = "1 1 1 1", Png = _imageUI.GetImage("2") },
new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = "96.039 87.558", OffsetMax = "135.315 114.647" }
}
},
{
new CuiButton
{
Button = { Color = "1 1 1 0", Command = "CloseMainUI" },
Text = { Text = "", Font = "robotocondensed-regular.ttf", FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "0 0 0 1" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = "96.039 87.558", OffsetMax = "135.315 114.647" }
},
LAYER_MAIN_BACKGROUND,
"BtnCloseUI"
},
{
new CuiLabel
{
RectTransform = { AnchorMin = "0 0.5", AnchorMax = "0 0.5", OffsetMin = "96.227 191.4", OffsetMax = "208.973 211.399" },
Text =
{
Text = "XDQUEST_UI_TASKLIST".GetAdaptedMessage(player.UserIDString), Font = "robotocondensed-regular.ttf", FontSize = 14, Align = TextAnchor.MiddleLeft,
Color = "0.7169812 0.7169812 0.7169812 1"
}
},
LAYER_MAIN_BACKGROUND,
"LabelQuestList"
},
{
new CuiLabel
{
RectTransform = { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-269.184 -102.227", OffsetMax = "-197.242 -72.373" },
Text =
{
Text = "XDQUEST_UI_Awards".GetAdaptedMessage(player.UserIDString), Font = "robotocondensed-regular.ttf", FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1"
}
},
LAYER_MAIN_BACKGROUND,
"PrizeTitle"
},
{
new CuiLabel
{
RectTransform = { AnchorMin = "0 0.5", AnchorMax = "0 0.5", OffsetMin = "250.187 191.399", OffsetMax = "350.187 211.401" },
Text =
{
Text = "XDQUEST_UI_TASKCount".GetAdaptedMessage(player.UserIDString, 0), Font = "robotocondensed-regular.ttf", FontSize = 14, Align = TextAnchor.MiddleRight,
Color = "1 1 1 1"
}
},
LAYER_MAIN_BACKGROUND,
"LabelQuestCount", "LabelQuestCount"
}
};
CuiHelper.DestroyUi(player, "UI_QuestMain");
CuiHelper.AddUi(player, container);
Category(player, UICategory.Available);
QuestListUI(player, UICategory.Available);
QuestInfo(player, 0, UICategory.Available);
}
private void OnConvoyEventWin(ulong userID)
{
QuestProgress(userID, QuestType.Convoy);
}
private void OnItemCraftFinished(ItemCraftTask task, Item item, ItemCrafter crafter)
{
QuestProgress(crafter.owner.userID, QuestType.Craft, task.blueprint.targetItem.shortname, "", null, item.amount);
}
private class QuestStatistics
{
public int CompletedTasks, TakenTasks, DeclinedTasks;
public Dictionary<long, int> TaskExecutionCounts = new();
public void GatherTaskStatistics(TaskType taskType, long? taskId = null)
{
switch (taskType)
{
case TaskType.Completed:
CompletedTasks += 1;
break;
case TaskType.Taken:
TakenTasks += 1;
break;
case TaskType.Declined:
DeclinedTasks += 1;
break;
case TaskType.TaskExecution:
if (taskId.HasValue)
{
if (!TaskExecutionCounts.TryAdd(taskId.Value, 1))
{
TaskExecutionCounts[taskId.Value] += 1;
}
}
else
{
throw new ArgumentNullException("For TaskExecution type, taskId must be provided.");
}
break;
default:
throw new ArgumentException("Unknown task type");
}
}
public Dictionary<long, int> GetTop5MostExecutedTasks()
{
List<KeyValuePair<long, int>> topTasks = new List<KeyValuePair<long, int>>();
foreach (KeyValuePair<long, int> task in TaskExecutionCounts)
{
if (topTasks.Count < 5)
{
topTasks.Add(task);
topTasks.Sort((a, b) => b.Value.CompareTo(a.Value));
}
else
{
if (task.Value > topTasks[4].Value)
{
topTasks[4] = task;
topTasks.Sort((a, b) => b.Value.CompareTo(a.Value));
}
}
}
Dictionary<long, int> result = new Dictionary<long, int>();
foreach (KeyValuePair<long, int> kvp in topTasks)
{
result[kvp.Key] = kvp.Value;
}
return result;
}
public Dictionary<long, int> GetTop5LeastExecutedTasks()
{
List<KeyValuePair<long, int>> leastTasks = new List<KeyValuePair<long, int>>();
foreach (KeyValuePair<long, int> task in TaskExecutionCounts)
{
if (leastTasks.Count < 5)
{
leastTasks.Add(task);
leastTasks.Sort((a, b) => a.Value.CompareTo(b.Value));
}
else
{
if (task.Value < leastTasks[4].Value)
{
leastTasks[4] = task;
leastTasks.Sort((a, b) => a.Value.CompareTo(b.Value));
}
}
}
Dictionary<long, int> result = new Dictionary<long, int>();
foreach (KeyValuePair<long, int> kvp in leastTasks)
{
result[kvp.Key] = kvp.Value;
}
return result;
}
}
private void OnAbandonedBaseEnded(Vector3 center, bool allowPVP, List<BasePlayer> intruders)
{
if (intruders.Count <= 0)
return;
foreach (BasePlayer player in intruders)
{
QuestProgress(player.userID, QuestType.AbandonedBases);
}
}
private void SendChat(BasePlayer player, string message, ConVar.Chat.ChatChannel channel = ConVar.Chat.ChatChannel.Global)
{
if (IQChat)
if (_config.settingsIQChat.UIAlertUse)
IQChat?.Call("API_ALERT_PLAYER_UI", player, message);
else IQChat?.Call("API_ALERT_PLAYER", player, message, _config.settingsIQChat.CustomPrefix, _config.settingsIQChat.CustomAvatar);
else player.SendConsoleCommand("chat.add", channel, 0, message);
}
private void OnCaravanEventWin(ulong userID)
{
QuestProgress(userID, QuestType.Caravan);
}
private void OnItemResearched(ResearchTable table, int amountToConsume)
{
QuestProgress(table.LastLootedBy, QuestType.Research, table.GetTargetItem().info.shortname);
}
private bool TakeItemsNeededForQuest(BasePlayer player, Quest globalQuest)
{
ItemDefinition idItem = ItemManager.FindItemDefinition(globalQuest.Target);
int? item = null;
if (player != null && player.inventory != null)
item = player.inventory.GetAmount(idItem.itemid);
if (item is 0 or null)
{
UINottice(player, "XDQUEST_UI_InsufficientResources".GetAdaptedMessage(player.UserIDString, idItem.displayName.english));
return false;
}
if (item < globalQuest.ActionCount)
{
UINottice(player, "XDQUEST_UI_NotResourcesAmount".GetAdaptedMessage(player.UserIDString, idItem.displayName.english, globalQuest.ActionCount));
return false;
}
if (item >= globalQuest.ActionCount)
{
player.inventory.Take(null, idItem.itemid, globalQuest.ActionCount);
}
return true;
}
private void OnPlayerDeath(BasePlayer player, HitInfo info)
{
if (player == null || info == null || !player.userID.IsSteamId())
return;
BasePlayer attacker = info.InitiatorPlayer;
if (attacker == null)
return;
if (IsFriends(player.userID.Get(), attacker.userID.Get()) || IsClans(player.UserIDString, attacker.UserIDString) || IsDuel(attacker.userID.Get()) || player.userID == attacker.userID)
return;
QuestProgress(attacker.userID, QuestType.EntityKill, "player");
}
private void OnEntityBuilt(Planner plan, GameObject go)
{
if(plan == null) return;
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.userID, QuestType.Deploy, plan.GetItem().info.shortname);
}
private void Category(BasePlayer player, UICategory category)
{
string color1 = category == UICategory.Available ? "0.4509804 0.5529412 0.2705882 0.8392157" : "0.6431373 0.6509804 0.654902 0.4";
string color2 = category == UICategory.Taken ? "0.4509804 0.5529412 0.2705882 0.8392157" : "0.6431373 0.6509804 0.654902 0.4";
string img = _imageUI.GetImage("16");
CuiElementContainer container = new CuiElementContainer();
container.Add(new CuiPanel
{
CursorEnabled = false,
Image = { Color = "1 1 1 0" },
RectTransform = { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-541.65 234.6", OffsetMax = "-284.33 337.6" }
}, LAYER_MAIN_BACKGROUND, XDQUEST_CATEGORY_MAIN, XDQUEST_CATEGORY_MAIN);
container.Add(new CuiElement
{
Name = "XDQUEST_CATEGORY_SPRITE",
Parent = XDQUEST_CATEGORY_MAIN,
Components =
{
new CuiRawImageComponent { Color = "0.7529412 0.5137255 0.04705882 1", Sprite = "assets/icons/Favourite_active.png", Material = "assets/icons/iconmaterial.mat", },
new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "0 1", OffsetMin = "1.3 -22.5", OffsetMax = "21.3 -2.5" }
}
});
container.Add(new CuiLabel
{
RectTransform = { AnchorMin = "0.5 1", AnchorMax = "0.5 1", OffsetMin = "-103.66 -25", OffsetMax = "125.915 0" },
Text =
{
Text = "XDQUEST_UI_CATEGORY".GetAdaptedMessage(player.UserIDString), Font = "robotocondensed-bold.ttf", FontSize = 12, Align = TextAnchor.MiddleLeft,
Color = "0.7169812 0.7169812 0.7169812 1"
}
}, XDQUEST_CATEGORY_MAIN, "XDQUEST_CATEGORY_TITLE");
container.Add(new CuiElement
{
Name = "XDQUEST_CATEGORY_BTN_1",
Parent = XDQUEST_CATEGORY_MAIN,
Components =
{
new CuiRawImageComponent { Color = color1, Png = img },
new CuiRectTransformComponent { AnchorMin = "0 0.5", AnchorMax = "0 0.5", OffsetMin = "0 4.8", OffsetMax = "150 22.8" }
}
});
container.Add(new CuiButton
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Button = { Color = "0 0 0 0", Command = category == UICategory.Available ? "" : $"UI_Handler category {UICategory.Available.ToString()}" },
Text =
{
Text = "XDQUEST_UI_CATEGORY_ONE".GetAdaptedMessage(player.UserIDString), Font = "robotocondensed-regular.ttf", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1"
}
}, "XDQUEST_CATEGORY_BTN_1");
container.Add(new CuiElement
{
Name = "XDQUEST_CATEGORY_BTN_2",
Parent = XDQUEST_CATEGORY_MAIN,
Components =
{
new CuiRawImageComponent { Color = color2, Png = img },
new CuiRectTransformComponent { AnchorMin = "0 0.5", AnchorMax = "0 0.5", OffsetMin = "0 -17.2", OffsetMax = "150 0.8" }
}
});
container.Add(new CuiButton
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Button = { Color = "0 0 0 0", Command = category == UICategory.Taken ? "" : $"UI_Handler category {UICategory.Taken.ToString()}" },
Text =
{
Text = "XDQUEST_UI_CATEGORY_TWO".GetAdaptedMessage(player.UserIDString), Font = "robotocondensed-regular.ttf", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1"
}
}, "XDQUEST_CATEGORY_BTN_2");
CuiHelper.DestroyUi(player, XDQUEST_CATEGORY_MAIN);
CuiHelper.AddUi(player, container);
}
private void CreateMapMarker(Vector3 pos)
{
Interface.CallHook("API_CreateMarker", pos, Name, 0, 3f, 0.2f, _config.mapSettings.nameMarkerMap, _config.mapSettings.colorMarker, _config.mapSettings.colorOutline);
}
private void Init()
{
Instance = this;
LoadPlayerData();
LoadQuestStatisticsData();
LoadQuestData();
}
public class FancyMessage
{
[JsonProperty("content")] public string Content;
[JsonProperty("username")] public string Username;
[JsonProperty("avatar_url")] public string AvatarUrl;
[JsonProperty("embeds")] public List<Embed> Embeds;
public FancyMessage(string content, List<Embed> embeds)
{
Content = content;
Username = "XDQuest Statistics";
AvatarUrl = "https://i.imgur.com/RxgzxNW.jpg";
Embeds = embeds;
}
public string ToJson() => JsonConvert.SerializeObject(this);
public class Embed
{
[JsonProperty("title")] public string Title { get; }
[JsonProperty("description")] public string Description { get; }
[JsonProperty("color")] public int Color { get; }
[JsonProperty("timestamp")] public string Timestamp { get; }
public Embed(string title, string description = "")
{
Title = title;
Description = description;
Color = 16689937;
Timestamp = DateTime.UtcNow.ToString("o");
}
}
}
private void OnServerShutdown() => Unload();
[ConsoleCommand("xdquest.stat")]
private void StatisticsPost (ConsoleSystem.Arg arg)
{
BasePlayer player = arg.Player();
if (player != null && !player.IsAdmin)
{
player.ConsoleMessage("XDQUEST_INSUFFICIENT_PERMISSIONS_ERROR".GetAdaptedMessage(player.UserIDString));
return;
}
if (!_config.statisticsCollectionSettings.useStatistics)
{
SendConsoleMessage(player, "XDQUEST_STAT_CMD_1".GetAdaptedMessage(PlayerOrNull(player)));
return;
}
if (string.IsNullOrEmpty(_config.statisticsCollectionSettings.discordWebhookUrl))
{
SendConsoleMessage(player, "XDQUEST_STAT_CMD_2".GetAdaptedMessage(PlayerOrNull(player)));
return;
}
GrabAndPostStatistics();
}
private void STCanReceiveYield(BasePlayer player, GrowableEntity entity, Item item)
{
if (player == null || item == null || item.info == null) return;
QuestProgress(player.userID, QuestType.Gather, item.info.shortname, "", null, item.amount);
}
private void DestroyUnneededComponents(BaseEntity entity)
{
UnityEngine.Object.Destroy(entity.GetComponent<DestroyOnGroundMissing>());
UnityEngine.Object.Destroy(entity.GetComponent<GroundWatch>());
}
private void UINottice(BasePlayer player, string msg, string sprite = "assets/icons/warning.png", string color = "0.76 0.34 0.10 1.00")
{
CuiElementContainer container = new CuiElementContainer
{
new CuiElement
{
FadeOut = 0.30f,
Name = "QuestUiNotice",
Parent = LAYER_MAIN_BACKGROUND,
Components =
{
new CuiRawImageComponent { Color = "1 1 1 1", Png = _imageUI.GetImage("12"), FadeIn = 0.30f },
new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "0 1", OffsetMin = "315 -110", OffsetMax = "610 -43" }
}
},
new CuiElement
{
FadeOut = 0.30f,
Name = "NoticeFeed",
Parent = "QuestUiNotice",
Components =
{
new CuiRawImageComponent { Color = color, Png = _imageUI.GetImage("13"), FadeIn = 0.30f },
new CuiRectTransformComponent { AnchorMin = "0 0.5", AnchorMax = "0 0.5", OffsetMin = "0.276 -33.458", OffsetMax = "12.692 33.459" }
}
},
//container.Add(new CuiElement
//{
// Parent = "QuestUi",
// Components = {
// new CuiRawImageComponent { Color = HexToRustFormat(color), Png = GetImage("16"), FadeIn = 0.30f },
// new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = "0.451 -23.243", OffsetMax = "1.60543422 12.543" }
// }
//});
new CuiElement
{
FadeOut = 0.30f,
Name = "NoticeSprite",
Parent = "QuestUiNotice",
Components =
{
new CuiImageComponent { Color = "1 1 1 1", Sprite = sprite, FadeIn = 0.30f },
new CuiRectTransformComponent { AnchorMin = "0 0.5", AnchorMax = "0 0.5", OffsetMin = "23.5 -15.5", OffsetMax = "54.5 15.5" }
}
},
{
new CuiLabel
{
RectTransform = { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-78.262 -33.458", OffsetMax = "143.522 33.459" },
Text = { Text = msg, Font = "robotocondensed-regular.ttf", FontSize = 11, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", FadeIn = 0.30f }
},
"QuestUiNotice",
"NoticeText"
}
};
CuiHelper.DestroyUi(player, "NoticeText");
CuiHelper.DestroyUi(player, "NoticeSprite");
CuiHelper.DestroyUi(player, "NoticeFeed");
CuiHelper.DestroyUi(player, "QuestUiNotice");
CuiHelper.AddUi(player, container);
DeleteNotification(player);
}
private object OnEntityGroundMissing(BaseEntity ent)
{
if (ent == null) return null;
if (ent.OwnerID == QUEST_BUILDING_OWNER) return false;
return null;
}
private QuestStatistics _questStatistics = new();
private const string SPHERE_PREFAB = "assets/prefabs/visualization/sphere.prefab";
private void LoadDataSounds()
{
string[] files = Interface.Oxide.DataFileSystem.GetFiles($"{Name}/Sounds/");
foreach (string filePath in files)
{
string fileName = GetFileNameWithoutExtension(filePath);
try
{
NpcSound data = Interface.Oxide.DataFileSystem.ReadObject<NpcSound>($"{Name}/Sounds/{fileName}");
if (data.voiceData != null && data.voiceData.Length > 0)
{
_cachedSounds.Add(fileName, data);
}
else
{
PrintError($"File {fileName} is corrupted and cannot be loaded!");
}
}
catch (Exception ex)
{
PrintError($"Error loading file {fileName}: {ex.Message}");
}
}
}
private float RadianToDegree(float radians)
{
return (float)(radians * 180.0f / Math.PI);
}
private const string XDQUEST_CATEGORY_MAIN = "XDQUEST_CATEGORY_MAIN";
private void ClearEnt()
{
List<BaseEntity> entities = Pool.Get<List<BaseEntity>>();
Vis.Entities(GetResultVector(), 20f, entities, LayerMask.GetMask("Construction", "Deployable", "Deployed", "Debris", "Default", "Player (Server)"));
foreach (BaseEntity entity in entities)
{
if(entity == null || entity.IsDestroyed || (entity.OwnerID != QUEST_BUILDING_OWNER && !entity.PrefabName.Contains("missionprovider_test")))
continue;
entity.Kill();
}
Pool.FreeUnmanaged(ref entities);
timer.Once(5f, HandleSpawn);
}
private static class ObjectCache
{
private static readonly object True = true;
private static readonly object False = false;
private static class StaticObjectCache<T>
{
private static readonly Dictionary<T, object> CacheByValue = new Dictionary<T, object>();
public static object Get(T value)
{
object cachedObject;
if (!CacheByValue.TryGetValue(value, out cachedObject))
{
cachedObject = value;
CacheByValue[value] = cachedObject;
}
return cachedObject;
}
}
public static object Get<T>(T value)
{
return StaticObjectCache<T>.Get(value);
}
public static object Get(bool value)
{
return value ? True : False;
}
}
private enum UICategory
{
Available,
Taken,
}
[ConsoleCommand("CloseMiniQuestList")]
void CloseMiniQuestList(ConsoleSystem.Arg arg)
{
CuiHelper.DestroyUi(arg.Player(), MINI_QUEST_LIST);
if (_openMiniQuestListPlayers.Contains(arg.Player().userID))
{
_openMiniQuestListPlayers.Remove(arg.Player().userID);
}
}
protected override void LoadDefaultConfig()
{
_config = new Configuration();
}
private AudioZoneController _audioZoneController;
private void QuestProgress(ulong playerUserID, QuestType questType, string entName = "", string skinId = "", List<Item> items = null, int count = 1)
{
if (!_playersInfo.TryGetValue(playerUserID, out PlayerData playerData))
return;
List<PlayerQuest> playerQuests = playerData.CurrentPlayerQuests.FindAll(x => x.ParentQuestType == questType && !x.Finished);
foreach (PlayerQuest quest in playerQuests)
{
Quest parentQuest = _questList[quest.ParentQuestID];
if (string.IsNullOrEmpty(entName) && items == null)
{
quest.AddCount(count);
return;
}
if (items != null)
{
bool isSkinID = ulong.TryParse(parentQuest.Target, out ulong skinIditem);
foreach (Item item in items)
{
if(item.info.shortname.Equals(parentQuest.Target, StringComparison.OrdinalIgnoreCase) || (isSkinID && item.skin.Equals(skinIditem)))
quest.AddCount(item.amount);
}
continue;
}
switch (questType)
{
case QuestType.IQCases:
case QuestType.HarborEvent:
case QuestType.SatelliteDishEvent:
case QuestType.Sputnik:
case QuestType.Caravan:
case QuestType.Convoy:
case QuestType.GasStationEvent:
case QuestType.FerryTerminalEvent:
case QuestType.Triangulation:
case QuestType.AbandonedBases:
case QuestType.IQDronePatrol:
case QuestType.IQDefenderSupply:
case QuestType.IQHeadReward:
{
if (parentQuest.Target.Equals(entName) || parentQuest.Target.Equals("0") || parentQuest.Target.Equals("999"))
quest.AddCount(count);
break;
}
case QuestType.Swipe:
{
if (parentQuest.Target.Equals(entName))
quest.AddCount(count);
break;
}
case QuestType.EntityKill:
{
if (parentQuest.IsMoreTarget)
{
foreach (string target in parentQuest.Targets)
{
if (entName.Equals(target, StringComparison.OrdinalIgnoreCase))
quest.AddCount(count);
}
}
else
{
if (entName.Equals(parentQuest.Target, StringComparison.OrdinalIgnoreCase))
quest.AddCount(count);
}
break;
}
default:
{
if (entName.Equals(parentQuest.Target, StringComparison.OrdinalIgnoreCase) || skinId.Equals(parentQuest.Target))
quest.AddCount(count);
break;
}
}
}
Interface.CallHook("OnQuestProgress", playerUserID, (int)questType, entName, skinId, items, count);
}
private Dictionary<long, Quest> _questList = new();
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject(this.Name + $"/PlayerInfo", _playersInfo);
Interface.Oxide.DataFileSystem.WriteObject(this.Name + $"/QuestStatistics", _questStatistics);
}
private const ulong QUEST_BUILDING_OWNER = 3774732;
private void OnServerInitialized()
{
_monument = TerrainMeta.Path.Monuments.FirstOrDefault(p => p.name.ToLower().Contains("compound") && p.IsSafeZone);
if (!CopyPaste)
{
UnloadWithMessage("Check if you have the 'Copy Paste' plugin installed");
return;
}
if (CopyPaste.Version < new VersionNumber(4, 2, 5))
{
UnloadWithMessage("You have an old version of Copy Paste!\nplease update the plugin to the latest version (4.2.5 or higher) - https://umod.org/plugins/copy-paste");
return;
}
if (_monument == null && !_config.spawnSettings.UseCustomPosition)
{
UnloadWithMessage("XDQUEST_MissingOutPost".GetAdaptedMessage());
return;
}
if (_questList.Count == 0)
{
PrintError("XDQUEST_MissingQuests".GetAdaptedMessage());
return;
}
foreach (string cmds in _config.settings.questListProgress)
cmd.AddChatCommand(cmds, this, nameof(OpenMQL_CMD));
LoadDataCopyPaste();
LoadDataSounds();
GatherHooksSub();
_imageUI = new ImageUI();
_imageUI.DownloadImage();
foreach (BasePlayer player in BasePlayer.activePlayerList)
OnPlayerConnected(player);
QuestCooldownsTimer = timer.Every(70f, CheckQuestCooldowns);
if (_config.statisticsCollectionSettings.useStatistics && !string.IsNullOrEmpty(_config.statisticsCollectionSettings.discordWebhookUrl))
{
timer.Every(_config.statisticsCollectionSettings.publishFrequency, GrabAndPostStatistics);
}
}
private void OnOpenedCase(BasePlayer player, string name)
{
QuestProgress(player.userID, QuestType.IQCases, name);
}
private static double CurrentTime()
{
return Facepunch.Math.Epoch.Current;
}
[PluginReference] Plugin CopyPaste, IQChat, Friends, Clans, EventHelper, Battles, Duel, Duelist, ArenaTournament, Notify, SkillTree, CustomVendingSetup;
private void Unload()
{
if (IsObjectNull(Instance))
return;
if (!IsObjectNull(QuestCooldownsTimer))
{
QuestCooldownsTimer.Destroy();
}
if (_config.mapSettings.mapUse)
DeleteMapMarker();
if (_imageUI != null)
{
_imageUI.UnloadImages();
_imageUI = null;
}
Instance = null;
QuestCooldownsTimer = null;
SaveData();
DestroyObjects();
RemoveHouseNpCs();
if (_npc != null)
_npc.KillMessage();
ClearPlayersData();
}
private void InitializeBoomBox(Transform npcTransform)
{
DeployableBoomBox boomBox = null;
try
{
Vector3 pos = npcTransform.position + new Vector3(0, 1.4f, 0);
Quaternion rot = npcTransform.rotation;
boomBox = GameManager.server.CreateEntity(BOOM_BOX_PREFAB, pos, rot) as DeployableBoomBox;
if (boomBox == null)
{
Debug.LogError("Unable to create DeployableBoomBox entity.");
return;
}
boomBox.enableSaving = false;
boomBox.OwnerID = QUEST_BUILDING_OWNER;
boomBox.Spawn();
UnityEngine.Object.Destroy(boomBox.GetComponent<GroundWatch>());
UnityEngine.Object.Destroy(boomBox.GetComponent<DestroyOnGroundMissing>());
boomBox.BoxController.SetFlag(BaseEntity.Flags.Reserved8, true);
boomBox.BoxController.SetFlag(BaseEntity.Flags.Busy, true);
boomBox.BoxController.ConditionLossRate = 0;
boomBox.networkEntityScale = true;
boomBox.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f);;
boomBox.SendNetworkUpdate();
NextTick(() =>
{
_houseNpc.Add(boomBox);
});
_audioZoneController = new GameObject().AddComponent<AudioZoneController>();
_audioZoneController.Activate(pos, boomBox, 3.2f);
}
catch (Exception e)
{
Debug.LogError("An error occurred while initializing BoomBox: " + e);
if (boomBox != null && !boomBox.IsDestroyed)
boomBox.Kill();
}
}
private const string LAYER_MAIN_BACKGROUND = "UI_QuestMainBackground";
public static XDQuest? Instance;
private void OnDispenserGathered(ResourceDispenser dispenser, BasePlayer player, Item item)
{
if(player == null) return;
QuestProgress(player.userID, QuestType.Gather, item.info.shortname, "", null, item.amount);
}
private void DeleteMapMarker()
{
Interface.CallHook("API_RemoveMarker", Name);
}
private void SpawnNPCs(Vector3 position, float rotationNpc)
{
Quaternion rotation = Quaternion.Euler(0, RadianToDegree(rotationNpc), 0);
InitializeNpc(position, rotation);
}
private void OnPasteFinished(List<BaseEntity> pastedEntities, string fileName)
{
if (!string.Equals(fileName, QUEST_BUILDING_NAME, StringComparison.CurrentCultureIgnoreCase))
return;
_houseNpc = pastedEntities;
try
{
foreach (BaseEntity item in _houseNpc)
{
if (item == null || item.IsDestroyed || item.transform == null)
continue;
item.OwnerID = QUEST_BUILDING_OWNER;
if (item is CeilingLight or SimpleLight or BaseOven or DigitalClock)
item.SetFlag(BaseEntity.Flags.On, true);
if (item is ElectricalHeater)
item.SetFlag(BaseEntity.Flags.Reserved8, true);
if (item is WeaponRack weaponRack)
foreach (BaseEntity child in weaponRack.children)
{
if (child != null && !child.IsDestroyed)
{
child.enableSaving = false;
BaseEntity.saveList.Remove(child);
}
}
if (item is CCTV_RC cctvRc)
{
cctvRc.UpdateIdentifier(_config.settings.CameraId);
cctvRc.SetFlag(BaseEntity.Flags.Reserved8, true);
cctvRc.UpdateRCAccess(true);
}
if (item is NeonSign neonSign)
{
neonSign.SetFlag(BaseEntity.Flags.Reserved8, true);
neonSign.isAnimating = true;
if (neonSign.isAnimating)
{
neonSign.CancelInvoke(neonSign.animationLoopAction);
neonSign.InvokeRepeating(neonSign.animationLoopAction, 2f, 2f);
}
DestroyUnneededComponents(neonSign);
neonSign.SendNetworkUpdate();
}
if (item.prefabID == 1447082346)
{
item.transform.GetPositionAndRotation(out Vector3 positionCopy, out Quaternion rotationCopy);
InitializeNpc(positionCopy, rotationCopy);
item.Kill();
continue;
}
if (item is DeployableBoomBox boomBox)
{
if (_config.settings.useRadio)
{
NextTick(() =>
{
boomBox.BoxController.CurrentRadioIp = _config.settings.RadioStation;
boomBox.BoxController.ConditionLossRate = 0;
boomBox.BoxController.baseEntity.ClientRPC(RpcTarget.NetworkGroup("OnRadioIPChanged"), boomBox.BoxController.CurrentRadioIp);
if (!boomBox.BoxController.IsOn())
{
boomBox.BoxController.ServerTogglePlay(true);
}
boomBox.BoxController.baseEntity.SendNetworkUpdate();
});
}
}
if (item is BuildingBlock buildingBlock)
buildingBlock.StopBeingRotatable();
if (item is DecayEntity decayEntity)
{
decayEntity.decay = null;
decayEntity.decayVariance = 0;
decayEntity.ResetUpkeepTime();
decayEntity.DecayTouch();
decayEntity.CancelInvoke(nameof(DecayEntity.DecayTick));
BuildingManager.server.decayEntities.Remove(decayEntity);
}
if (item is BaseCombatEntity combatEntity)
{
combatEntity.pickup.enabled = false;
}
if (item is Door door)
{
door.pickup.enabled = false;
door.canTakeLock = false;
door.canTakeCloser = false;
}
if (!IsIgnoreClass(item))
{
item.SetFlag(BaseEntity.Flags.Busy, true);
item.SetFlag(BaseEntity.Flags.Locked, true);
}
}
PrintWarning("XDQUEST_CopyPasteSuccessfully".GetAdaptedMessage());
}
catch (Exception ex)
{
PrintError("XDQUEST_BuildingPasteError".GetAdaptedMessage());
Log(ex.ToString(), "LogError");
}
}
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["XDQUEST_CopyPasteError"] = "There was a problem with CopyPaste! Contact the Developer!\nDiscord: @DezLife\nnvk.com/dezlife",
["XDQUEST_CopyPasteSuccessfully"] = "The building has spawned successfully!",
["XDQUEST_BuildingPasteError"] = "There was a problem with spawning the Building! Contact the Developer!\nDiscord: @DezLife\nvk.com/dezlife",
["XDQUEST_MissingOutPost"] = "Your map doesnt have an Outpost Monument. Please use a custom spawn point.",
["XDQUEST_MissingQuests"]
= "You do not have a file with tasks, the plugin will not work correctly! Create one on the Website - https://xdquest.skyplugins.ru/ or use the included one.",
["XDQUEST_FileNotLoad"] = "The construction file was not found : {0}. Move it to the copy paste folder",
["XDQUEST_UI_TASKLIST"] = "Quest List",
["XDQUEST_UI_Awards"] = "Rewards",
["XDQUEST_UI_TASKCount"] = "<color=#42a1f5>{0}</color> QUESTS",
["XDQUEST_UI_CHIPperformed"] = "Completed",
["XDQUEST_UI_CHIPInProgress"] = "In progress",
["XDQUEST_UI_QUESTREPEATCAN"] = "Yes",
["XDQUEST_UI_QUESTREPEATfForbidden"] = "No",
["XDQUEST_UI_Missing"] = "Missing",
["XDQUEST_UI_InfoRepeatInCD"] = "Repeat {0} | Cooldown {1} | Hand in {2}",
["XDQUEST_UI_QuestNecessary"] = "Needed",
["XDQUEST_UI_QuestNotNecessary"] = "Not needed",
["XDQUEST_UI_QuestBtnPerformed"] = "COMPLETED",
["XDQUEST_UI_QuestBtnTake"] = "TAKE",
["XDQUEST_UI_QuestBtnPass"] = "COMPLETE",
["XDQUEST_UI_QuestBtnDelivery"] = "DELIVER",
["XDQUEST_UI_QuestBtnRefuse"] = "REFUSE",
["XDQUEST_UI_ACTIVEOBJECTIVES"] = "Objective: {0}",
["XDQUEST_UI_MiniQLInfo"] = "{0}\nProgress: {1} / {2}\nQuest: {3}",
["XDQUEST_UI_MiniQLInfoDelivery"] = "{0}\nQuest: {3}",
["XDQUEST_UI_CMDPosChange"]
= "You have successfully changed the position for building within the Outpost.\n(You need to reload the plugin)\nYou can configure the building's rotation in the config",
["XDQUEST_UI_CMDCustomPosAdd"]
= "You have successfully added a custom building position.\n(You need to reload the plugin)\nYou can rotate the building in the config!\nRemember to enable the option to spawn a building on a custom position in the config.",
["XDQUEST_UI_QuestLimit"] = "You have to many <color=#4286f4>unfinished</color> Quests",
["XDQUEST_UI_AlreadyTaken"] = "You have already <color=#4286f4>taken</color> this Quest!",
["XDQUEST_UI_NotPerm"] = "You do not have the rights to perform this Quest.",
["XDQUEST_UI_AlreadyDone"] = "You have already <color=#4286f4>completed</color> this Quest!",
["XDQUEST_UI_TookTasks"] = "You have <color=#4286f4>successfully</color> accepted the Quest {0}",
["XDQUEST_UI_ACTIVECOLDOWN"] = "This Quest is on Cooldown.",
["XDQUEST_UI_LackOfSpace"] = "Your inventory is full! Clear some space and try again!",
["XDQUEST_UI_QuestsCompleted"] = "Quest Completed! Enjoy your reward!",
["XDQUEST_UI_PassedTasks"] = "So this Quest was to much for you? \n Try again later!",
["XDQUEST_UI_ActiveQuestCount"] = "You have no active Quests.",
["XDQUEST_Finished_QUEST"] = "You have completed the task: <color=#4286f4>{0}</color>",
["XDQUEST_Finished_QUEST_ALL"] = "Player <color=#4286f4>{0}</color> just completed a task: <color=#4286f4>{1}</color> and got a reward!",
["XDQUEST_UI_InsufficientResources"] = "You don't have {0}, you should definitely bring this to Sidorovich",
["XDQUEST_UI_InsufficientResourcesSkin"] = "You don’t have the required item, you need to bring it to Sidorovich",
["XDQUEST_UI_NotResourcesAmount"] = "You don't have enough {0}, you need {1}",
["XDQUEST_SoundLoadErrorExt"] = "The voice file {0} is missing, upload it using this path - (/data/XDQuest/Sounds). Or remove it from the configuration",
["XDQUEST_UI_CATEGORY"] = "CATEGORIES",
["XDQUEST_UI_CATEGORY_ONE"] = "Available tasks",
["XDQUEST_UI_CATEGORY_TWO"] = "Active tasks",
["XDQUEST_UI_TASKS_LIST_EMPTY"] = "Quest list is empty",
["XDQUEST_UI_TASKS_INFO_EMPTY"] = "Select a task to see information about it",
["XDQUEST_REPEATABLE_QUEST_AVAILABLE_AGAIN"] = "You can participate in the quest \"<color=#4286f4>{0}</color>\" again! \nDon't miss your chance!",
["XDQUEST_STAT_1"] = "Main Statistics",
["XDQUEST_STAT_2"] = "**Tasks Completed:** {0}\n\n**Total Tasks Taken:** {1}\n\n**Tasks Declined:** {
// ... [Code preview truncated due to length. Full source in attached file] ...