Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Chronicles of Northarun v0.1.1
Chronicles.dll
Decompiled an hour ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace Chronicles; public enum Cat { All, Battles, Fallen, Explorers, Gods, Guilds, Villages, Skald } public class Entry { public long Id; public long Ticks; public int Day; public string Kind = ""; public string[] A = new string[0]; public Cat Category { get { if (Kind == "api") { foreach (Cat value in Enum.GetValues(typeof(Cat))) { if (value != Cat.All && value.ToString().ToLowerInvariant() == Arg(2)) { return value; } } } return CategoryOf(Kind); } } public string Arg(int i) { if (i < 0 || i >= A.Length) { return ""; } return A[i] ?? ""; } public static Cat CategoryOf(string kind) { switch (kind) { case "boss": case "raid": return Cat.Battles; case "death": return Cat.Fallen; case "biome": case "newcomer": return Cat.Explorers; case "god_chosen": case "god_legend": case "blot_end": case "blot_failed": return Cat.Gods; case "guild_found": case "guild_level": case "guild_ach": case "guild_banner": case "guild_disband": return Cat.Guilds; case "vv_saga": case "vv_tales": case "vv_tier": return Cat.Villages; default: return Cat.Skald; } } public void Write(ZPackage p) { p.Write(Id); p.Write(Ticks); p.Write(Day); p.Write(Kind ?? ""); p.Write(A.Length); string[] a = A; foreach (string text in a) { p.Write(text ?? ""); } } public static Entry Read(ZPackage p) { Entry entry = new Entry(); entry.Id = p.ReadLong(); entry.Ticks = p.ReadLong(); entry.Day = p.ReadInt(); entry.Kind = p.ReadString(); int num = Math.Min(p.ReadInt(), 32); entry.A = new string[num]; for (int i = 0; i < num; i++) { entry.A[i] = p.ReadString(); } return entry; } } public class Hero { public long Pid; public string Name = ""; public int Bosses; public int Deaths; public int Firsts; public long Joined; public long LastDeathVerse; public void Write(ZPackage p) { p.Write(Pid); p.Write(Name ?? ""); p.Write(Bosses); p.Write(Deaths); p.Write(Firsts); p.Write(Joined); p.Write(LastDeathVerse); } public static Hero Read(ZPackage p) { Hero hero = new Hero(); hero.Pid = p.ReadLong(); hero.Name = p.ReadString(); hero.Bosses = p.ReadInt(); hero.Deaths = p.ReadInt(); hero.Firsts = p.ReadInt(); hero.Joined = p.ReadLong(); hero.LastDeathVerse = p.ReadLong(); return hero; } } public class SagaStats { public int Day; public int Verses; public int Heroes; public int BossKills; public long Since; public readonly List<KeyValuePair<string, int>> TopBosses = new List<KeyValuePair<string, int>>(); public readonly List<KeyValuePair<string, int>> TopDeaths = new List<KeyValuePair<string, int>>(); public readonly List<KeyValuePair<string, int>> TopFirsts = new List<KeyValuePair<string, int>>(); public readonly List<KeyValuePair<string, int>> Bosses = new List<KeyValuePair<string, int>>(); public void Write(ZPackage p) { p.Write(Day); p.Write(Verses); p.Write(Heroes); p.Write(BossKills); p.Write(Since); List<KeyValuePair<string, int>>[] array = new List<KeyValuePair<string, int>>[4] { TopBosses, TopDeaths, TopFirsts, Bosses }; foreach (List<KeyValuePair<string, int>> list in array) { p.Write(list.Count); foreach (KeyValuePair<string, int> item in list) { p.Write(item.Key ?? ""); p.Write(item.Value); } } } public static SagaStats Read(ZPackage p) { SagaStats sagaStats = new SagaStats(); sagaStats.Day = p.ReadInt(); sagaStats.Verses = p.ReadInt(); sagaStats.Heroes = p.ReadInt(); sagaStats.BossKills = p.ReadInt(); sagaStats.Since = p.ReadLong(); List<KeyValuePair<string, int>>[] array = new List<KeyValuePair<string, int>>[4] { sagaStats.TopBosses, sagaStats.TopDeaths, sagaStats.TopFirsts, sagaStats.Bosses }; foreach (List<KeyValuePair<string, int>> list in array) { int num = Math.Min(p.ReadInt(), 64); for (int j = 0; j < num; j++) { list.Add(new KeyValuePair<string, int>(p.ReadString(), p.ReadInt())); } } return sagaStats; } } internal static class Hooks { private static readonly FieldInfo LastHit = AccessTools.Field(typeof(Character), "m_lastHit"); private static readonly FieldInfo NView = AccessTools.Field(typeof(Character), "m_nview"); private static readonly FieldInfo KnownBiome = AccessTools.Field(typeof(Player), "m_knownBiome"); [HarmonyPatch(typeof(Player), "OnSpawned")] [HarmonyPostfix] private static void Spawned(Player __instance, bool spawnValkyrie) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { Rpc.ToServer("CH_Hello", spawnValkyrie); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Saga hello: " + ex.Message)); } } [HarmonyPatch(typeof(Character), "OnDeath")] [HarmonyPrefix] private static void CharacterDeath(Character __instance) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)__instance == (Object)null) && __instance.m_boss && !__instance.IsPlayer() && !__instance.IsTamed()) { ZNetView val = (ZNetView)((NView != null) ? /*isinst with value type is only supported in some contexts*/: null); if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner()) { ZPackage val2 = new ZPackage(); val2.Write(Utils.GetPrefabName(((Component)__instance).gameObject)); val2.Write(__instance.m_name ?? ""); val2.Write(((Component)__instance).transform.position); val2.Write(__instance.GetLevel()); Rpc.ToServer("CH_Boss", val2); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Saga boss death: " + ex.Message)); } } [HarmonyPatch(typeof(Player), "OnDeath")] [HarmonyPrefix] private static void PlayerDeath(Player __instance) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Expected I4, but got Unknown if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { HitData val = (HitData)((LastHit != null) ? /*isinst with value type is only supported in some contexts*/: null); int num = ((val != null) ? ((int)val.m_hitType) : 0); string text = ""; string text2 = ""; string text3 = ""; Character val2 = ((val != null) ? val.GetAttacker() : null); if ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)__instance) { Player val3 = (Player)(object)((val2 is Player) ? val2 : null); if ((Object)(object)val3 != (Object)null) { text3 = val3.GetPlayerName(); } else { text = Utils.GetPrefabName(((Component)val2).gameObject); text2 = val2.m_name ?? ""; } } else if ((Object)(object)val2 == (Object)(object)__instance && num < 3) { num = 14; } ZPackage val4 = new ZPackage(); val4.Write(num); val4.Write(text); val4.Write(text2); val4.Write(text3); val4.Write((int)__instance.GetCurrentBiome()); Rpc.ToServer("CH_Death", val4); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Saga death: " + ex.Message)); } } [HarmonyPrefix] [HarmonyPatch(typeof(Player), "AddKnownBiome")] private static void BiomeBefore(Player __instance, BiomeSector biome, out bool __state) { __state = false; try { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && biome != null && !(KnownBiome == null)) { HashSet<string> hashSet = KnownBiome.GetValue(__instance) as HashSet<string>; __state = hashSet != null && !hashSet.Contains(biome.GetName(false)); } } catch { } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "AddKnownBiome")] private static void BiomeAfter(BiomeSector biome, bool __state) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected I4, but got Unknown if (!__state || biome == null) { return; } try { int num = (int)biome.Biome; if (num != 0) { Rpc.ToServer("CH_Biome", num); } } catch { } } [HarmonyPostfix] [HarmonyPatch(typeof(RandEventSystem), "SetRandomEvent")] private static void RaidStarted(RandomEvent ev, Vector3 pos) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (ev == null || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } try { SagaServer.OnRaid(ev, pos); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Saga raid: " + ex.Message)); } } } internal static class Integrations { public static bool Gods; public static bool Guilds; public static bool Villages; private static FieldInfo _gName; private static FieldInfo _gTag; private static bool Server { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } public static void Apply(Harmony h) { Gods = Patch(h, "Gods.GodsServer", "AnnounceAll", "GodsAnnounce", postfix: false); Villages = Patch(h, "VikingsVillage.VillageServer", "AnnounceAll", "VillageAnnounce", postfix: false); Guilds = Patch(h, "Guilds.Guild", "AddLog", "GuildLog", postfix: true); if (Guilds) { Patch(h, "Guilds.GuildServer", "DisbandGuild", "GuildDisband", postfix: false); } Plugin.Log.LogInfo((object)("Saga sources: Gods " + (Gods ? "yes" : "no") + ", Guilds " + (Guilds ? "yes" : "no") + ", Vikings Village " + (Villages ? "yes" : "no") + ".")); } private static bool Patch(Harmony h, string type, string method, string patch, bool postfix) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown try { Type type2 = AccessTools.TypeByName(type); if (type2 == null) { return false; } MethodInfo methodInfo = AccessTools.Method(type2, method, (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogWarning((object)("Saga: " + type + "." + method + " not found — that mod's events are not written.")); return false; } HarmonyMethod val = new HarmonyMethod(typeof(Integrations).GetMethod(patch, BindingFlags.Static | BindingFlags.NonPublic)); if (postfix) { h.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { h.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Saga: could not hook " + type + "." + method + ": " + ex.Message)); return false; } } private static string S(object[] args, int i) { if (args == null || i >= args.Length || args[i] == null) { return ""; } return Convert.ToString(args[i], CultureInfo.InvariantCulture); } private static string After(string s, string prefix) { if (!s.StartsWith(prefix)) { return s; } return s.Substring(prefix.Length); } private static void GodsAnnounce(string key, object[] args) { if (!Server || !Plugin.RecordGods.Value) { return; } try { switch (key) { case "announce_chosen": SagaServer.Record("god_chosen", S(args, 0), After(S(args, 1), "@godg_")); break; case "announce_legend": SagaServer.Record("god_legend", S(args, 0), After(S(args, 1), "@godg_")); break; case "blot_end": SagaServer.Record("blot_end", After(S(args, 0), "@godg_"), S(args, 1)); break; case "blot_failed": SagaServer.Record("blot_failed", After(S(args, 0), "@godg_"), S(args, 1), S(args, 2)); break; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Saga (Gods " + key + "): " + ex.Message)); } } private static void VillageAnnounce(string key, object[] args) { if (!Server || !Plugin.RecordVillages.Value) { return; } try { switch (key) { case "announce_saga": SagaServer.Record("vv_saga", S(args, 0), "~vvq:" + After(S(args, 1), "#q:")); break; case "announce_all_tales": SagaServer.Record("vv_tales", S(args, 0)); break; case "announce_tier": SagaServer.Record("vv_tier", S(args, 0), "~vv:" + After(S(args, 1), "@")); break; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Saga (Vikings Village " + key + "): " + ex.Message)); } } private static void GuildInfo(object g, out string tag, out string name) { tag = (name = ""); if (g != null) { if (_gName == null) { _gName = AccessTools.Field(g.GetType(), "Name"); } if (_gTag == null) { _gTag = AccessTools.Field(g.GetType(), "Tag"); } if (_gName != null) { name = (_gName.GetValue(g) as string) ?? ""; } if (_gTag != null) { tag = (_gTag.GetValue(g) as string) ?? ""; } } } private static void GuildLog(object __instance, object[] __args) { if (!Server || !Plugin.RecordGuilds.Value || __args == null || __args.Length < 6) { return; } try { int num = Convert.ToInt32(__args[0]); GuildInfo(__instance, out var tag, out var name); string text = S(__args, 1); string text2 = S(__args, 5); long num2 = Convert.ToInt64(__args[4]); switch (num) { case 0: SagaServer.Record("guild_found", text, tag, (name.Length > 0) ? name : text2); break; case 18: SagaServer.Record("guild_level", tag, name, num2.ToString()); break; case 20: if (num2 >= 3) { SagaServer.Record("guild_ach", tag, name, "~guilds:ach_" + text2); } break; case 22: if (SagaServer.BannerAllowed(tag)) { SagaServer.Record("guild_banner", text, tag, name); } break; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Saga (Guilds log): " + ex.Message)); } } private static void GuildDisband(object[] __args) { if (!Server || !Plugin.RecordGuilds.Value || __args == null || __args.Length < 1) { return; } try { GuildInfo(__args[0], out var tag, out var name); SagaServer.Record("guild_disband", tag, name); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Saga (Guilds disband): " + ex.Message)); } } } internal static class Refs { private static MethodInfo _guildsT; private static MethodInfo _vvT; private static MethodInfo _godsT; private static MethodInfo _vvQuest; private static MethodInfo _vvTitle; private static FieldInfo _vvCurrent; private static bool _looked; public static string Text(string arg) { if (string.IsNullOrEmpty(arg)) { return ""; } try { if (arg[0] == '$') { return (Localization.instance != null) ? Localization.instance.Localize(arg) : arg.Substring(1); } if (arg[0] != '~') { return arg; } Look(); int num = arg.IndexOf(':'); if (num < 0) { return arg; } string text = arg.Substring(1, num - 1); string text2 = arg.Substring(num + 1); return text switch { "guilds" => Call(_guildsT, text2), "vv" => Call(_vvT, text2), "gods" => Call(_godsT, text2), "vvq" => Quest(text2), _ => text2, }; } catch { return arg; } } private static void Look() { if (!_looked) { _looked = true; _guildsT = LocT("Guilds.Loc"); _vvT = LocT("VikingsVillage.Loc"); _godsT = LocT("Gods.Loc"); Type type = AccessTools.TypeByName("VikingsVillage.Content"); if (type != null) { _vvCurrent = AccessTools.Field(type, "Current"); _vvQuest = AccessTools.Method(type, "Quest", new Type[1] { typeof(string) }, (Type[])null); } Type type2 = AccessTools.TypeByName("VikingsVillage.QuestText"); if (type2 != null) { _vvTitle = AccessTools.Method(type2, "Title", (Type[])null, (Type[])null); } } } private static MethodInfo LocT(string type) { Type type2 = AccessTools.TypeByName(type); if (!(type2 != null)) { return null; } return AccessTools.Method(type2, "T", new Type[1] { typeof(string) }, (Type[])null); } private static string Call(MethodInfo m, string key) { if (m == null) { return Pretty(key); } string text = m.Invoke(null, new object[1] { key }) as string; if (!string.IsNullOrEmpty(text) && !(text == key)) { return text; } return Pretty(key); } private static string Quest(string id) { if (_vvCurrent == null || _vvQuest == null) { return Pretty(id); } object value = _vvCurrent.GetValue(null); object obj = ((value != null) ? _vvQuest.Invoke(value, new object[1] { id }) : null); if (obj == null) { return Pretty(id); } string text = null; try { if (_vvTitle != null) { text = _vvTitle.Invoke(null, new object[1] { obj }) as string; } } catch { } if (string.IsNullOrEmpty(text)) { FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), "Title"); text = ((fieldInfo != null) ? (fieldInfo.GetValue(obj) as string) : null); } if (!string.IsNullOrEmpty(text)) { return text; } return Pretty(id); } private static string Pretty(string key) { int num = key.IndexOf('_'); string text = ((num >= 0 && num < 6) ? key.Substring(num + 1) : key).Replace('_', ' '); if (text.Length <= 0) { return text; } return char.ToUpperInvariant(text[0]) + text.Substring(1); } } public static class ChronicleApi { public static bool IsReady() { return SagaServer.Ready; } public static long Write(string category, string textEn, string textPl) { if (string.IsNullOrEmpty(textEn)) { return 0L; } return SagaServer.Record("api", textEn, string.IsNullOrEmpty(textPl) ? textEn : textPl, (category ?? "skald").ToLowerInvariant())?.Id ?? 0; } } internal static class Loc { private static readonly Dictionary<string, string> En = new Dictionary<string, string> { { "title", "Chronicles of Northarun" }, { "subtitle", "Day {0} · {1} verses · the saga began {2}" }, { "subtitle_empty", "Day {0} · the saga has not begun yet" }, { "cat_0", "All" }, { "cat_1", "Battles" }, { "cat_2", "Fallen" }, { "cat_3", "Explorers" }, { "cat_4", "Gods" }, { "cat_5", "Guilds" }, { "cat_6", "Villages" }, { "cat_7", "Skald" }, { "day", "Day {0}" }, { "older", "Older verses…" }, { "loading", "The skald is reading the stone…" }, { "empty", "Nothing has been carved here yet." }, { "heroes_title", "Heroes of the saga" }, { "h_bosses", "Boss slayers" }, { "h_deaths", "Regulars of Valhalla" }, { "h_firsts", "Pathfinders" }, { "h_bosslist", "Bosses felled" }, { "h_stats", "{0} heroes · {1} boss kills" }, { "h_none", "nobody yet" }, { "hint", "The server carves here every deed of every Viking of Northarun. Admins: saga add <text> · saga del <id> · saga last" }, { "hover_name", "Runestone of the Chronicles" }, { "hover_read", "Read the saga" }, { "hover_latest", "Latest verse:" }, { "piece_desc", "A tall runestone where the skalds carve the saga of Northarun: bosses slain, lands discovered, deaths absurd and glorious. Interact to read." }, { "msg_new", "Saga: {0}" }, { "and", "and" }, { "god_odin", "Odin" }, { "god_thor", "Thor" }, { "god_freya", "Freyja" }, { "god_njord", "Njord" }, { "god_loki", "Loki" }, { "godg_odin", "Odin" }, { "godg_thor", "Thor" }, { "godg_freya", "Freyja" }, { "godg_njord", "Njord" }, { "godg_loki", "Loki" }, { "bin_0", "somewhere in the wilds" }, { "bin_1", "in the Meadows" }, { "bin_8", "in the Black Forest" }, { "bin_2", "in the Swamp" }, { "bin_4", "in the Mountains" }, { "bin_16", "on the Plains" }, { "bin_256", "on the open sea" }, { "bin_512", "in the Mistlands" }, { "bin_32", "in the Ashlands" }, { "bin_64", "in the Deep North" }, { "bnom_1", "the Meadows" }, { "bnom_8", "the Black Forest" }, { "bnom_2", "the Swamp" }, { "bnom_4", "the Mountains" }, { "bnom_16", "the Plains" }, { "bnom_256", "the Ocean" }, { "bnom_512", "the Mistlands" }, { "bnom_32", "the Ashlands" }, { "bnom_64", "the Deep North" }, { "v_boss_first", "For the first time since the world was young, {0} has fallen. The deed belongs to {1} — let the skalds never forget it." }, { "v_boss_0", "{0} fell once more before {1}. The saga now counts {2} such falls." }, { "v_boss_1", "{1} challenged {0}, and the beast did not rise again. It was its {3} death on Northarun." }, { "v_boss_2", "Steel sang and {0} was silenced. The glory goes to {1}." }, { "v_boss_solo_0", "With no shield-brothers and no fear, {1} faced {0} — and walked away. It was the {3} fall of the beast." }, { "v_boss_solo_1", "{1} needed no war band: {0} fell to a single Viking." }, { "v_d_pvp_0", "Viking blood spilled by a Viking hand: {3} slew {0} {1}. Such things are not forgotten." }, { "v_d_pvp_1", "{3} and {0} settled their quarrel {1}. Only {3} walked away." }, { "v_d_weak_0", "{0} met their end {1} at the hands of a mighty {2}. The skalds will tell it with a straight face, if they can." }, { "v_d_weak_1", "Let it be written that {0} was slain {1} by a fearsome {2}. Valhalla laughed for an hour." }, { "v_d_env3_0", "The ground {1} rose to meet {0} far faster than expected. Odin's ravens noted the height." }, { "v_d_env3_1", "{0} trusted the cliffs {1} more than the cliffs deserved. The fall was long; the landing, final." }, { "v_d_env4_0", "{0} tried to drink the sea {1} and lost. Rán's nets are fuller tonight." }, { "v_d_env4_1", "The waves {1} closed over {0}. Rán keeps what she takes." }, { "v_d_env5_0", "{0} came too close to the fire {1} and became part of it." }, { "v_d_env6_0", "The cold {1} crept into the bones of {0} and stayed there." }, { "v_d_env7_0", "Poison did quietly {1} what no blade could: {0} is no more." }, { "v_d_env8_0", "The waters {1} proved deadlier than any foe for {0}." }, { "v_d_env9_0", "{0} lit a fire without a smoke hole. The smoke did the rest." }, { "v_d_env10_0", "{0} sailed to the very edge of the world and learned that it is real." }, { "v_d_env11_0", "Something heavy fell on {0} {1}. The saga does not say what, only that it was heavy." }, { "v_d_env12_0", "{0} was run down {1} by their own cart. The cart shows no remorse." }, { "v_d_env13_0", "The forest takes its revenge: a tree felled with great skill by {0} landed exactly on {0}." }, { "v_d_env13_1", "{0} shouted \"Timber!\" a heartbeat too late {1}." }, { "v_d_env14_0", "{0} faced the most dangerous foe of all — their own hand — and lost." }, { "v_d_env15_0", "The roof that was meant to shelter {0} came down upon them {1}." }, { "v_d_env16_0", "{0} was cut down {1} by a ballista. Somebody built it well." }, { "v_d_env17_0", "The ship turned on its own crew {1}: {0} did not survive the voyage." }, { "v_d_env18_0", "A stalactite chose {0} {1}. Caves remember who disturbs them." }, { "v_d_env19_0", "A catapult stone found {0} {1}." }, { "v_d_env20_0", "Fire rained from the sky {1} and found {0}." }, { "v_d_env21_0", "{0} went for a swim in the boiling sea of the Ashlands. Once." }, { "v_d_env22_0", "{0} stepped into the lava {1}. The lava did not step back." }, { "v_d_env23_0", "{0} was fed to the obliterator. Nothing was left to bury." }, { "v_d_env24_0", "A drawbridge closed on {0} {1}. Mind the gap." }, { "v_d_foe_0", "{0} fell {1}, slain by {2}." }, { "v_d_foe_1", "{2} ended the journey of {0} {1}." }, { "v_d_other_0", "{0} died {1} in a way the skalds still cannot explain." }, { "v_d_milestone", "That was their {0} death; the Valkyries greet them by name now." }, { "v_biome_land_0", "{0} was the first of all Vikings to set foot {1}. Where one goes, others will follow." }, { "v_biome_land_1", "No Viking of Northarun had seen {2} before {0}. Now the way is known." }, { "v_biome_ocean_0", "{0} was the first to sail out onto the open sea. Beyond the horizon, Njord waits." }, { "v_newcomer_0", "A Valkyrie descends over Northarun and leaves {0} on its shores — the {1} soul to walk these lands." }, { "v_newcomer_1", "The Valkyries brought a new warrior: {0}. The saga of Northarun has its {1} hero." }, { "v_raid_0", "Trouble came for {0} {1}: <i>{2}</i>" }, { "v_raid_nobody_0", "Trouble stirred {1}: <i>{2}</i>" }, { "v_god_chosen_0", "{0} has joined the Chosen of {2}. The god's gaze rests upon them." }, { "v_god_legend_0", "{0} has become a Legend of {2}. Few mortals ever stand so close to the gods." }, { "v_blot_end_0", "The Blót of {1} is over: {2} Vikings brought their gifts, and the god was pleased." }, { "v_blot_failed_0", "The Blót of {1} ended with gifts worth {2} of the {3} the god demanded. {0} turned away, disappointed." }, { "v_guild_found_0", "{0} founded the guild <b>[{1}] {2}</b>. A new banner rises over Northarun." }, { "v_guild_level_0", "The guild <b>[{0}] {1}</b> grows in strength and reaches level {2}." }, { "v_guild_ach_0", "The guild <b>[{0}] {1}</b> earned golden glory: {2}." }, { "v_guild_banner_0", "{0} planted the banner of <b>[{1}] {2}</b> and claimed the land around it." }, { "v_guild_disband_0", "The guild <b>[{0}] {1}</b> is no more. Its banner has been lowered." }, { "v_vv_saga_0", "{0} closed a chapter of the village saga: <i>{1}</i>." }, { "v_vv_tales_0", "{0} has heard every tale the village skalds know. Few remember so much." }, { "v_vv_tier_0", "The villagers now call {0} by a new name: <b>{1}</b>." }, { "v_skald", "The skald {1} carves: <i>{0}</i>" }, { "v_skald_anon", "The skald carves: <i>{0}</i>" } }; private static readonly Dictionary<string, string> Pl = new Dictionary<string, string> { { "title", "Kroniki Northarun" }, { "subtitle", "Dzień {0} · wersów: {1} · saga trwa od {2}" }, { "subtitle_empty", "Dzień {0} · saga jeszcze się nie zaczęła" }, { "cat_0", "Wszystko" }, { "cat_1", "Bitwy" }, { "cat_2", "Polegli" }, { "cat_3", "Odkrywcy" }, { "cat_4", "Bogowie" }, { "cat_5", "Gildie" }, { "cat_6", "Wioski" }, { "cat_7", "Skald" }, { "day", "Dzień {0}" }, { "older", "Starsze wersy…" }, { "loading", "Skald odczytuje kamień…" }, { "empty", "Nic tu jeszcze nie wyryto." }, { "heroes_title", "Bohaterowie sagi" }, { "h_bosses", "Pogromcy bossów" }, { "h_deaths", "Stali bywalcy Valhalli" }, { "h_firsts", "Przecierający szlaki" }, { "h_bosslist", "Pokonani bossowie" }, { "h_stats", "Bohaterów: {0} · zabitych bossów: {1}" }, { "h_none", "jeszcze nikt" }, { "hint", "Serwer ryje tu każdy czyn każdego Wikinga Northarun. Admini: saga add <tekst> · saga del <id> · saga last" }, { "hover_name", "Kamień Kronik" }, { "hover_read", "Czytaj sagę" }, { "hover_latest", "Najnowszy wers:" }, { "piece_desc", "Wysoki kamień runiczny, na którym skaldowie ryją sagę Northarun: pokonanych bossów, odkryte krainy, śmierci absurdalne i chwalebne. Użyj, by czytać." }, { "msg_new", "Saga: {0}" }, { "and", "i" }, { "god_odin", "Odyn" }, { "god_thor", "Thor" }, { "god_freya", "Freja" }, { "god_njord", "Njord" }, { "god_loki", "Loki" }, { "godg_odin", "Odyna" }, { "godg_thor", "Thora" }, { "godg_freya", "Frei" }, { "godg_njord", "Njorda" }, { "godg_loki", "Lokiego" }, { "bin_0", "gdzieś w dziczy" }, { "bin_1", "na Łąkach" }, { "bin_8", "w Czarnym Lesie" }, { "bin_2", "na Bagnach" }, { "bin_4", "w Górach" }, { "bin_16", "na Równinach" }, { "bin_256", "na otwartym morzu" }, { "bin_512", "w Mglistych Ziemiach" }, { "bin_32", "w Popielnych Ziemiach" }, { "bin_64", "na Dalekiej Północy" }, { "bnom_1", "Łąki" }, { "bnom_8", "Czarny Las" }, { "bnom_2", "Bagna" }, { "bnom_4", "Góry" }, { "bnom_16", "Równiny" }, { "bnom_256", "Ocean" }, { "bnom_512", "Mgliste Ziemie" }, { "bnom_32", "Popielne Ziemie" }, { "bnom_64", "Daleka Północ" }, { "v_boss_first", "Po raz pierwszy, odkąd świat jest młody, pada {0}. Ten czyn należy do: {1}. Niech skaldowie nigdy o nim nie zapomną." }, { "v_boss_0", "{0} znów pada. Zwycięzcy: {1}. Saga liczy już {2} takich upadków." }, { "v_boss_1", "Wyzwanie zostaje rzucone, a {0} nie powstaje więcej. Chwała dla: {1}. To już {3} śmierć bestii na Northarun." }, { "v_boss_2", "Stal śpiewa i {0} milknie. Chwała dla: {1}." }, { "v_boss_solo_0", "Bez tarczowych braci i bez lęku: {1} mierzy się z bestią — {0} — i odchodzi zwycięsko. To {3} upadek bestii." }, { "v_boss_solo_1", "{1} nie potrzebuje drużyny: {0} pada z ręki jednego Wikinga." }, { "v_d_pvp_0", "Krew Wikinga przelana ręką Wikinga: {3} pokonuje {0} {1}. Takich rzeczy się nie zapomina." }, { "v_d_pvp_1", "{3} i {0} rozstrzygają spór {1}. Odchodzi tylko {3}." }, { "v_d_weak_0", "{0} ginie {1} z ręki groźnego przeciwnika: {2}. Skaldowie spróbują opowiedzieć to z kamienną twarzą." }, { "v_d_weak_1", "Niech zostanie zapisane, że {1} straszliwy przeciwnik — {2} — pokonuje wędrowca imieniem {0}. Valhalla śmieje się do dziś." }, { "v_d_env3_0", "{0} spada z wysoka {1} — ziemia okazuje się twardsza niż odwaga. Kruki Odyna zanotowały wysokość." }, { "v_d_env3_1", "Klify {1} są mniej godne zaufania, niż sądzi {0}. Upadek jest długi, lądowanie — ostateczne." }, { "v_d_env4_0", "{0} próbuje wypić morze {1} i przegrywa. Sieci Rán są dziś pełniejsze." }, { "v_d_env4_1", "Fale {1} zamykają się nad głową: {0}. Rán zatrzymuje to, co weźmie." }, { "v_d_env5_0", "{0} podchodzi zbyt blisko ognia {1} i staje się jego częścią." }, { "v_d_env6_0", "Mróz {1} wkrada się w kości i już z nich nie wychodzi. Zamarza: {0}." }, { "v_d_env7_0", "Trucizna robi po cichu {1} to, czego nie zdołało żadne ostrze. Odchodzi {0}." }, { "v_d_env8_0", "Wody {1} okazują się groźniejsze niż każdy wróg. Ofiara: {0}." }, { "v_d_env9_0", "{0} rozpala ogień bez otworu na dym. Dym robi resztę." }, { "v_d_env10_0", "{0} żegluje na sam skraj świata i przekonuje się, że skraj istnieje naprawdę." }, { "v_d_env11_0", "Coś ciężkiego spada {1} prosto na: {0}. Saga nie mówi co — tylko że było ciężkie." }, { "v_d_env12_0", "Własny wóz przejeżdża {0} {1}. Wóz nie okazuje skruchy." }, { "v_d_env13_0", "Las bierze odwet: drzewo ścięte z wielką wprawą przez {0} spada dokładnie na {0}." }, { "v_d_env13_1", "{0} krzyczy „Uwaga, drzewo!” o jedno uderzenie serca za późno {1}." }, { "v_d_env14_0", "{0} staje do walki z najgroźniejszym wrogiem — własną ręką — i przegrywa." }, { "v_d_env15_0", "Dach, który miał chronić, wali się {1}. Pod gruzami: {0}." }, { "v_d_env16_0", "Balista ścina {0} {1}. Ktoś ją dobrze zbudował." }, { "v_d_env17_0", "Statek zwraca się przeciw własnej załodze {1}. Rejsu nie przeżywa: {0}." }, { "v_d_env18_0", "Stalaktyt wybiera {1} swój cel: {0}. Jaskinie pamiętają, kto je niepokoi." }, { "v_d_env19_0", "Kamień z katapulty znajduje {1} swój cel: {0}." }, { "v_d_env20_0", "Ogień spada z nieba {1} i znajduje: {0}." }, { "v_d_env21_0", "{0} kąpie się we wrzącym morzu Popielnych Ziem. Raz." }, { "v_d_env22_0", "{0} wchodzi w lawę {1}. Lawa się nie cofa." }, { "v_d_env23_0", "{0} trafia do niszczarki. Nie zostaje nic do pochowania." }, { "v_d_env24_0", "Zwodzony most zamyka się {1} na: {0}." }, { "v_d_foe_0", "{0} ginie {1}. Zabójca: {2}." }, { "v_d_foe_1", "Wędrówka dobiega końca {1} — {2} pokonuje: {0}." }, { "v_d_other_0", "{0} ginie {1} w okolicznościach, których skaldowie wciąż nie potrafią wyjaśnić." }, { "v_d_milestone", "To już {0} śmierć — Walkirie witają po imieniu." }, { "v_biome_land_0", "Pierwsze stopy, które stanęły {1}, należą do: {0}. Gdzie jeden ruszy, inni pójdą śladem." }, { "v_biome_land_1", "{2}: żaden Wiking Northarun nie oglądał wcześniej tej krainy. Drogę wskazuje {0}." }, { "v_biome_ocean_0", "Pierwsza łódź na otwartym morzu — za sterem {0}. Za horyzontem czeka Njord." }, { "v_newcomer_0", "Walkiria zstępuje nad Northarun i zostawia na brzegu nowego wędrowca: {0} — {1} dusza w tych krainach." }, { "v_newcomer_1", "Walkirie przynoszą nowego wojownika: {0}. Saga Northarun ma już {1} bohatera." }, { "v_raid_0", "Kłopoty {1}! Przychodzą po: {0}. <i>{2}</i>" }, { "v_raid_nobody_0", "Kłopoty {1}! <i>{2}</i>" }, { "v_god_chosen_0", "{0} wstępuje w krąg Wybranych {2}. Wzrok boga spoczywa na tym Wikingu." }, { "v_god_legend_0", "{0} staje się Legendą {2}. Niewielu śmiertelników staje tak blisko bogów." }, { "v_blot_end_0", "Blót ku czci {1} dobiega końca: dary składa {2} Wikingów, a bóg jest zadowolony." }, { "v_blot_failed_0", "Blót ku czci {1} kończy się darami wartymi {2} z {3}, których żądał bóg. {0} z rozczarowaniem odwraca wzrok." }, { "v_guild_found_0", "{0} zakłada gildię <b>[{1}] {2}</b>. Nad Northarun wznosi się nowy sztandar." }, { "v_guild_level_0", "Gildia <b>[{0}] {1}</b> rośnie w siłę i osiąga poziom {2}." }, { "v_guild_ach_0", "Gildia <b>[{0}] {1}</b> zdobywa złotą chwałę: {2}." }, { "v_guild_banner_0", "{0} wbija sztandar <b>[{1}] {2}</b> i obejmuje we władanie okoliczne ziemie." }, { "v_guild_disband_0", "Gildia <b>[{0}] {1}</b> przestaje istnieć. Jej sztandar zostaje opuszczony." }, { "v_vv_saga_0", "{0} zamyka rozdział sagi wioski: <i>{1}</i>." }, { "v_vv_tales_0", "{0} zna już każdą opowieść skaldów z wiosek. Niewielu pamięta tak wiele." }, { "v_vv_tier_0", "Mieszkańcy wiosek nazywają teraz {0} nowym mianem: <b>{1}</b>." }, { "v_skald", "Skald {1} ryje w kamieniu: <i>{0}</i>" }, { "v_skald_anon", "Skald ryje w kamieniu: <i>{0}</i>" } }; public static bool IsPolish { get { if (Plugin.Language == null) { return false; } if (Plugin.Language.Value == SagaLanguage.Polish) { return true; } if (Plugin.Language.Value == SagaLanguage.English) { return false; } try { return Localization.instance != null && Localization.instance.GetSelectedLanguage() == "Polish"; } catch { return false; } } } public static bool Has(bool pl, string key) { if (!(pl ? Pl : En).ContainsKey(key)) { return En.ContainsKey(key); } return true; } public static string Get(bool pl, string key) { if ((pl ? Pl : En).TryGetValue(key, out var value)) { return value; } if (En.TryGetValue(key, out value)) { return value; } return key; } public static string Get(bool pl, string key, params object[] args) { string text = Get(pl, key); if (args == null || args.Length == 0) { return text; } try { return string.Format(text, args); } catch { return text; } } public static string T(string key) { return Get(IsPolish, key); } public static string T(string key, params object[] args) { return Get(IsPolish, key, args); } } internal static class Rpc { public const string Hello = "CH_Hello"; public const string Boss = "CH_Boss"; public const string Death = "CH_Death"; public const string Biome = "CH_Biome"; public const string Request = "CH_Request"; public const string Admin = "CH_Admin"; public const string Page = "CH_Page"; public const string New = "CH_New"; public const string Msg = "CH_Msg"; public static bool IsServer { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } public static void Register() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.Register<bool>("CH_Hello", (Action<long, bool>)SagaServer.RPC_Hello); instance.Register<ZPackage>("CH_Boss", (Action<long, ZPackage>)SagaServer.RPC_Boss); instance.Register<ZPackage>("CH_Death", (Action<long, ZPackage>)SagaServer.RPC_Death); instance.Register<int>("CH_Biome", (Action<long, int>)SagaServer.RPC_Biome); instance.Register<ZPackage>("CH_Request", (Action<long, ZPackage>)SagaServer.RPC_Request); instance.Register<string>("CH_Admin", (Action<long, string>)SagaServer.RPC_Admin); instance.Register<ZPackage>("CH_Page", (Action<long, ZPackage>)SagaClient.RPC_Page); instance.Register<ZPackage>("CH_New", (Action<long, ZPackage>)SagaClient.RPC_New); instance.Register<string>("CH_Msg", (Action<long, string>)SagaClient.RPC_Msg); } } public static long ServerId() { if ((Object)(object)ZNet.instance == (Object)null) { return 0L; } if (ZNet.instance.IsServer()) { return ZDOMan.GetSessionID(); } return ZNet.instance.GetServerPeer()?.m_uid ?? 0; } public static void ToServer(string method, params object[] args) { long num = ServerId(); if (ZRoutedRpc.instance != null && num != 0) { ZRoutedRpc.instance.InvokeRoutedRPC(num, method, args); } } public static bool Resolve(long sender, out long pid, out string name) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) pid = 0L; name = ""; if (sender == ZDOMan.GetSessionID()) { if ((Object)(object)Player.m_localPlayer == (Object)null) { return false; } pid = Player.m_localPlayer.GetPlayerID(); name = Player.m_localPlayer.GetPlayerName(); return pid != 0; } ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer == null) { return false; } ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); if (zDO != null) { pid = zDO.GetLong(ZDOVars.s_playerID, 0L); } if (pid == 0) { pid = peer.m_playerID; } name = peer.m_playerName ?? ""; return pid != 0; } public static bool IsAdminSender(long sender) { try { if (sender == ZDOMan.GetSessionID()) { return ZNet.instance.LocalPlayerIsAdminOrHost(); } ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer == null || peer.m_socket == null) { return false; } string text = peer.m_socket.GetHostName() ?? ""; if (ZNet.instance.IsAdmin(text)) { return true; } int num = text.IndexOf('_'); string text2 = ((num >= 0) ? text.Substring(num + 1) : text); return text2.Length > 0 && (ZNet.instance.IsAdmin(text2) || ZNet.instance.IsAdmin("Steam_" + text2)); } catch { return false; } } public static List<OnlinePlayer> Online() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) List<OnlinePlayer> list = new List<OnlinePlayer>(); if ((Object)(object)ZNet.instance == (Object)null) { return list; } if ((Object)(object)Player.m_localPlayer != (Object)null && !ZNet.instance.IsDedicated()) { OnlinePlayer onlinePlayer = new OnlinePlayer(); onlinePlayer.Pid = Player.m_localPlayer.GetPlayerID(); onlinePlayer.Uid = ZDOMan.GetSessionID(); onlinePlayer.Name = Player.m_localPlayer.GetPlayerName(); onlinePlayer.Pos = ((Component)Player.m_localPlayer).transform.position; list.Add(onlinePlayer); } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (!((ZDOID)(ref peer.m_characterID)).IsNone()) { OnlinePlayer onlinePlayer2 = new OnlinePlayer(); ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); if (zDO != null) { onlinePlayer2.Pid = zDO.GetLong(ZDOVars.s_playerID, 0L); onlinePlayer2.Pos = zDO.GetPosition(); } else { onlinePlayer2.Pos = peer.m_refPos; } if (onlinePlayer2.Pid == 0) { onlinePlayer2.Pid = peer.m_playerID; } if (onlinePlayer2.Pid != 0) { onlinePlayer2.Uid = peer.m_uid; onlinePlayer2.Name = peer.m_playerName ?? ""; list.Add(onlinePlayer2); } } } return list; } } internal class OnlinePlayer { public long Pid; public long Uid; public string Name = ""; public Vector3 Pos; } public enum SagaLanguage { Auto, English, Polish } public enum DeathMode { Notable, All, Off } [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("adrian.valheim.chronicles", "Chronicles of Northarun", "0.1.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGUID = "adrian.valheim.chronicles"; public const string PluginName = "Chronicles of Northarun"; public const string PluginVersion = "0.1.1"; public const string DiscordUrl = "https://discord.gg/6x9j6N58m"; public static Plugin Instance; public static ManualLogSource Log; public static ConfigEntry<SagaLanguage> Language; public static ConfigEntry<bool> ShowNewEntries; public static ConfigEntry<bool> RunestoneLight; public static ConfigEntry<int> MaxEntries; public static ConfigEntry<bool> RecordBosses; public static ConfigEntry<float> BossPartyRange; public static ConfigEntry<DeathMode> Deaths; public static ConfigEntry<float> DeathCooldownMinutes; public static ConfigEntry<int> DeathVersesPerHour; public static ConfigEntry<float> DeathChance; public static ConfigEntry<string> DeathMilestones; public static ConfigEntry<string> WeakCreatures; public static ConfigEntry<bool> RecordBiomes; public static ConfigEntry<bool> RecordNewcomers; public static ConfigEntry<bool> RecordRaids; public static ConfigEntry<bool> RecordGods; public static ConfigEntry<bool> RecordGuilds; public static ConfigEntry<bool> RecordVillages; public static ConfigEntry<string> DiscordWebhook; public static ConfigEntry<SagaLanguage> DiscordLanguage; public static ConfigEntry<string> DiscordCategories; public static ConfigEntry<string> Recipe; public static ConfigEntry<string> Station; public static ConfigEntry<string> HammerCategory; private Harmony _harmony; private float _serverTick; private void Awake() { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Expected O, but got Unknown //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Expected O, but got Unknown //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Expected O, but got Unknown //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Expected O, but got Unknown //IL_03e2: Unknown result type (might be due to invalid IL or missing references) //IL_03ec: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Language = ((BaseUnityPlugin)this).Config.Bind<SagaLanguage>("1 - Client", "Language", SagaLanguage.Auto, "Language of the saga window and messages: Auto (follows the game language), English or Polish."); Language.SettingChanged += delegate { SagaUI.Rebuild(); }; ShowNewEntries = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Client", "ShowNewEntries", true, "Show new verses of the saga (bosses, deaths, discoveries, raids, the skald) in the top-left corner and in chat. Gods, Guilds and Vikings Village announce their own events already, so those are not repeated."); RunestoneLight = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Client", "RunestoneLight", true, "The runes of the Chronicles runestone cast a warm light. Turn off on weak computers."); MaxEntries = ((BaseUnityPlugin)this).Config.Bind<int>("2 - Saga", "MaxEntries", 5000, new ConfigDescription("Most verses kept in the saga; the oldest are forgotten first.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 100000), new object[0])); RecordBosses = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordBosses", true, "Write every boss kill into the saga (who, how many, the first kill on the server)."); BossPartyRange = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Saga", "BossPartyRange", 80f, new ConfigDescription("Players within this many metres of a dying boss share the glory.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 300f), new object[0])); Deaths = ((BaseUnityPlugin)this).Config.Bind<DeathMode>("2 - Saga", "Deaths", DeathMode.Notable, "Deaths in the saga: Notable = only absurd ones (falls, trees, drowning, own cart, weak creatures, another Viking, milestones), All = every death, Off = none."); DeathCooldownMinutes = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Saga", "DeathPlayerCooldownMinutes", 90f, new ConfigDescription("Real minutes between two death verses of the same player (milestones are always written).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1440f), new object[0])); DeathVersesPerHour = ((BaseUnityPlugin)this).Config.Bind<int>("2 - Saga", "DeathVersesPerHour", 3, new ConfigDescription("Most death verses for the whole server in one real hour (milestones not counted). 0 = no limit.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), new object[0])); DeathChance = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Saga", "DeathChance", 0.2f, new ConfigDescription("Chance that a common death (fall, drowning, fire, frost, poison, lava…) gets a verse. Rare ones (a tree, own cart, the edge of the world, a Neck, another Viking…) are always written if the limits allow.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[0])); DeathMilestones = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Saga", "DeathMilestones", "10,25,50,100,200,500,1000", "Numbers of deaths of one player that are always worth a verse (comma separated)."); WeakCreatures = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Saga", "WeakCreatures", "Neck,Greyling,Boar,Boar_piggy,Deer,Hare,Hen,Chicken,Leech,Wolf_cub,Lox_Calf", "Creature prefabs whose victory over a Viking is embarrassing enough for the saga (comma separated)."); RecordBiomes = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordBiomes", true, "Write the first Viking of the server to set foot in each biome."); RecordNewcomers = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordNewcomers", true, "Write the first arrival of every new character on the server."); RecordRaids = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordRaids", true, "Write raids and other world events (the ground shakes, the forest moves...) with the Viking they came for."); RecordGods = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordGods", true, "Favor of the Gods: Chosen and Legends of the gods, the end of every Blót."); RecordGuilds = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordGuilds", true, "Guilds: founding, levels, gold achievements, banners, disbanding."); RecordVillages = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Saga", "RecordVillages", true, "Vikings Village: chapters of the village saga, all tales heard, the Kin and Hero reputation."); DiscordWebhook = ((BaseUnityPlugin)this).Config.Bind<string>("3 - Discord", "Webhook", "", "Optional Discord webhook URL (server only) — every new verse of the chosen categories is posted to the channel."); DiscordLanguage = ((BaseUnityPlugin)this).Config.Bind<SagaLanguage>("3 - Discord", "Language", SagaLanguage.English, "Language of the verses posted to Discord (Auto = English)."); DiscordCategories = ((BaseUnityPlugin)this).Config.Bind<string>("3 - Discord", "Categories", "battles,fallen,explorers,gods,guilds,villages,skald", "Categories posted to Discord (comma separated): battles, fallen, explorers, gods, guilds, villages, skald."); Recipe = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Runestone", "Recipe", "Stone:40,Coal:10", "Build cost of the Chronicles runestone (Prefab:amount, comma separated). Restart needed."); Station = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Runestone", "CraftingStation", "piece_workbench", "Crafting station that must be nearby to build the runestone (empty = none). Restart needed."); HammerCategory = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Runestone", "HammerCategory", "Misc", "Hammer tab of the runestone (Misc, Crafting, Building, Furniture or a new tab name). Restart needed."); _harmony = new Harmony("adrian.valheim.chronicles"); _harmony.PatchAll(typeof(Patches)); _harmony.PatchAll(typeof(Hooks)); Integrations.Apply(_harmony); Runestone.Register(); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new SagaAdminCommand()); Log.LogInfo((object)"Chronicles of Northarun 0.1.1 loaded."); } private void Update() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { _serverTick += Time.deltaTime; if (_serverTick >= 1f) { _serverTick = 0f; SagaServer.Tick(); } } if (GUIManager.IsHeadless()) { return; } if ((Object)(object)Player.m_localPlayer == (Object)null) { if (SagaUI.IsOpen) { SagaUI.Close(); } } else if (SagaUI.IsOpen) { bool keyDown = ZInput.GetKeyDown((KeyCode)27, false); bool flag = ZInput.GetButtonDown("Use") && Time.frameCount > SagaUI.OpenedFrame + 1; if (keyDown || flag || ((Character)Player.m_localPlayer).IsDead() || SagaUI.TooFar()) { SagaUI.Close(); } } } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } } } internal static class Patches { [HarmonyPatch(typeof(Game), "Start")] [HarmonyPostfix] private static void GameStart() { Rpc.Register(); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { SagaServer.EnsureLoaded(); } } [HarmonyPostfix] [HarmonyPatch(typeof(ZNet), "OnDestroy")] private static void ZNetDestroy() { SagaServer.Unload(); SagaClient.Reset(); if (SagaUI.IsOpen) { SagaUI.Close(); } } [HarmonyPatch(typeof(Menu), "Update")] [HarmonyPrefix] private static bool MenuUpdate() { if (!SagaUI.IsOpen) { return SagaUI.ClosedFrame != Time.frameCount; } return false; } } internal class SagaAdminCommand : ConsoleCommand { public override string Name => "saga"; public override string Help => "Chronicles of Northarun (server admins): add <text> (a verse from the skald) | del <id> | last [n] (recent verses with their ids)"; public override void Run(string[] args) { if (ZRoutedRpc.instance == null || Rpc.ServerId() == 0) { Console.instance.Print("Not connected to a world."); return; } ZRoutedRpc.instance.InvokeRoutedRPC(Rpc.ServerId(), "CH_Admin", new object[1] { string.Join(" ", args) }); } } internal static class Runestone { public const string PrefabName = "Chronicles_Runestone"; public static readonly Color Glow = new Color(1f, 0.62f, 0.26f); private static readonly string[] BaseCandidates = new string[3] { "stone_wall_2x1", "stone_wall_1x1", "stone_wall_4x2" }; public static void Register() { PrefabManager.OnVanillaPrefabsAvailable += Create; } private static void Create() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Expected O, but got Unknown PrefabManager.OnVanillaPrefabsAvailable -= Create; string text = null; string[] baseCandidates = BaseCandidates; foreach (string text2 in baseCandidates) { if ((Object)(object)PrefabManager.Instance.GetPrefab(text2) != (Object)null) { text = text2; break; } } if (text == null) { Plugin.Log.LogError((object)"No vanilla stone piece found to build the runestone from — the runestone is not available."); return; } try { PieceConfig val = new PieceConfig(); val.Name = Loc.T("hover_name"); val.Description = Loc.T("piece_desc"); val.PieceTable = "Hammer"; val.Category = (string.IsNullOrEmpty(Plugin.HammerCategory.Value) ? "Misc" : Plugin.HammerCategory.Value.Trim()); if (!string.IsNullOrEmpty(Plugin.Station.Value)) { val.CraftingStation = Plugin.Station.Value.Trim(); } string[] array = Plugin.Recipe.Value.Split(','); foreach (string text3 in array) { string[] array2 = text3.Split(':'); if (array2.Length == 2 && int.TryParse(array2[1].Trim(), out var result) && result > 0) { val.AddRequirement(array2[0].Trim(), result, true); } } CustomPiece val2 = new CustomPiece("Chronicles_Runestone", text, val); GameObject piecePrefab = val2.PiecePrefab; if ((Object)(object)piecePrefab == (Object)null) { Plugin.Log.LogError((object)("Could not clone " + text + " for the runestone.")); return; } if (!RuneModel.Apply(piecePrefab)) { Plugin.Log.LogError((object)"Runestone model could not be loaded — runestone skipped."); return; } piecePrefab.AddComponent<RunestoneSaga>(); PieceManager.Instance.AddPiece(val2); } catch (Exception ex) { Plugin.Log.LogError((object)("Runestone registration failed: " + ex)); } } } public class RunestoneSaga : MonoBehaviour, Hoverable, Interactable { private ZNetView _nv; private Light[] _lights = (Light[])(object)new Light[0]; private float[] _base = new float[0]; private void Awake() { _nv = ((Component)this).GetComponent<ZNetView>(); } private void Start() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_nv == (Object)null || !_nv.IsValid() || GUIManager.IsHeadless() || !Plugin.RunestoneLight.Value || RuneModel.Lights.Count == 0) { return; } List<Light> list = new List<Light>(); foreach (Vector3 light in RuneModel.Lights) { GameObject val = new GameObject("ChroniclesLight"); val.transform.SetParent(((Component)this).transform, false); val.transform.localPosition = light; Light val2 = val.AddComponent<Light>(); val2.type = (LightType)2; val2.color = Runestone.Glow; val2.intensity = 0.9f; val2.range = 4.5f; val2.shadows = (LightShadows)0; list.Add(val2); } _lights = list.ToArray(); _base = new float[_lights.Length]; for (int i = 0; i < _lights.Length; i++) { _base[i] = _lights[i].intensity; } } private void Update() { if (_lights.Length == 0) { return; } float time = Time.time; for (int i = 0; i < _lights.Length; i++) { if ((Object)(object)_lights[i] != (Object)null) { _lights[i].intensity = _base[i] * (0.85f + 0.2f * Mathf.PerlinNoise(time * 1.3f + (float)i * 5f, 0.4f)); } } } public float GetHoverOffset() { return 0f; } public string GetHoverName() { return Loc.T("hover_name"); } public string GetHoverText() { string text = "<color=#FFB75E>" + Loc.T("hover_name") + "</color>"; text = text + "\n[<color=yellow><b>$KEY_Use</b></color>] " + Loc.T("hover_read"); Entry latest = SagaClient.Latest; if (latest != null) { string text2 = SagaServer.Strip(SagaText.Verse(latest, Loc.IsPolish)); if (text2.Length > 140) { text2 = text2.Substring(0, 137) + "…"; } string text3 = text; text = text3 + "\n<color=#A89F8A>" + Loc.T("hover_latest") + "</color>\n<size=14><color=#D8CCB0>" + text2 + "</color></size>"; } return Localization.instance.Localize(text); } public bool Interact(Humanoid user, bool hold, bool alt) { if (hold || (Object)(object)user != (Object)(object)Player.m_localPlayer) { return false; } SagaUI.Open(this); return true; } public bool UseItem(Humanoid user, ItemData item) { return false; } } internal static class RuneModel { private const float Emission = 1.5f; public static Mesh Mesh; public static Material[] Materials; public static readonly List<Vector3> BoxCenters = new List<Vector3>(); public static readonly List<Vector3> BoxSizes = new List<Vector3>(); public static readonly List<Vector3> Lights = new List<Vector3>(); public static Sprite Icon; private static Texture2D _white; internal static byte[] Resource(string name) { Assembly assembly = typeof(RuneModel).Assembly; using Stream stream = assembly.GetManifestResourceStream("Chronicles." + name); if (stream == null) { return null; } byte[] array = new byte[stream.Length]; for (int i = 0; i < array.Length; i += stream.Read(array, i, array.Length - i)) { } return array; } public static Texture2D LoadTexture(string name, bool mips, bool linear) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown byte[] array = Resource(name); if (array == null) { return null; } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, mips, linear); ImageConversion.LoadImage(val, array, false); ((Object)val).name = "Chronicles_" + name; return val; } public static Sprite LoadSprite(string name) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) Texture2D val = LoadTexture(name, mips: false, linear: false); if ((Object)(object)val == (Object)null) { return null; } ((Texture)val).wrapMode = (TextureWrapMode)1; Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); ((Object)val2).name = "Chronicles_" + name; return val2; } private static bool Load(Material template) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Expected O, but got Unknown //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Mesh != (Object)null) { return true; } byte[] array = Resource("runestone.mesh"); if (array == null || (Object)(object)template == (Object)null) { return false; } using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(array))) { binaryReader.ReadInt32(); int num = binaryReader.ReadInt32(); Vector3[] array2 = (Vector3[])(object)new Vector3[num]; Vector3[] array3 = (Vector3[])(object)new Vector3[num]; Vector2[] array4 = (Vector2[])(object)new Vector2[num]; for (int i = 0; i < num; i++) { ref Vector3 reference = ref array2[i]; reference = new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()); } for (int j = 0; j < num; j++) { ref Vector3 reference2 = ref array3[j]; reference2 = new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()); } for (int k = 0; k < num; k++) { ref Vector2 reference3 = ref array4[k]; reference3 = new Vector2(binaryReader.ReadSingle(), binaryReader.ReadSingle()); } int num2 = binaryReader.ReadInt32(); int[][] array5 = new int[num2][]; for (int l = 0; l < num2; l++) { array5[l] = new int[binaryReader.ReadInt32()]; for (int m = 0; m < array5[l].Length; m++) { array5[l][m] = binaryReader.ReadInt32(); } } int num3 = binaryReader.ReadInt32(); for (int n = 0; n < num3; n++) { BoxCenters.Add(new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle())); BoxSizes.Add(new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle())); } int num4 = binaryReader.ReadInt32(); for (int num5 = 0; num5 < num4; num5++) { binaryReader.ReadInt32(); Lights.Add(new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle())); } Mesh val = new Mesh(); ((Object)val).name = "ChroniclesRunestone"; if (num > 65000) { val.indexFormat = (IndexFormat)1; } val.vertices = array2; val.normals = array3; val.uv = array4; Color32[] array6 = (Color32[])(object)new Color32[num]; for (int num6 = 0; num6 < num; num6++) { ref Color32 reference4 = ref array6[num6]; reference4 = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); } val.colors32 = array6; val.subMeshCount = num2; for (int num7 = 0; num7 < num2; num7++) { val.SetTriangles(array5[num7], num7); } val.RecalculateTangents(); val.RecalculateBounds(); Mesh = val; } Texture2D val2 = LoadTexture("stone.png", mips: true, linear: false); if ((Object)(object)val2 != (Object)null) { ((Texture)val2).wrapMode = (TextureWrapMode)0; ((Texture)val2).anisoLevel = 4; val2.Compress(true); } Materials = (Material[])(object)new Material[4] { MakeMaterial(template, val2, "ChroniclesRunestone_Stone", glows: false, null, Normal("stone_n.png", repeat: true)), MakeMaterial(template, Panel("panel_front.png"), "ChroniclesRunestone_Front", glows: true, Mask("glow_front.png"), Normal("panel_front_n.png", repeat: false)), MakeMaterial(template, null, "ChroniclesRunestone_Glow", glows: true, null, null), MakeMaterial(template, Panel("panel_back.png"), "ChroniclesRunestone_Back", glows: true, Mask("glow_back.png"), Normal("panel_back_n.png", repeat: false)) }; if (Materials.Length > Mesh.subMeshCount) { Array.Resize(ref Materials, Mesh.subMeshCount); } Icon = LoadSprite("icon_runestone.png"); Plugin.Log.LogInfo((object)("Runestone: " + Mesh.vertexCount + " vertices, " + BoxCenters.Count + " colliders, shader " + ((Object)template.shader).name)); return true; } private static Texture2D Panel(string name) { Texture2D val = LoadTexture(name, mips: true, linear: false); if ((Object)(object)val == (Object)null) { return null; } ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).anisoLevel = 4; val.Compress(true); return val; } private static Texture2D Mask(string name) { Texture2D val = LoadTexture(name, mips: true, linear: true); if ((Object)(object)val != (Object)null) { ((Texture)val).wrapMode = (TextureWrapMode)1; } return val; } private static Texture2D Normal(string name, bool repeat) { Texture2D val = LoadTexture(name, mips: true, linear: true); if ((Object)(object)val == (Object)null) { return null; } ((Texture)val).wrapMode = (TextureWrapMode)(!repeat); ((Texture)val).anisoLevel = 4; val.Apply(true, true); return val; } private static Texture2D White() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_white != (Object)null) { return _white; } _white = new Texture2D(4, 4, (TextureFormat)4, false); Color32[] array = (Color32[])(object)new Color32[16]; for (int i = 0; i < 16; i++) { ref Color32 reference = ref array[i]; reference = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); } _white.SetPixels32(array); _white.Apply(); ((Object)_white).name = "ChroniclesWhite"; return _white; } private static Material MakeMaterial(Material template, Texture2D tex, string name, bool glows, Texture2D emissionMap, Texture2D bump) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Invalid comparison between Unknown and I4 //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) Material val = new Material(template); ((Object)val).name = name; Shader shader = val.shader; if ((Object)(object)shader != (Object)null) { for (int i = 0; i < shader.GetPropertyCount(); i++) { if ((int)shader.GetPropertyType(i) == 4) { string propertyName = shader.GetPropertyName(i); val.SetTextureScale(propertyName, Vector2.one); val.SetTextureOffset(propertyName, Vector2.zero); } } } if ((Object)(object)tex == (Object)null) { tex = White(); } val.mainTexture = (Texture)(object)tex; if (val.HasProperty("_MainTex")) { val.SetTexture("_MainTex", (Texture)(object)tex); } if (val.HasProperty("_Color")) { val.color = Color.white; } string[] array = new string[5] { "_MetallicGlossMap", "_MetallicTex", "_OcclusionMap", "_DetailAlbedoMap", "_DetailNormalMap" }; foreach (string text in array) { if (val.HasProperty(text)) { val.SetTexture(text, (Texture)null); } } if ((Object)(object)bump != (Object)null && val.HasProperty("_BumpMap")) { val.SetTexture("_BumpMap", (Texture)(object)bump); if (val.HasProperty("_BumpScale")) { val.SetFloat("_BumpScale", 1f); } val.EnableKeyword("_NORMALMAP"); } else if (val.HasProperty("_BumpMap")) { val.SetTexture("_BumpMap", (Texture)null); } if (val.HasProperty("_TriplanarMap")) { val.SetFloat("_TriplanarMap", 0f); } if (val.HasProperty("_Cutoff")) { val.SetFloat("_Cutoff", 0f); } if (glows && val.HasProperty("_EmissionColor")) { if (val.HasProperty("_EmissionMap")) { val.SetTexture("_EmissionMap", (Texture)(object)(((Object)(object)emissionMap != (Object)null) ? emissionMap : White())); } val.SetColor("_EmissionColor", Runestone.Glow * 1.5f); val.EnableKeyword("_EMISSION"); val.globalIlluminationFlags = (MaterialGlobalIlluminationFlags)1; } return val; } public static bool Apply(GameObject prefab) { //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Expected O, but got Unknown //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Expected O, but got Unknown //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0322: Unknown result type (might be due to invalid IL or missing references) Material template = PickTemplate(prefab); int layer = prefab.layer; Collider[] componentsInChildren = prefab.GetComponentsInChildren<Collider>(true); int num = 0; if (num < componentsInChildren.Length) { Collider val = componentsInChildren[num]; layer = ((Component)val).gameObject.layer; } if (!Load(template)) { return false; } LODGroup[] componentsInChildren2 = prefab.GetComponentsInChildren<LODGroup>(true); foreach (LODGroup val2 in componentsInChildren2) { Object.DestroyImmediate((Object)(object)val2); } Renderer[] componentsInChildren3 = prefab.GetComponentsInChildren<Renderer>(true); foreach (Renderer val3 in componentsInChildren3) { Object.DestroyImmediate((Object)(object)val3); } MeshFilter[] componentsInChildren4 = prefab.GetComponentsInChildren<MeshFilter>(true); foreach (MeshFilter val4 in componentsInChildren4) { Object.DestroyImmediate((Object)(object)val4); } Collider[] componentsInChildren5 = prefab.GetComponentsInChildren<Collider>(true); foreach (Collider val5 in componentsInChildren5) { Object.DestroyImmediate((Object)(object)val5); } GameObject val6 = new GameObject("ChroniclesRunestoneModel", new Type[2] { typeof(MeshFilter), typeof(MeshRenderer) }); val6.layer = layer; val6.transform.SetParent(prefab.transform, false); val6.GetComponent<MeshFilter>().sharedMesh = Mesh; MeshRenderer component = val6.GetComponent<MeshRenderer>(); ((Renderer)component).sharedMaterials = Materials; ((Renderer)component).shadowCastingMode = (ShadowCastingMode)1; for (int m = 0; m < BoxCenters.Count; m++) { BoxCollider val7 = val6.AddComponent<BoxCollider>(); val7.center = BoxCenters[m]; val7.size = BoxSizes[m]; } Transform[] componentsInChildren6 = prefab.GetComponentsInChildren<Transform>(true); foreach (Transform val8 in componentsInChildren6) { if ((Object)(object)val8 != (Object)(object)prefab.transform && ((Component)val8).CompareTag("snappoint")) { Object.DestroyImmediate((Object)(object)((Component)val8).gameObject); } } GameObject val9 = new GameObject("_snappoint"); val9.tag = "snappoint"; val9.layer = layer; val9.transform.SetParent(prefab.transform, false); val9.transform.localPosition = Vector3.zero; val9.SetActive(false); Piece component2 = prefab.GetComponent<Piece>(); if ((Object)(object)component2 != (Object)null) { component2.m_notOnFloor = false; component2.m_inCeilingOnly = false; component2.m_groundPiece = false; component2.m_groundOnly = false; component2.m_cultivatedGroundOnly = false; component2.m_waterPiece = false; component2.m_noInWater = true; component2.m_notOnWood = false; component2.m_notOnTiltingSurface = false; component2.m_clipGround = true; component2.m_clipEverything = false; component2.m_noClipping = false; component2.m_onlyInTeleportArea = false; component2.m_allowedInDungeons = false; component2.m_comfort = 0; if ((Object)(object)Icon != (Object)null) { component2.m_icon = Icon; } } WearNTear component3 = prefab.GetComponent<WearNTear>(); if ((Object)(object)component3 != (Object)null) { component3.m_health = 2500f; component3.m_noRoofWear = true; component3.m_noSupportWear = true; component3.m_supports = true; component3.m_materialType = (MaterialType)1; component3.m_burnable = false; } return true; } private static Material PickTemplate(GameObject prefab) { Material result = null; int num = -1; Renderer[] componentsInChildren = prefab.GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { if (!(val is MeshRenderer)) { continue; } Material[] sharedMaterials = val.sharedMaterials; foreach (Material val2 in sharedMaterials) { if ((Object)(object)val2 == (Object)null || (Object)(object)val2.shader == (Object)null) { continue; } string text = ((Object)val2.shader).name.ToLowerInvariant(); string text2 = ((Object)val2).name.ToLowerInvariant(); if (!text.Contains("snow") && !text2.Contains("snow")) { int num2 = 1; if (text.Contains("piece")) { num2 += 4; } if (text2.Contains("stone")) { num2 += 2; } if (val2.HasProperty("_MainTex") && (Object)(object)val2.GetTexture("_MainTex") != (Object)null) { num2 += 2; } if (num2 > num) { num = num2; result = val2; } } } } return result; } } internal static class SagaClient { public static readonly List<Entry> Loaded = new List<Entry>(); public static int LoadedCat = -1; public static bool HasMore; public static bool Waiting; public static SagaStats Stats; public static Entry Latest; public static void Reset() { Loaded.Clear(); LoadedCat = -1; HasMore = false; Waiting = false; Stats = null; Latest = null; } public static void Request(int cat, bool older) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown long num = 0L; if (older && cat == LoadedCat && Loaded.Count > 0) { num = Loaded[Loaded.Count - 1].Id; } if (!older) { Loaded.Clear(); LoadedCat = cat; HasMore = false; } Waiting = true; ZPackage val = new ZPackage(); val.Write(cat); val.Write(num); val.Write(40); Rpc.ToServer("CH_Request", val); } public static void RPC_Page(long sender, ZPackage pkg) { try { int num = pkg.ReadInt(); long num2 = pkg.ReadLong(); int num3 = pkg.ReadInt(); List<Entry> list = new List<Entry>(); for (int i = 0; i < num3; i++) { list.Add(Entry.Read(pkg)); } bool hasMore = pkg.ReadBool(); if (pkg.ReadBool()) { Stats = SagaStats.Read(pkg); } if (num != LoadedCat) { return; } if (num2 == 0) { Loaded.Clear(); } foreach (Entry e in list) { if (!Loaded.Exists((Entry x) => x.Id == e.Id)) { Loaded.Add(e); } } HasMore = hasMore; Waiting = false; if (num2 == 0 && list.Count > 0 && (Latest == null || list[0].Id > Latest.Id) && num == 0) { Latest = list[0]; } SagaUI.OnData(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Bad saga page: " + ex.Message)); } } public static void RPC_New(long sender, ZPackage pkg) { try { bool flag = pkg.ReadBool(); Entry e = Entry.Read(pkg); if (Latest == null || e.Id >= Latest.Id) { Latest = e; } if (!flag) { return; } if (LoadedCat >= 0 && (LoadedCat == 0 || LoadedCat == (int)e.Category) && !Loaded.Exists((Entry x) => x.Id == e.Id)) { Loaded.Insert(0, e); if (Stats != null) { Stats.Verses++; } SagaUI.OnData(); } if (!Plugin.ShowNewEntries.Value || !Announced(e) || GUIManager.IsHeadless()) { return; } string text = Loc.T("msg_new", SagaText.Verse(e, Loc.IsPolish)); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true); } try { if ((Object)(object)Chat.instance != (Object)null) { ((Terminal)Chat.instance).AddString("<color=#E8C77A>" + text + "</color>"); } } catch { } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Bad saga verse: " + ex.Message)); } } private static bool Announced(Entry e) { switch (e.Category) { case Cat.Battles: case Cat.Fallen: case Cat.Explorers: case Cat.Skald: return true; default: return false; } } public static void RPC_Msg(long sender, string text) { string[] array = (text ?? "").Split('\n'); foreach (string text2 in array) { if ((Object)(object)Console.instance != (Object)null) { Console.instance.Print(text2); } Plugin.Log.LogInfo((object)text2); } } } internal static class SagaServer { private const int SaveVersion = 1; private static bool _loaded; private static bool _dirty; private static string _path; private static long _nextId = 1L; private static readonly List<Entry> Entries = new List<Entry>(); private static readonly Dictionary<long, Hero> Heroes = new Dictionary<long, Hero>(); private static readonly HashSet<int> Biomes = new HashSet<int>(); private static readonly Dictionary<string, int> BossKills = new Dictionary<string, int>(); private static readonly Dictionary<string, string> BossNames = new Dictionary<string, string>(); private static readonly Dictionary<string, float> RecentBoss = new Dictionary<string, float>(); private static readonly Dictionary<string, long> RecentBanner = new Dictionary<string, long>(); private static string _lastRaid = ""; private static float _lastRaidAt = -1000f; private static int _seconds; private static readonly HashSet<int> EnvDeaths = new HashSet<int> { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 }; private static readonly Random Rnd = new Random(); private static readonly List<long> RecentDeathVerses = new List<long>(); private static readonly HashSet<int> RareEnv = new HashSet<int> { 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 23, 24 }; private static readonly HashSet<int> KnownBiomes = new HashSet<int> { 1, 2, 4, 8, 16, 32, 64, 256, 512 }; private static readonly Queue<string> _discord = new Queue<string>(); private static bool _posting; private static readonly Regex Tags = new Regex("<[^>]+>"); public static bool Ready => _loaded; private static bool IsServer { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } public static void EnsureLoaded() { //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Expected O, but got Unknown if (_loaded || !IsServer) { return; } _loaded = true; Entries.Clear(); Heroes.Clear(); Biomes.Clear(); BossKills.Clear(); BossNames.Clear(); _nextId = 1L; string text = ZNet.instance.GetWorldName(); if (string.IsNullOrEmpty(text)) { text = "world"; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { text = text.Replace(oldChar, '_'); } string text2 = Path.Combine(Paths.ConfigPath, "Chronicles"); Directory.CreateDirectory(text2); _path = Path.Combine(text2, text + ".dat"); if (!File.Exists(_path)) { Plugin.Log.LogInfo((object)("A new saga begins for world " + text)); SeedFromWorld(); _dirty = true; return; } try { ZPackage val = new ZPackage(File.ReadAllBytes(_path)); int num = val.ReadInt(); _nextId = val.ReadLong(); int num2 = val.ReadInt(); for (int j = 0; j < num2; j++) { Entries.Add(Entry.Read(val)); } num2 = val.ReadInt(); for (int k = 0; k < num2; k++) { Hero hero = Hero.Read(val); Heroes[hero.Pid] = hero; } num2 = val.ReadInt(); for (int l = 0; l < num2; l++) { Biomes.Add(val.ReadInt()); } num2 = val.ReadInt(); for (int m = 0; m < num2; m++) { string key = val.ReadString(); BossNames[key] = val.ReadString(); BossKills[key] = val.ReadInt(); } foreach (Entry entry in Entries) { if (entry.Id >= _nextId) { _nextId = entry.Id + 1; } } Plugin.Log.LogInfo((object)("Saga loaded: " + Entries.Count + " verses, " + Heroes.Count + " heroes (save v" + num + ").")); } catch (Exception ex) { string text3 = _path + ".broken-" + DateTime.Now.ToString("yyyyMMdd-HHmmss"); Plugin.Log.LogError((object)("Could not load " + _path + " — copy saved as " + text3 + ". " + ex)); try { File.Copy(_path, text3, overwrite: true); } catch { } } } private static void SeedFromWorld() { Biomes.Add(1); string[][] array = new string[7][] { new string[4] { "defeated_eikthyr", "Eikthyr", "$enemy_eikthyr", "8" }, new string[4] { "defeated_gdking", "gd_king", "$enemy_gdking", "2,256" }, new string[4] { "defeated_bonemass", "Bonemass", "$enemy_bonemass", "4" }, new string[4] { "defeated_dragon", "Dragon", "$enemy_dragon", "16" }, new string[4] { "defeated_goblinking", "GoblinKing", "$enemy_goblinking", "512" }, new string[4] { "defeated_queen", "SeekerQueen", "$enemy_seekerqueen", "32" }, new string[4] { "defeated_fader", "Fader", "$enemy_fader", "64" } }; int num = -1; for (int i = 0; i < array.Length; i++) { if (GlobalKey(array[i][0])) { num = i; BossKills[array[i][1]] = 1; BossNames[array[i][1]] = array[i][2]; } } for (int j = 0; j <= num; j++) { string[] array2 = array[j][3].Split(','); foreach (string s in array2) { Biomes.Add(int.Parse(s)); } } if (num >= 0) { Plugin.Log.LogInfo((object)("Saga: the world already knows " + (num + 1) + " bosses — their biomes and first kills are not announced again.")); } } private static bool GlobalKey(string key) { try { return (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(key); } catch { return false; } } private static void Save() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown if (!_loaded || string.IsNullOrEmpty(_path)) { return; } _dirty = false; try { ZPackage val = new ZPackage(); val.Write(1); val.Write(_nextId); val.Write(Entries.Count); foreach (Entry entry in Entries) { entry.Write(val); } val.Write(Heroes.Count); foreach (Hero value2 in Heroes.Values) { value2.Write(val); } val.Write(Biomes.Count); foreach (int biome in Biomes) { val.Write(biome); } val.Write(BossKills.Count); foreach (KeyValuePair<string, int> bossKill in BossKills) { BossNames.TryGetValue(bossKill.Key, out var value); val.Write(bossKill.Key); val.Write(value ?? bossKill.Key); val.Write(bossKill.Value); } string text = _path + ".tmp"; File.WriteAllBytes(text, val.GetArray()); if (File.Exists(_path)) { File.Copy(_path, _path + ".bak", overwrite: true); } File.Copy(text, _path, overwrite: true); File.Delete(text); } catch (Exception ex) { Plugin.Log.LogError((object)("Saga save error: " + ex)); } } public static void Unload() { if (_loaded && _dirty) { Save(); } _loaded = false; Entries.Clear(); Heroes.Clear(); Biomes.Clear(); BossKills.Clear(); BossNames.Clear(); RecentBoss.Clear(); RecentBanner.Clear(); _discord.Clear(); _posting = false; } public static void Tick() { if (!_loaded) { EnsureLoaded(); } if (_loaded) { _seconds++; if (_dirty && _seconds % 30 == 0) { Save(); } } } public static Entry Record(string kind, params string[] args) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown if (!IsServer) { return null; } EnsureLoaded(); Entry entry = new Entry(); entry.Id = _nextId++; entry.Ticks = DateTime.UtcNow.Ticks; entry.Day = WorldDay(); entry.Kind = kind; entry.A = args ?? new string[0]; Entries.Add(entry); int value = Plugin.MaxEntries.Value; if (Entries.Count > value) { Entries.RemoveRange(0, Entries.Count - value); } _dirty = true; try { ZPackage val = new ZPackage(); val.Write(true); entry.Write(val); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "CH_New", new object[1] { val }); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Saga broadcast: " + ex.Message)); } PostDiscord(entry); return entry; } private static int WorldDay() { try { return ((Object)(object)EnvMan.instance != (Object)null) ? EnvMan.instance.GetDay() : 0; } catch { return 0; } } private static Hero HeroOf(long pid, string name) { if (!Heroes.TryGetValue(pid, out var value)) { value = new Hero(); value.Pid = pid; value.Joined = DateTime.UtcNow.Ticks; Heroes[pid] = value; _dirty = true; } if (!string.IsNullOrEmpty(name) && value.Name != name) { value.Name = name; _dirty = true; } return value; } public static void RPC_Hello(long sender, bool valkyrie) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown if (!IsServer) { return; } EnsureLoaded(); if (Rpc.Resolve(sender, out var pid, out var name)) { bool flag = !Heroes.ContainsKey(pid); HeroOf(pid, name); if (flag && valkyrie && Plugin.RecordNewcomers.Value) { Record("newcomer", name, Heroes.Count.ToString()); } if (Entries.Count > 0) { ZPackage val = new ZPackage(); val.Write(false); Entries[Entries.Count - 1].Write(val); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "CH_New", new object[1] { val }); } } } public static void RPC_Boss(long sender, ZPackage pkg) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) if (!IsServer || !Plugin.RecordBosses.Value) { return; } EnsureLoaded(); if (!Rpc.Resolve(sender, out var pid, out var name)) { return; } string text; string text2; Vector3 val; int num; try { text = pkg.ReadString(); text2 = pkg.ReadString(); val = pkg.ReadVector3(); num = pkg.ReadInt(); } catch { return; } GameObject val2 = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(text) : null); Character val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent<Character>() : null); if ((Object)(object)val3 == (Object)null || !val3.m_boss) { return; } if (string.IsNullOrEmpty(text2) || text2.Length > 80) { text2 = val3.m_name; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (RecentBoss.TryGetValue(text, out var value) && realtimeSinceStartup - value < 30f) { return; } RecentBoss[text] = realtimeSinceStartup; List<string> list = new List<string>(); float value2 = Plugin.BossPartyRange.Value; foreach (OnlinePlayer item in Rpc.Online()) { if (item.Pid == pid || !(Vector3.Distance(item.Pos, val) > value2)) { list.Add(item.Name); HeroOf(item.Pid, item.Name).Bosses++; } } if (list.Count == 0) { list.Add(name); } BossKills.TryGetValue(text, out var value3); value3++; BossKills[text] = value3; BossNames[text] = text2; Record("boss", text2, text, string.Join("\n", list.ToArray()), value3.ToString(), (value3 == 1) ? "1" : "0", num.ToString()); } public static void RPC_Death(long sender, ZPackage pkg) { if (!IsServer) { return; } EnsureLoaded(); if (!Rpc.Resolve(sender, out var pid, out var name)) { return; } int num; string text; string text2; string text3; int num2; try { num = pkg.ReadInt(); text = pkg.ReadString(); text2 = pkg.ReadString(); text3 = pkg.ReadString(); num2 = pkg.ReadInt(); } catch { return; } Hero hero = HeroOf(pid, name); hero.Deaths++; _dirty = true; if (Plugin.Deaths.Value == DeathMode.Off) { return; } if (text3.Length > 0) { bool flag = false; foreach (OnlinePlayer item in Rpc.Online()) { if (item.Name == text3 && item.Pid != pid) { flag = true; break; } } if (!flag) { text3 = ""; } } string text4 = ((text3.Length > 0) ? "pvp" : (EnvDeaths.Contains(num) ? ("env" + num) : ((text.Length <= 0 || !IsWeak(text)) ? "foe" : "weak"))); bool flag2 = IsMilestone(hero.Deaths); bool flag3 = text4 != "foe" || flag2; if (Plugin.Deaths.Value == DeathMode.Notable && !flag3) { return; } long now = DateTime.UtcNow.Ticks; if (!flag2) { if (now - hero.LastDeathVerse < (long)(Plugin.DeathCooldownMinutes.Value * 600000000f)) { return; } RecentDeathVerses.RemoveAll((long t) => now - t >= 36000000000L); if ((Plugin.DeathVersesPerHour.Value > 0 && RecentDeathVerses.Count >= Plugin.DeathVersesPerHour.Value) || (!RareDeath(text4) && Rnd.NextDouble() >= (double)Plugin.DeathChance.Value)) { return; } RecentDeathVerses.Add(now); } hero.LastDeathVerse = now; if (text2.Length > 80) { text2 = ""; } Record("death", name, text4, text2, text3, num2.ToString(), hero.Deaths.ToString(), flag2 ? "1" : "0"); } private static bool RareDeath(string kind) { if (kind == "pvp" || kind == "weak") { return true; } if (kind.StartsWith("env") && int.TryParse(kind.Substring(3), out var result)) { return RareEnv.Contains(result); } return false; } private static bool IsWeak(string prefab) { string[] array = (Plugin.WeakCreatures.Value ?? "").Split(','); foreach (string text in array) { if (string.Equals(text.Trim(), prefab, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool IsMilestone(int deaths) { string[] array = (Plugin.DeathMilestones.Value ?? "").Split(','); foreach (string text in array) { if (int.TryParse(text.Trim(), out var result) && result == deaths) { return true; } } return false; } public static void RPC_Biome(long sender, int biome) { if (IsServer && Plugin.RecordBiomes.Value && KnownBiomes.Contains(biome)) { EnsureLoaded(); if (Rpc.Resolve(sender, out var pid, out var name) && Biomes.Add(biome)) { HeroOf(pid, name).Firsts++; Record("biome", name, biome.ToString()); } } } public static void OnRaid(RandomEvent ev, Vector3 pos) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected I4, but got Unknown if (!IsServer || ev == null || !Plugin.RecordRaids.Value) { return; } EnsureLoaded(); float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < 60f || (ev.m_name == _lastRaid && realtimeSinceStartup - _lastRaidAt < 120f)) { return; } _lastRaid = ev.m_name; _lastRaidAt = realtimeSinceStartup; string text = ""; float num = Mathf.Max(60f, ev.m_eventRange + 30f); foreach (OnlinePlayer item in Rpc.Online()) { float num2 = Vector3.Distance(item.Pos, pos); if (num2 < num) { num = num2; text = item.Name; } } int num3 = 0; try { if (WorldGenerator.instance != null) { num3 = (int)WorldGenerator.instance.GetBiome(pos); } } catch { } Record("raid", text, ev.m_name ?? "", ev.m_startMessage ?? "", num3.ToString()); } public static bool BannerAllowed(string tag) { long ticks = DateTime.UtcNow.Ticks; if (RecentBanner.TryGetValue(tag ?? "", out var value) && ticks - value < 864000000000L) { return false; } RecentBanner[tag ?? ""] = ticks; return true; } public static void RPC_Request(long sender, ZPackage req) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown if (!IsServer) { return; } EnsureLoaded(); int num; long num2; int num3; try { num = req.ReadInt(); num2 = req.ReadLong(); num3 = Mathf.Clamp(req.ReadInt(), 1, 80); } catch { return; } List<Entry> list = new List<Entry>(); bool flag = false; for (int num4 = Entries.Count - 1; num4 >= 0; num4--) { Entry entry = Entries[num4]; if ((num2 <= 0 || entry.Id < num2) && (num == 0 || entry.Category == (Cat)num)) { if (list.Count >= num3) { flag = true; break; } list.Add(entry); } } ZPackage val = new ZPackage(); val.Write(num); val.Write(num2); val.Write(list.Count); foreach (Entry item in list) { item.Write(val); } val.Write(flag); val.Write(num2 == 0); if (num2 == 0) { Stats().Write(val); } ZRoutedRpc.instance.InvokeRoutedRPC(sender, "CH_Page", new object[1] { val }); } private static SagaStats Stats() { SagaStats sagaStats = new SagaStats(); sagaStats.Day = WorldDay(); sagaStats.Verses = Entries.Count; sagaStats.Heroes = Heroes.Count; sagaStats.Since = ((Entries.Count > 0) ? Entries[0].Ticks : 0); foreach (int value2 in BossKills.Values) { sagaStats.BossKills += value2; } List<Hero> hs = new List<Hero>(Heroes.Values); Top(hs, sagaStats.TopBosses, (Hero h) => h.Bosses); Top(hs, sagaStats.TopDeaths, (Hero h) => h.Deaths); Top(hs, sagaStats.TopFirsts, (Hero h) => h.Firsts); List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(); foreach (KeyValuePair<string, int> bossKill in BossKills) { BossNames.TryGetValue(bossKill.Key, out var value); list.Add(new KeyValuePair<string, int>(value ?? bossKill.Key, bossKill.Value)); } list.Sort((KeyValuePair<string, int> a, KeyValuePair<string, int> b) => b.Value.CompareTo(a.Value)); for (int num = 0; num < list.Count && num < 16; num++) { sagaStats.Bosses.Add(list[num]); } return sagaStats; } private static void Top(List<Hero> hs, List<KeyValuePair<string, int>> into, Func<Hero, int> value) { hs.Sort((Hero a, Hero b) => value(b).CompareTo(value(a))); foreach (Hero h in hs) { if (into.Count < 5 && value(h) > 0) { into.Add(new KeyValuePair<string, int>(h.Name, value(h))); continue; } break; } } public static void RPC_Admin(long sender, string cmd) { if (!IsServer) { return; } EnsureLoaded(); Rpc.Resolve(sender, out var _, out var name); if (!Rpc.IsAdminSender(sender)) { Reply(sender, "Only server admins can use this command."); return; } cmd = (cmd ?? "").Trim(); string text = cmd; string text2 = ""; int num = cmd.IndexOf(' '); if (num > 0) { text = cmd.Substring(0, num); text2 = cmd.Substring(num + 1).Trim(); } switch (text.ToLowerInvariant()) { case "add": { if (text2.Length == 0) { Reply(sender, "Usage: saga add <text>"); break; } if (text2.Length > 600) { text2 = text2.Substring(0, 600); } Entry entry2 = Record("skald", text2, name); Plugin.Log.LogInfo((object)("Saga: " + name + " added verse #" + entry2.Id)); Reply(sender, "Verse #" + entry2.Id + " carved into the saga."); break; } case "del": case "delete": case "remove": { if (!long.TryParse(text2.TrimStart('#'), out var id)) { Reply(sender, "Usage: saga del <id>"); break; } int num2 = Entries.RemoveAll((Entry x) => x.Id == id); if (num2 > 0) { _dirty = true; Plugin.Log.LogInfo((object)("Saga: " + name + " removed verse #" + id)); } Reply(sender, (num2 > 0) ? ("Verse #" + id + " erased from the saga.") : ("No verse #" + id + ".")); break; } case "last": case "list": { if (!int.TryParse(text2, out var result)) { result = 10; } result = Mathf.Clamp(result, 1, 40); StringBuilder stringBuilder = new StringBuilder(); for (int i = Math.Max(0, Entries.Count - result); i < Entries.Count; i++) { Entry entry = Entries[i]; stringBuilder.Append('#').Append(entry.Id).Append(" (") .Append(entry.Kind) .Append(") ") .Append(Strip(SagaText.Verse(entry, pl: false))) .Append('\n'); } Reply(sender, (stringBuilder.Length > 0) ? stringBuilder.ToString().TrimEnd('\n') : "The saga is still empty."); break; } default: Reply(sender, "saga add <text> | saga del <id> | saga last [n]"); break; } } private static void Reply(long target, string text) { if (target != 0 && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(target, "CH_Msg", new object[1] { text }); } } private static void PostDiscord(Entry e) { string text = (Plugin.DiscordWebhook.Value ?? "").Trim(); if (!text.StartsWith("https://") || (Object)(object)Plugin.Instance == (Object)null) { return; } string text2 = e.Category.ToString().ToLowerInvariant(); bool flag = false; string[] array = (Plugin.DiscordCategories.Value ?? "").Split(','); foreach (string text3 in array) { if (text3.Trim().ToLowerInvariant() == text2) { flag = true; break; } } if (flag) { bool pl = Plugin.DiscordLanguage.Value == SagaLanguage.Polish; string item; try { item = "ᚱ **" + Loc.Get(pl, "day", e.Day) + "** · " + Markdown(SagaText.Verse(e, pl)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Saga verse for Discord: " + ex.Message)); return; } if (_discord.Count > 50) { _discord.Dequeue(); } _discord.Enqueue(item); if (!_posting) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(PostLoop(text)); } } } private static IEnumerator PostLoop(string url) { _posting = true; while (_discord.Count > 0) { string text = _discord.Dequeue(); string json = "{\"content\":\"" + text.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n") + "\"}"; UnityWebRequest req = new UnityWebRequest(url, "POST"); try { req.uploadHandler = (UploadHandler)new UploadHandlerRaw(Encoding.UTF8.GetBytes(json)); req.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); req.SetRequestHeader("Content-Type", "application/json"); req.timeout = 15; yield return req.SendWebRequest(); if ((int)req.result != 1) { Plugin.Log.LogWarning((object)("Discord webhook failed: " + req.error)); } } finally { ((IDisposable)req)?.Dispose(); } yield return (object)new WaitForSecondsRealtime(1.5f); } _posting = false; } public static string Strip(string s) { return Tags.Replace(s ?? "", ""); } private static string Markdown(string s) { s = (s ?? "").Replace("<b>", "**").Replace("</b>", "**").Replace("<i>", "*") .Replace("</i>", "*"); return Strip(s); } } internal static class SagaText { public static readonly string[] CatColor = new string[8] { "#E8D9B0", "#E86A4A", "#A9A2C8", "#7FC7E8", "#F2C14E", "#8FD16A", "#D9A066", "#C8B88A" }; public static string Verse(Entry e, bool pl) { Func<string, object[], string> func = (string key, object[] a) => Variant(pl, key, e.Id, a); switch (e.Kind) { case "boss": { string text2 = Who(Refs.Text(e.Arg(0))); string[] array = e.Arg(2).Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries); int num = Int(e.Arg(3)); object[] array2 = new object[4] { text2, Names(array, pl), num, Ord(num, pl) }; if (e.Arg(4) == "1") { return Loc.Get(pl, "v_boss_first", array2); } return func((array.Length == 1) ? "v_boss_solo" : "v_boss", array2); } case "death": { string text3 = e.Arg(1); string text4 = Refs.Text(e.Arg(2)); object[] arg2 = new object[4] { Who(e.Arg(0)), BiomeIn(e.Arg(4), pl), text4, Who(e.Arg(3)) }; string text5 = "v_d_" + text3; if (!Loc.Has(pl, text5 + "_0") || (text3 == "foe" && text4.Length == 0)) { text5 = "v_d_other"; } string text6 = func(text5, arg2); if (e.Arg(6) == "1") { text6 = text6 + " " + Loc.Get(pl, "v_d_milestone", Ord(Int(e.Arg(5)), pl)); } return text6; } case "biome": { string text = e.Arg(1); object[] arg = new object[3] { Who(e.Arg(0)), BiomeIn(text, pl), Loc.Get(pl, "bnom_" + text) }; return func((text == "256") ? "v_biome_ocean" : "v_biome_land", arg); } case "newcomer": return func("v_newcomer", new object[2] { Who(e.Arg(0)), Ord(Int(e.Arg(1)), pl) }); case "raid": { string text7 = Refs.Text(e.Arg(2)); if (text7.Length == 0 || text7[0] == '[') { text7 = e.Arg(1); } object[] arg3 = new object[3] { Who(e.Arg(0)), BiomeIn(e.Arg(3), pl), text7 }; return func((e.Arg(0).Length > 0) ? "v_raid" : "v_raid_nobody", arg3); } case "god_chosen": case "god_legend": return func("v_" + e.Kind, new object[3] { Who(e.Arg(0)), God(e.Arg(1), pl, genitive: false), God(e.Arg(1), pl, genitive: true) }); case "blot_end": return func("v_blot_end", new object[3] { God(e.Arg(0), pl, genitive: false), God(e.Arg(0), pl, genitive: true), e.Arg(1) }); case "blot_failed": return func("v_blot_failed", new object[4] { God(e.Arg(0), pl, genitive: false), God(e.Arg(0), pl, genitive: true), e.Arg(1), e.Arg(2) }); case "guild_found": case "guild_banner": return func("v_" + e.Kind, new object[3] { Who(e.Arg(0)), e.Arg(1), e.Arg(2) }); case "guild_level": case "guild_disband": return func("v_" + e.Kind, new object[3] { e.Arg(0), e.Arg(1), e.Arg(2) }); case "guild_ach": return func("v_guild_ach", new object