using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GUIFramework; using HarmonyLib; using LostScrollsII.Bounty; using LostScrollsII.Companions; using LostScrollsII.Economy; using LostScrollsII.Integration; using LostScrollsII.Ranking; using Microsoft.CodeAnalysis; using Splatform; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; using ValheimServerGuide.Discord; using ValheimServerGuide.Triggers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("LostScrollsII")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.9.1.0")] [assembly: AssemblyInformationalVersion("0.9.1+7b81ffa85b20e1622905f275f7685e83d3fed6dd")] [assembly: AssemblyProduct("LostScrollsII")] [assembly: AssemblyTitle("LostScrollsII")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.9.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LostScrollsII { [BepInPlugin("com.lostscrollsii", "Lost Scrolls II", "0.9.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.lostscrollsii"; public const string PluginName = "Lost Scrolls II"; public const string PluginVersion = "0.9.1"; private Harmony _harmony; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public static ConfigEntry CommunionKey { get; private set; } public static ConfigEntry CommunionChannelSeconds { get; private set; } public static ConfigEntry CommunionMaxDistance { get; private set; } public static ConfigEntry CommunionBreakOnDamage { get; private set; } public static ConfigEntry StaffSealEnabled { get; private set; } public static ConfigEntry SealMinBloodMagic { get; private set; } public static ConfigEntry SealFullSpeedBloodMagic { get; private set; } public static ConfigEntry SealChannelMaxSeconds { get; private set; } public static ConfigEntry SealChannelMinSeconds { get; private set; } public static ConfigEntry SealMaxDistance { get; private set; } public static ConfigEntry ChoreAssignKey { get; private set; } public static ConfigEntry ChoreAssignRadius { get; private set; } public static ConfigEntry DuelSelectKey { get; private set; } public static ConfigEntry PartyDuelKey { get; private set; } public static ConfigEntry MaxPartySize { get; private set; } public static ConfigEntry StanceCycleKey { get; private set; } public static ConfigEntry InventoryKey { get; private set; } public static ConfigEntry ShowMapPins { get; private set; } public static ConfigEntry CompanionPinColor { get; private set; } public static ConfigEntry CompanionPinScale { get; private set; } public static ConfigEntry ShowDeathMarker { get; private set; } public static ConfigEntry MoveContainerPanel { get; private set; } public static ConfigEntry ContainerPanelOffset { get; private set; } public static ConfigEntry MenuBarOffset { get; private set; } public static ConfigEntry RankingKFactor { get; private set; } public static ConfigEntry RankingPairCooldown { get; private set; } public static ConfigEntry ShowRankOnNameTag { get; private set; } public static ConfigEntry RankingUiKey { get; private set; } public static ConfigEntry TournamentUiKey { get; private set; } public static ConfigEntry RequiredEntrantLevel { get; private set; } public static ConfigEntry MaxEntrants { get; private set; } public static ConfigEntry WagersEnabled { get; private set; } public static ConfigEntry TournamentValcoinFee { get; private set; } public static ConfigEntry TournamentValcoinPrize { get; private set; } public static ConfigEntry TournamentCoinFee { get; private set; } public static ConfigEntry TournamentCoinPrize { get; private set; } public static ConfigEntry DuelValcoinStake { get; private set; } public static ConfigEntry DuelCoinStake { get; private set; } public static ConfigEntry WageredBracketSize { get; private set; } public static ConfigEntry WageredRegistrationMinutes { get; private set; } public static ConfigEntry BountyEnabled { get; private set; } public static ConfigEntry BountySearchMinRadius { get; private set; } public static ConfigEntry BountySearchMaxRadius { get; private set; } public static ConfigEntry BountyLandCheckRadius { get; private set; } public static ConfigEntry BountyWaterMargin { get; private set; } public static ConfigEntry BountyMaxHeightVariance { get; private set; } public static ConfigEntry BountySampleAttempts { get; private set; } public static ConfigEntry BountyTierBlackForest { get; private set; } public static ConfigEntry BountyTierSwamp { get; private set; } public static ConfigEntry BountyTierMountain { get; private set; } public static ConfigEntry BountyTierPlains { get; private set; } public static ConfigEntry BountyHealthBase { get; private set; } public static ConfigEntry BountyHealthGrowth { get; private set; } public static ConfigEntry BountyMaxStarLevel { get; private set; } public static ConfigEntry BountyMinionsBase { get; private set; } public static ConfigEntry BountyMinionsPerTier { get; private set; } public static ConfigEntry BountyMaxMinions { get; private set; } public static ConfigEntry BountyMinionRingRadius { get; private set; } public static ConfigEntry BountyAlertRangeMultiplier { get; private set; } public static ConfigEntry BountyRoamRadius { get; private set; } public static ConfigEntry BountyMaxTierBonus { get; private set; } public static ConfigEntry BountyValcoinBaseChance { get; private set; } public static ConfigEntry BountyValcoinChancePerTier { get; private set; } public static ConfigEntry BountyValcoinRankBonus { get; private set; } public static ConfigEntry BountyValcoinRankDepth { get; private set; } public static ConfigEntry BountyValcoinMaxChance { get; private set; } public static ConfigEntry BountyPointsPerTier { get; private set; } public static ConfigEntry BountyLeaderboardBonusRank { get; private set; } public static ConfigEntry BountyUiKey { get; private set; } public static ConfigEntry BountyMaxBoardEntries { get; private set; } public static ConfigEntry BountyMaxActivePerPlayer { get; private set; } public static ConfigEntry BountyArrivalRadius { get; private set; } public static ConfigEntry BountyButtonOffset { get; private set; } public static ConfigEntry BountyRefreshHours { get; private set; } public static ConfigEntry BountyEliteChance { get; private set; } public static ConfigEntry BountyEliteRankTopN { get; private set; } private void Awake() { //IL_0a34: Unknown result type (might be due to invalid IL or missing references) //IL_0a3e: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; CommunionKey = ((BaseUnityPlugin)this).Config.Bind("Recruitment", "CommunionKey", (KeyCode)103, "Key pressed while hovering a recruited companion to feed it a health mead. (The Communion Rite that recruits a subdued Dvergr is now channeled by holding the Block button, not this key.)"); CommunionChannelSeconds = ((BaseUnityPlugin)this).Config.Bind("Recruitment", "CommunionChannelSeconds", 5f, "How long you must hold the Communion key to complete the rite. The corruption fights back the whole time — release the key, stray too far, or take a hit and the rite breaks."); CommunionMaxDistance = ((BaseUnityPlugin)this).Config.Bind("Recruitment", "CommunionMaxDistance", 4f, "How far you may drift from the Dvergr mid-rite before the connection snaps and the rite fails."); CommunionBreakOnDamage = ((BaseUnityPlugin)this).Config.Bind("Recruitment", "CommunionBreakOnDamage", true, "If true, taking damage while channeling the Communion Rite breaks it (the shadow reclaims the Dvergr). Turn off for a more forgiving rite."); StaffSealEnabled = ((BaseUnityPlugin)this).Config.Bind("Recruitment", "StaffSealEnabled", true, "Allow sealing a companion into a Communion Totem in the field with an equipped Dead Raiser (StaffSkeleton) + a Wisp, by holding Block on your own Follow-stance ally. The Incinerator ritual is unaffected either way."); SealMinBloodMagic = ((BaseUnityPlugin)this).Config.Bind("Recruitment", "SealMinBloodMagic", 20, "Minimum Blood Magic skill needed to seal a companion with a Dead Raiser. Below this the rite cannot be started."); SealFullSpeedBloodMagic = ((BaseUnityPlugin)this).Config.Bind("Recruitment", "SealFullSpeedBloodMagic", 100, "The Blood Magic skill at which the sealing channel reaches its fastest (SealChannelMinSeconds). Between SealMinBloodMagic and this value the channel time scales smoothly."); SealChannelMaxSeconds = ((BaseUnityPlugin)this).Config.Bind("Recruitment", "SealChannelMaxSeconds", 5f, "How long the Dead Raiser sealing channel takes at the MINIMUM Blood Magic skill (the slowest it ever is)."); SealChannelMinSeconds = ((BaseUnityPlugin)this).Config.Bind("Recruitment", "SealChannelMinSeconds", 2f, "How long the Dead Raiser sealing channel takes at SealFullSpeedBloodMagic (the fastest it ever is)."); SealMaxDistance = ((BaseUnityPlugin)this).Config.Bind("Recruitment", "SealMaxDistance", 4f, "How far you may drift from the companion mid-sealing before the binding fails."); ChoreAssignKey = ((BaseUnityPlugin)this).Config.Bind("Chores", "ChoreAssignKey", (KeyCode)104, "Key pressed while hovering a workstation (Smelter-family), a ripe crop, or a tamed animal to assign/unassign the nearest matching-caste companion to that chore."); ChoreAssignRadius = ((BaseUnityPlugin)this).Config.Bind("Chores", "ChoreAssignRadius", 10f, "Max distance from the player to look for a recruited companion to assign to a chore."); DuelSelectKey = ((BaseUnityPlugin)this).Config.Bind("Duels", "DuelSelectKey", (KeyCode)106, "Press while hovering your own recruited companion to toggle its duel mode. A duel-mode companion fights other players' duel-mode companions and ignores everyone else."); PartyDuelKey = ((BaseUnityPlugin)this).Config.Bind("Duels", "PartyDuelKey", (KeyCode)107, "Press while hovering your own recruited companion to toggle a party duel: your nearby Follow-stance companions form a team that fights another player's party-duel team."); MaxPartySize = ((BaseUnityPlugin)this).Config.Bind("Duels", "MaxPartySize", 4, "Maximum number of your companions that join a party duel when you toggle it on."); StanceCycleKey = ((BaseUnityPlugin)this).Config.Bind("Companions", "StanceCycleKey", (KeyCode)101, "Press while hovering your companion to cycle its stance: Follow -> Guard -> Standby."); InventoryKey = ((BaseUnityPlugin)this).Config.Bind("Companions", "InventoryKey", (KeyCode)121, "Press while hovering your companion to open its inventory (a chest-like panel that also lets you rename it)."); ShowMapPins = ((BaseUnityPlugin)this).Config.Bind("Companions", "ShowMapPins", true, "Show a live minimap pin at each of your own recruited companions. Pins are client-side — other players never see your companions, and you never see theirs."); CompanionPinColor = ((BaseUnityPlugin)this).Config.Bind("Companions", "CompanionPinColor", "FFB84D", "Hex tint (RRGGBB) for companion map pins, which use the player icon in a smaller size. Distinguishes your allies from your own marker."); CompanionPinScale = ((BaseUnityPlugin)this).Config.Bind("Companions", "CompanionPinScale", 0.7f, "Size multiplier for companion map pins (1 = full player-icon size). Smaller keeps them from crowding your own marker."); ShowDeathMarker = ((BaseUnityPlugin)this).Config.Bind("Companions", "ShowDeathMarker", true, "Drop a persistent death marker (skull) on your map, labelled with the companion's name, when one of your companions dies."); MoveContainerPanel = ((BaseUnityPlugin)this).Config.Bind("Interface", "MoveContainerPanel", true, "Let the chest/storage UI be repositioned: it opens at ContainerPanelOffset, and you can drag it anywhere on screen by grabbing an empty part of the panel. Set to false to leave the panel exactly where the game puts it. Automatically ignored when BiomeLords is installed (it moves the same panel itself)."); ContainerPanelOffset = ((BaseUnityPlugin)this).Config.Bind("Interface", "ContainerPanelOffset", "auto", "Where the chest/storage UI sits, as an \"x,y\" pixel offset from the game's own position (+x right, +y up). \"auto\" places it two inventory rows below, so extra rows added by other mods aren't hidden behind it. Dragging the panel writes the new value here; set it back to \"auto\" to restore the default position."); MenuBarOffset = ((BaseUnityPlugin)this).Config.Bind("Interface", "MenuBarOffset", "0,0", "Nudge the row of menu buttons at the top of the inventory screen by \"x,y\" pixels (+x right, +y up). The row holds Rankings, Tournaments and Bounty Board; the matching function keys keep working either way."); RankingKFactor = ((BaseUnityPlugin)this).Config.Bind("Ranking", "EloKFactor", 32, "Elo K-factor for the duel ladder — higher means bigger rating swings per match."); RankingPairCooldown = ((BaseUnityPlugin)this).Config.Bind("Ranking", "PairCooldownSeconds", 300f, "Seconds before the same two companions can move each other's rating again (W/L still count). Anti friend-farming."); ShowRankOnNameTag = ((BaseUnityPlugin)this).Config.Bind("Ranking", "ShowRankOnNameTag", true, "Append the ladder rank (e.g. #3) to a companion's floating name when it has one."); RankingUiKey = ((BaseUnityPlugin)this).Config.Bind("Ranking", "RankingUiKey", (KeyCode)287, "Opens the ranking board — a read-only view of the duel and party ladders. Close it with Escape, like reading a runestone. (de_ladder / de_party_ladder still work in the console.)"); TournamentUiKey = ((BaseUnityPlugin)this).Config.Bind("Tournaments", "TournamentUiKey", (KeyCode)288, "Opens the tournament board — status, registration (lock a companion's Communion Totem into a slot to enter), the bracket, and admin controls. (de_tournament still works in the console.)"); RequiredEntrantLevel = ((BaseUnityPlugin)this).Config.Bind("Tournaments", "RequiredEntrantLevel", 0, "0 = no restriction. When >0, a 1v1 entrant's companion level must match this exactly to be allowed to register (party entries are not level-gated)."); MaxEntrants = ((BaseUnityPlugin)this).Config.Bind("Tournaments", "MaxEntrants", 4, "Hard cap on tournament entrants. `de_tournament start` clamps any size above this, and a size of 0 (or omitted) now means \"use this cap\" instead of unlimited."); WagersEnabled = ((BaseUnityPlugin)this).Config.Bind("Wagers", "WagersEnabled", true, "Master switch for wagered events: player-started tournaments and duel invites staked in Coins or Valcoins. Turn off to leave only the free admin-run tournaments."); WageredBracketSize = ((BaseUnityPlugin)this).Config.Bind("Wagers", "BracketSize", 4, "How many entrants a wagered tournament needs. The bracket starts automatically once it is full, and a tournament that never fills is cancelled with every stake refunded."); WageredRegistrationMinutes = ((BaseUnityPlugin)this).Config.Bind("Wagers", "RegistrationMinutes", 30f, "How long a wagered tournament stays open for registration before it is auto-cancelled and every stake refunded. 0 = never expire (the host or an admin must cancel it)."); TournamentCoinFee = ((BaseUnityPlugin)this).Config.Bind("Wagers", "TournamentCoinFee", 100, "Coins it costs to START a Coin tournament, and the same amount each entrant pays to register."); TournamentCoinPrize = ((BaseUnityPlugin)this).Config.Bind("Wagers", "TournamentCoinPrize", 999, "Coins paid to the champion of a Coin tournament."); TournamentValcoinFee = ((BaseUnityPlugin)this).Config.Bind("Wagers", "TournamentValcoinFee", 10, "Valcoins it costs to START a Valcoin tournament, and the same amount each entrant pays to register. Requires the Valheim Donations wallet API on the server."); TournamentValcoinPrize = ((BaseUnityPlugin)this).Config.Bind("Wagers", "TournamentValcoinPrize", 100, "Valcoins the champion of a Valcoin tournament receives. DISPLAY ONLY on this side: the actual payout is minted by the donations mod from its own valcoin_quests.yaml entry `ls_tournament_prize`, which is the only place allowed to price a Valcoin reward. Keep the two numbers in step."); DuelCoinStake = ((BaseUnityPlugin)this).Config.Bind("Wagers", "DuelCoinStake", 100, "Coins staked by each side of a Coin duel invite. The winner takes both stakes."); DuelValcoinStake = ((BaseUnityPlugin)this).Config.Bind("Wagers", "DuelValcoinStake", 10, "Valcoins staked by each side of a Valcoin duel invite. The winner takes both stakes — no Valcoins are created, they only change hands."); BountyEnabled = ((BaseUnityPlugin)this).Config.Bind("Bounty", "Enabled", true, "Master switch for bounty hunting. Even when true the feature only runs on a server/host that ALSO has BiomeLords, ValheimServerGuide and Valheim Donations installed — set it to false to switch bounties off on a server that would otherwise qualify. Check the log line \"[bounty] feature gate: ON/OFF\" to see which condition failed."); BountySearchMinRadius = ((BaseUnityPlugin)this).Config.Bind("Bounty", "SearchMinRadius", 500f, "Closest a bounty may be posted to the world centre, in metres. Keeps targets out of the starting area."); BountySearchMaxRadius = ((BaseUnityPlugin)this).Config.Bind("Bounty", "SearchMaxRadius", 6000f, "Furthest a bounty may be posted from the world centre, in metres. Keep well inside the world edge (~10000) so candidates don't fall into the edge ocean."); BountyLandCheckRadius = ((BaseUnityPlugin)this).Config.Bind("Bounty", "LandCheckRadius", 80f, "How far around a candidate spot the ground must be dry land, in metres. Checked as three rings — clearing the outermost means the landmass is at least twice this across, which is what rules out islets and narrow spits. Lower it only if bounties become hard to place."); BountyWaterMargin = ((BaseUnityPlugin)this).Config.Bind("Bounty", "WaterMargin", 3f, "How far above sea level the ground must sit, in metres, to count as dry. Raise it to push bounties further inland from shorelines."); BountyMaxHeightVariance = ((BaseUnityPlugin)this).Config.Bind("Bounty", "MaxHeightVariance", 10f, "Largest height swing allowed in the immediate area around a bounty, in metres. Rejects cliff faces and spires where a fight can't happen. Raise it for more mountain bounties."); BountySampleAttempts = ((BaseUnityPlugin)this).Config.Bind("Bounty", "SampleAttempts", 200, "How many random spots to test before giving up on placing one bounty. Higher is slower but more reliable on worlds with little qualifying land."); BountyTierBlackForest = ((BaseUnityPlugin)this).Config.Bind("Bounty", "TierBlackForest", 1, "Base difficulty tier (1-5) for bounties posted in the Black Forest."); BountyTierSwamp = ((BaseUnityPlugin)this).Config.Bind("Bounty", "TierSwamp", 2, "Base difficulty tier (1-5) for bounties posted in the Swamp."); BountyTierMountain = ((BaseUnityPlugin)this).Config.Bind("Bounty", "TierMountain", 3, "Base difficulty tier (1-5) for bounties posted in the Mountains."); BountyTierPlains = ((BaseUnityPlugin)this).Config.Bind("Bounty", "TierPlains", 4, "Base difficulty tier (1-5) for bounties posted in the Plains."); BountyHealthBase = ((BaseUnityPlugin)this).Config.Bind("Bounty", "HealthMultiplierBase", 3f, "Health multiplier for a tier 1 bounty target, over the creature's normal health. Applied on top of its star level."); BountyHealthGrowth = ((BaseUnityPlugin)this).Config.Bind("Bounty", "HealthMultiplierGrowth", 1.6f, "How much the health multiplier compounds per tier. With the defaults: tier 1 = 3x, tier 2 = 4.8x, tier 3 = 7.7x, tier 4 = 12.3x, tier 5 = 19.7x."); BountyMaxStarLevel = ((BaseUnityPlugin)this).Config.Bind("Bounty", "MaxStarLevel", 3, "Cap on the vanilla star level given to a bounty target. Stars carry DAMAGE scaling, so raising this makes targets hit far harder, not just survive longer — most of a tier's difficulty is meant to come from health and escort size instead."); BountyMinionsBase = ((BaseUnityPlugin)this).Config.Bind("Bounty", "MinionsBase", 1, "How many escort minions a tier 1 bounty target has."); BountyMinionsPerTier = ((BaseUnityPlugin)this).Config.Bind("Bounty", "MinionsPerTier", 1, "Extra escort minions added per tier above 1."); BountyMaxMinions = ((BaseUnityPlugin)this).Config.Bind("Bounty", "MaxMinions", 6, "Hard cap on escort minions, whatever the tier. Bounds the AI load of one fight."); BountyMinionRingRadius = ((BaseUnityPlugin)this).Config.Bind("Bounty", "MinionRingRadius", 6f, "How far from the bounty target its escort spawns, in metres."); BountyAlertRangeMultiplier = ((BaseUnityPlugin)this).Config.Bind("Bounty", "AlertRangeMultiplier", 2f, "How much further than normal a bounty creature notices hunters. Bounty Dvergr hunt on sight rather than waiting to be provoked, so they notice you first."); BountyRoamRadius = ((BaseUnityPlugin)this).Config.Bind("Bounty", "RoamRadius", 12f, "How far a bounty creature wanders from where it was posted, in metres. Keeps the map pin meaningful; it still chases hunters normally."); BountyMaxTierBonus = ((BaseUnityPlugin)this).Config.Bind("Bounty", "MaxTierBonus", 1, "How many tiers a top-standing bounty hunter's rewards may be bumped by. The bump selects a higher-tier reward entry, so better hunters get better bundles without a second reward table."); BountyValcoinBaseChance = ((BaseUnityPlugin)this).Config.Bind("Bounty", "ValcoinBaseChance", 0.1f, "Chance (0-1) that a tier 1 bounty pays Valcoin, before any rank bonus. Valcoin is a REWARD ONLY — nothing in bounty hunting can be bought with it."); BountyValcoinChancePerTier = ((BaseUnityPlugin)this).Config.Bind("Bounty", "ValcoinChancePerTier", 0.05f, "Extra Valcoin chance (0-1) added per tier above 1."); BountyValcoinRankBonus = ((BaseUnityPlugin)this).Config.Bind("Bounty", "ValcoinRankBonus", 0.25f, "Extra Valcoin chance (0-1) for the #1 ranked duel/party player, tapering to zero at ValcoinRankDepth. This is what ties the coin payout to the duel and tournament ladders."); BountyValcoinRankDepth = ((BaseUnityPlugin)this).Config.Bind("Bounty", "ValcoinRankDepth", 10, "How deep the ladder bonus reaches. At rank 1 the full ValcoinRankBonus applies; at this rank and below, none of it does."); BountyValcoinMaxChance = ((BaseUnityPlugin)this).Config.Bind("Bounty", "ValcoinMaxChance", 0.75f, "Hard cap (0-1) on the Valcoin payout chance, so a payout is never guaranteed however high a player ranks."); BountyPointsPerTier = ((BaseUnityPlugin)this).Config.Bind("Bounty", "PointsPerTier", 10, "Bounty-ladder points awarded per tier of the bounty answered (tier 3 pays 3x this). Tier-weighting is what stops the board being won by grinding easy postings."); BountyLeaderboardBonusRank = ((BaseUnityPlugin)this).Config.Bind("Bounty", "LeaderboardBonusRank", 3, "Hunters ranked this high or better on the bounty ladder get +1 reward tier (capped by MaxTierBonus). 0 disables the bonus entirely."); BountyUiKey = ((BaseUnityPlugin)this).Config.Bind("Bounty", "BountyUiKey", (KeyCode)289, "Opens the Wanted Board — open postings, your active bounty, and the hunter standings. There is also a Bounty Board button in your inventory. Close it with Escape."); BountyMaxBoardEntries = ((BaseUnityPlugin)this).Config.Bind("Bounty", "MaxBoardEntries", 3, "How many OPEN postings the board keeps available. Postings players have accepted don't count toward this, so the board still offers work while others are out hunting."); BountyMaxActivePerPlayer = ((BaseUnityPlugin)this).Config.Bind("Bounty", "MaxActiveBountyPerPlayer", 1, "How many bounties one player may hold at a time."); BountyArrivalRadius = ((BaseUnityPlugin)this).Config.Bind("Bounty", "ArrivalRadius", 80f, "How close a hunter must get before the bounty's creatures are spawned, in metres. They aren't spawned until someone travels there — Valheim doesn't simulate unloaded zones, so a camp placed in advance would sit frozen."); BountyButtonOffset = ((BaseUnityPlugin)this).Config.Bind("Bounty", "InventoryButtonOffset", "0,0", "LEGACY — superseded by Interface/MenuBarOffset. The Bounty Board button is now one of a row of menu buttons, so this nudges the WHOLE row. Left in place because it still works: if you had already dialled a value in here it is honoured and MenuBarOffset is ignored. Leave it at \"0,0\" to use MenuBarOffset instead."); BountyRefreshHours = ((BaseUnityPlugin)this).Config.Bind("Bounty", "RefreshHours", 24f, "How long an UNCLAIMED posting stays on the board before it's retired and replaced, in real hours. Accepted bounties never expire — a hunter part-way to their mark keeps it. Set to 0 to disable rotation entirely."); BountyEliteChance = ((BaseUnityPlugin)this).Config.Bind("Bounty", "EliteChance", 0.25f, "Chance (0-1) that a newly posted bounty is an ELITE (top-tier) one. Only ever one open at a time, and only hunters ranked high enough may take it — see EliteRankTopN. The top tier is never reached from a biome alone, so this is the only way it appears."); BountyEliteRankTopN = ((BaseUnityPlugin)this).Config.Bind("Bounty", "EliteRankTopN", 10, "How high a player must rank on the duel OR party ladder to answer elite postings. This is the second, non-Valcoin reason to compete: rank buys ACCESS here, where the coin roll only changes a chance. 0 disables the gate (anyone may take them)."); _harmony = new Harmony("com.lostscrollsii"); _harmony.PatchAll(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); Log.LogInfo((object)"Lost Scrolls II v0.9.1 loaded."); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void Update() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && !TextInput.IsVisible() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Console.IsVisible() && !CompanionInventoryGui.IsTyping && (!TournamentRegistration.IsOpen || Input.GetKeyDown(TournamentUiKey.Value)) && (!BountyBoardPanel.IsOpen || Input.GetKeyDown(BountyUiKey.Value))) { if (Input.GetKeyDown(BountyUiKey.Value)) { BountyBoardPanel.Toggle(localPlayer); } if (Input.GetKeyDown(CommunionKey.Value)) { HandleFeedInput(localPlayer); } if (CommunionRite.BlockHeld()) { TryBeginCommune(localPlayer); TryBeginSeal(localPlayer); } if (Input.GetKeyDown(ChoreAssignKey.Value)) { HandleChoreAssignInput(localPlayer); } if (Input.GetKeyDown(DuelSelectKey.Value)) { HandleDuelInput(localPlayer); } if (Input.GetKeyDown(PartyDuelKey.Value)) { HandlePartyDuelInput(localPlayer); } if (Input.GetKeyDown(StanceCycleKey.Value)) { HandleStanceCycleInput(localPlayer); } if (Input.GetKeyDown(InventoryKey.Value)) { HandleInventoryInput(localPlayer); } if (Input.GetKeyDown(RankingUiKey.Value)) { RankingBoard.Open(); } if (Input.GetKeyDown(TournamentUiKey.Value)) { TournamentRegistration.Toggle(localPlayer); } } } private void HandleInventoryInput(Player player) { GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return; } Character componentInParent = hoverObject.GetComponentInParent(); DvergrCompanion dvergrCompanion = ((componentInParent != null) ? ((Component)componentInParent).GetComponent() : null); if ((Object)(object)dvergrCompanion == (Object)null) { return; } if (!dvergrCompanion.IsOwner(player)) { MessageHud.instance.ShowMessage((MessageType)2, "This companion answers to another.", 0, (Sprite)null, false); return; } CompanionInventory companionInventory = ((Component)dvergrCompanion).GetComponent(); if ((Object)(object)companionInventory == (Object)null) { companionInventory = ((Component)dvergrCompanion).gameObject.AddComponent(); } CompanionInventoryGui.Open(dvergrCompanion, companionInventory); } private void HandleFeedInput(Player player) { GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return; } Character componentInParent = hoverObject.GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { return; } DvergrCompanion component = ((Component)componentInParent).GetComponent(); if (!((Object)(object)component == (Object)null)) { bool flag = component.IsOwner(player); if (MeadFeedingService.TryFeed(componentInParent, player)) { MessageHud.instance.ShowMessage((MessageType)2, flag ? "Your ally drinks deep." : (component.DisplayName + " drinks deep."), 0, (Sprite)null, false); } else { MessageHud.instance.ShowMessage((MessageType)2, "You have no health mead to offer.", 0, (Sprite)null, false); } } } private void TryBeginCommune(Player player) { if ((Object)(object)CommunionRite.Instance == (Object)null || CommunionRite.Instance.IsActive) { return; } GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if (!((Object)(object)hoverObject == (Object)null)) { Character componentInParent = hoverObject.GetComponentInParent(); if (!((Object)(object)componentInParent == (Object)null) && !((Object)(object)((Component)componentInParent).GetComponent() != (Object)null) && CommunionService.IsSubduedDvergr(componentInParent)) { CommunionRite.Instance.Begin(componentInParent, player); } } } private void TryBeginSeal(Player player) { SealingRite instance = SealingRite.Instance; if ((Object)(object)instance == (Object)null || instance.IsActive || ((Object)(object)CommunionRite.Instance != (Object)null && CommunionRite.Instance.IsActive)) { return; } GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if (!((Object)(object)hoverObject == (Object)null)) { Character componentInParent = hoverObject.GetComponentInParent(); DvergrCompanion dvergrCompanion = (((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent).GetComponent() : null); if (!((Object)(object)dvergrCompanion == (Object)null)) { instance.Begin(dvergrCompanion, player); } } } private void HandleChoreAssignInput(Player player) { //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return; } Character componentInParent = hoverObject.GetComponentInParent(); DvergrCompanion dvergrCompanion = (((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent).GetComponent() : null); if ((Object)(object)dvergrCompanion != (Object)null) { if (!dvergrCompanion.IsOwner(player)) { MessageHud.instance.ShowMessage((MessageType)2, "This companion answers to another.", 0, (Sprite)null, false); return; } ChoreAI component = ((Component)dvergrCompanion).GetComponent(); if ((Object)(object)component != (Object)null && component.IsAssigned) { component.Unassign(); MessageHud.instance.ShowMessage((MessageType)2, "Ally returns to your side.", 0, (Sprite)null, false); } else { MessageHud.instance.ShowMessage((MessageType)2, "This ally has no chore to leave.", 0, (Sprite)null, false); } return; } Smelter componentInParent2 = hoverObject.GetComponentInParent(); Fermenter val = (((Object)(object)componentInParent2 == (Object)null) ? hoverObject.GetComponentInParent() : null); CookingStation val2 = (((Object)(object)componentInParent2 == (Object)null && (Object)(object)val == (Object)null) ? hoverObject.GetComponentInParent() : null); Pickable val3 = (((Object)(object)componentInParent2 == (Object)null && (Object)(object)val == (Object)null && (Object)(object)val2 == (Object)null) ? hoverObject.GetComponentInParent() : null); ItemStand val4 = null; Character val5 = null; Container val6 = null; if ((Object)(object)componentInParent2 == (Object)null && (Object)(object)val == (Object)null && (Object)(object)val2 == (Object)null && (Object)(object)val3 == (Object)null) { ItemStand componentInParent3 = hoverObject.GetComponentInParent(); if ((Object)(object)componentInParent3 != (Object)null && componentInParent3.HaveAttachment() && componentInParent3.GetAttachedItem() == "Cultivator") { val4 = componentInParent3; } if ((Object)(object)val4 == (Object)null) { Character componentInParent4 = hoverObject.GetComponentInParent(); if ((Object)(object)componentInParent4 != (Object)null && componentInParent4.IsTamed() && (Object)(object)((Component)componentInParent4).GetComponent() == (Object)null) { val5 = componentInParent4; } if ((Object)(object)val5 == (Object)null) { val6 = hoverObject.GetComponentInParent(); } } } if ((Object)(object)componentInParent2 == (Object)null && (Object)(object)val == (Object)null && (Object)(object)val2 == (Object)null && (Object)(object)val3 == (Object)null && (Object)(object)val4 == (Object)null && (Object)(object)val5 == (Object)null && (Object)(object)val6 == (Object)null) { return; } ChoreAI choreAI = ChoreAI.ClaimantOf(((Object)(object)componentInParent2 != (Object)null) ? ((Component)componentInParent2).gameObject : (((Object)(object)val != (Object)null) ? ((Component)val).gameObject : (((Object)(object)val2 != (Object)null) ? ((Component)val2).gameObject : (((Object)(object)val3 != (Object)null) ? ((Component)val3).gameObject : (((Object)(object)val4 != (Object)null) ? ((Component)val4).gameObject : (((Object)(object)val5 != (Object)null) ? ((Component)val5).gameObject : (((Object)(object)val6 != (Object)null) ? ((Component)val6).gameObject : null))))))); if ((Object)(object)choreAI == (Object)null && (Object)(object)val5 != (Object)null) { choreAI = ChoreAI.FeederCovering(((Component)val5).transform.position); } if ((Object)(object)choreAI != (Object)null) { DvergrCompanion component2 = ((Component)choreAI).GetComponent(); if ((Object)(object)component2 != (Object)null && component2.IsOwner(player)) { choreAI.Unassign(); MessageHud.instance.ShowMessage((MessageType)2, "Ally returns to your side.", 0, (Sprite)null, false); } else { MessageHud.instance.ShowMessage((MessageType)2, choreAI.WorkerName + " is already working here.", 0, (Sprite)null, false); } return; } DvergrCaste? requiredCaste = (((Object)(object)componentInParent2 != (Object)null) ? ChoreRules.RequiredCaste(componentInParent2) : new DvergrCaste?((!((Object)(object)val6 != (Object)null)) ? DvergrCaste.SupportMage : DvergrCaste.Rogue)); DvergrCompanion dvergrCompanion2 = FindNearestRecruitedCompanion(((Component)player).transform.position, ChoreAssignRadius.Value, requiredCaste, player, freeOnly: true); if ((Object)(object)dvergrCompanion2 == (Object)null) { string text = (requiredCaste.HasValue ? ("Only your own free " + ChoreRules.DisplayName(requiredCaste.Value) + " can do this — none nearby.") : "No free ally of yours nearby to assign."); MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false); return; } ChoreAI choreAI2 = ((Component)dvergrCompanion2).GetComponent(); if ((Object)(object)choreAI2 == (Object)null) { choreAI2 = ((Component)dvergrCompanion2).gameObject.AddComponent(); } if ((Object)(object)componentInParent2 != (Object)null) { choreAI2.AssignToSmelter(componentInParent2); MessageHud.instance.ShowMessage((MessageType)2, "Ally tends the station.", 0, (Sprite)null, false); } else if ((Object)(object)val != (Object)null) { choreAI2.AssignToFermenter(val); MessageHud.instance.ShowMessage((MessageType)2, "Ally tends the brew.", 0, (Sprite)null, false); } else if ((Object)(object)val2 != (Object)null) { choreAI2.AssignToCooking(val2); MessageHud.instance.ShowMessage((MessageType)2, "Ally tends the cookfire.", 0, (Sprite)null, false); } else if ((Object)(object)val3 != (Object)null || (Object)(object)val4 != (Object)null) { choreAI2.AssignToFarm(((Object)(object)val3 != (Object)null) ? ((Component)val3).gameObject : ((Component)val4).gameObject); MessageHud.instance.ShowMessage((MessageType)2, "Ally tends the field.", 0, (Sprite)null, false); } else if ((Object)(object)val5 != (Object)null) { choreAI2.AssignToFeedAnimals(((Component)val5).gameObject); MessageHud.instance.ShowMessage((MessageType)2, "Ally tends the animals.", 0, (Sprite)null, false); } else { choreAI2.AssignToHaul(val6); MessageHud.instance.ShowMessage((MessageType)2, "Ally hauls to this chest.", 0, (Sprite)null, false); } } private void HandleDuelInput(Player player) { GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if (!((Object)(object)hoverObject == (Object)null)) { Character componentInParent = hoverObject.GetComponentInParent(); DvergrCompanion dvergrCompanion = (((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent).GetComponent() : null); if ((Object)(object)dvergrCompanion == (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, "That is not an ally you can pit in a duel.", 0, (Sprite)null, false); } else if (!dvergrCompanion.IsOwner(player)) { MessageHud.instance.ShowMessage((MessageType)2, "Only its owner can send this companion to duel.", 0, (Sprite)null, false); } else if (dvergrCompanion.DuelMode) { dvergrCompanion.ExitDuelMode(DvergrCompanion.DuelExitReason.OwnerStopped); } else if (!dvergrCompanion.EnterDuelMode()) { MessageHud.instance.ShowMessage((MessageType)2, "This companion cannot duel right now — unassign its chore first.", 0, (Sprite)null, false); } } } private void HandlePartyDuelInput(Player player) { //IL_0170: Unknown result type (might be due to invalid IL or missing references) GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return; } Character componentInParent = hoverObject.GetComponentInParent(); DvergrCompanion dvergrCompanion = (((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent).GetComponent() : null); if ((Object)(object)dvergrCompanion == (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, "That is not an ally you can send to a party duel.", 0, (Sprite)null, false); return; } if (!dvergrCompanion.IsOwner(player)) { MessageHud.instance.ShowMessage((MessageType)2, "Only its owner can send this companion to duel.", 0, (Sprite)null, false); return; } long playerID = player.GetPlayerID(); bool flag = false; foreach (DvergrCompanion item in DvergrCompanion.All) { if ((Object)(object)item != (Object)null && item.OwnerId == playerID && item.PartyDuelMode) { flag = true; break; } } if (flag) { int num = 0; foreach (DvergrCompanion item2 in DvergrCompanion.All) { if ((Object)(object)item2 != (Object)null && item2.OwnerId == playerID && item2.PartyDuelMode) { item2.ExitPartyDuelMode(DvergrCompanion.DuelExitReason.OwnerStopped); num++; } } MessageHud.instance.ShowMessage((MessageType)2, $"Your party ({num}) stands down.", 0, (Sprite)null, false); return; } List list = new List(); Character.GetCharactersInRange(((Component)player).transform.position, 25f, list); List list2 = new List(); foreach (Character item3 in list) { DvergrCompanion component = ((Component)item3).GetComponent(); if (!((Object)(object)component == (Object)null) && component.OwnerId == playerID && component.Stance == CompanionStance.Follow) { ChoreAI component2 = ((Component)item3).GetComponent(); if ((component2 == null || !component2.IsAssigned) && !component.DuelMode && !component.IsFeral) { list2.Add(component); } } } if (list2.Count == 0) { MessageHud.instance.ShowMessage((MessageType)2, "No free Follow-stance allies of yours nearby to form a party.", 0, (Sprite)null, false); return; } list2.Sort((DvergrCompanion a, DvergrCompanion b) => Vector3.Distance(((Component)player).transform.position, ((Component)a).transform.position).CompareTo(Vector3.Distance(((Component)player).transform.position, ((Component)b).transform.position))); int num2 = Mathf.Max(1, MaxPartySize.Value); int num3 = 0; foreach (DvergrCompanion item4 in list2) { if (num3 >= num2) { break; } if (item4.EnterPartyDuelMode()) { num3++; } } MessageHud.instance.ShowMessage((MessageType)2, $"Your party ({num3}) squares up — waiting for a rival party.", 0, (Sprite)null, false); } private void HandleStanceCycleInput(Player player) { GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return; } Character componentInParent = hoverObject.GetComponentInParent(); DvergrCompanion dvergrCompanion = ((componentInParent != null) ? ((Component)componentInParent).GetComponent() : null); if ((Object)(object)dvergrCompanion == (Object)null) { return; } if (!dvergrCompanion.IsOwner(player)) { MessageHud.instance.ShowMessage((MessageType)2, "This companion answers to another.", 0, (Sprite)null, false); return; } ChoreAI component = ((Component)componentInParent).GetComponent(); if ((component != null && component.IsAssigned) || dvergrCompanion.InAnyDuelMode) { MessageHud.instance.ShowMessage((MessageType)2, "Ally is busy — unassign it first.", 0, (Sprite)null, false); return; } CompanionStance companionStance = ((dvergrCompanion.Stance == CompanionStance.Follow) ? CompanionStance.Guard : ((dvergrCompanion.Stance == CompanionStance.Guard) ? CompanionStance.Standby : CompanionStance.Follow)); dvergrCompanion.SetStance(companionStance, ((Component)player).gameObject); MessageHud.instance.ShowMessage((MessageType)2, "Ally stance: " + companionStance switch { CompanionStance.Guard => "Guard", CompanionStance.Follow => "Follow", _ => "Standby", } + ".", 0, (Sprite)null, false); } private static DvergrCompanion FindNearestRecruitedCompanion(Vector3 origin, float radius, DvergrCaste? requiredCaste = null, Player owner = null, bool freeOnly = false) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) List list = new List(); Character.GetCharactersInRange(origin, radius, list); DvergrCompanion result = null; float num = float.MaxValue; foreach (Character item in list) { DvergrCompanion component = ((Component)item).GetComponent(); if ((Object)(object)component == (Object)null || (requiredCaste.HasValue && component.Caste != requiredCaste.Value) || ((Object)(object)owner != (Object)null && !component.IsOwner(owner))) { continue; } if (freeOnly) { ChoreAI component2 = ((Component)item).GetComponent(); if (component2 != null && component2.IsAssigned) { continue; } } float num2 = Vector3.Distance(origin, ((Component)item).transform.position); if (num2 < num) { num = num2; result = component; } } return result; } } } namespace LostScrollsII.Ranking { public static class CompetitiveJson { private class Writer { private readonly StringBuilder _sb = new StringBuilder(256); private readonly bool _pretty; private int _depth; private bool _needComma; public Writer(bool pretty) { _pretty = pretty; } public void BeginObj() { Sep(); _sb.Append('{'); _depth++; _needComma = false; } public void EndObj() { _depth--; NewlineIndent(); _sb.Append('}'); _needComma = true; } public void BeginArr() { _sb.Append('['); _depth++; _needComma = false; } public void EndArr() { _depth--; NewlineIndent(); _sb.Append(']'); _needComma = true; } public void Key(string k) { Sep(); _sb.Append('"').Append(k).Append("\":"); if (_pretty) { _sb.Append(' '); } _needComma = false; } public void Field(string k, string v) { Key(k); WriteString(v); _needComma = true; } public void Field(string k, int v) { Key(k); _sb.Append(v.ToString(CultureInfo.InvariantCulture)); _needComma = true; } public void Field(string k, long v) { Key(k); _sb.Append(v.ToString(CultureInfo.InvariantCulture)); _needComma = true; } public void Field(string k, bool v) { Key(k); _sb.Append(v ? "true" : "false"); _needComma = true; } public void Field(string k, float v) { Key(k); _sb.Append(v.ToString("R", CultureInfo.InvariantCulture)); _needComma = true; } public void ArrString(string v) { Sep(); WriteString(v); _needComma = true; } private void Sep() { if (_needComma) { _sb.Append(','); } NewlineIndent(); } private void NewlineIndent() { if (_pretty && _depth != 0) { _sb.Append('\n').Append(' ', _depth * 4); } } private void WriteString(string v) { _sb.Append('"'); string text = v ?? string.Empty; foreach (char c in text) { switch (c) { case '"': _sb.Append("\\\""); continue; case '\\': _sb.Append("\\\\"); continue; case '\n': _sb.Append("\\n"); continue; case '\r': _sb.Append("\\r"); continue; case '\t': _sb.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder = _sb.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("x4")); } else { _sb.Append(c); } } _sb.Append('"'); } public override string ToString() { return _sb.ToString(); } } public static string Write(LeaderboardData d, bool pretty = false) { d = d ?? new LeaderboardData(); Writer writer = new Writer(pretty); writer.BeginObj(); writer.Field("seasonId", d.seasonId); writer.Key("companions"); writer.BeginArr(); foreach (CompanionRecord item in d.companions ?? new List()) { WriteCompanion(writer, item); } writer.EndArr(); writer.Key("parties"); writer.BeginArr(); foreach (PartyRecord item2 in d.parties ?? new List()) { WriteParty(writer, item2); } writer.EndArr(); writer.EndObj(); return writer.ToString(); } public static string Write(TournamentBook b, bool pretty = false) { b = b ?? new TournamentBook(); Writer writer = new Writer(pretty); writer.BeginObj(); writer.Key("tournaments"); writer.BeginArr(); foreach (TournamentState item in b.tournaments ?? new List()) { if (item != null) { WriteTournamentObj(writer, item); } } writer.EndArr(); writer.EndObj(); return writer.ToString(); } public static TournamentBook ReadTournamentBook(string json) { TournamentBook tournamentBook = new TournamentBook(); if (!(Parse(json) is Dictionary dictionary)) { return tournamentBook; } if (dictionary.ContainsKey("tournaments")) { foreach (object item in Arr(dictionary, "tournaments")) { if (item is Dictionary root) { tournamentBook.tournaments.Add(ReadTournamentObj(root)); } } return tournamentBook; } TournamentState tournamentState = ReadTournamentObj(dictionary); tournamentState.key = ""; tournamentBook.tournaments.Add(tournamentState); return tournamentBook; } public static string Write(TournamentState s, bool pretty = false) { Writer writer = new Writer(pretty); WriteTournamentObj(writer, s ?? new TournamentState()); return writer.ToString(); } private static void WriteTournamentObj(Writer w, TournamentState s) { w.BeginObj(); w.Field("key", s.key); w.Field("currency", s.currency); w.Field("entryFee", s.entryFee); w.Field("prize", s.prize); w.Field("hostId", s.hostId); w.Field("hostName", s.hostName); w.Field("openedTicks", s.openedTicks); w.Field("hostCredit", s.hostCredit); w.Field("completedTicks", s.completedTicks); w.Field("active", s.active); w.Field("mode", s.mode); w.Field("phase", s.phase); w.Field("size", s.size); w.Field("bracketSize", s.bracketSize); w.Field("currentRound", s.currentRound); w.Field("championId", s.championId); w.Field("championLabel", s.championLabel); w.Field("seasonId", s.seasonId); w.Field("eliminationType", s.eliminationType); w.Field("totalRounds", s.totalRounds); w.Key("entrants"); w.BeginArr(); foreach (TournamentEntrant item in s.entrants ?? new List()) { WriteEntrant(w, item); } w.EndArr(); w.Key("matches"); w.BeginArr(); foreach (TournamentMatch item2 in s.matches ?? new List()) { WriteMatch(w, item2); } w.EndArr(); w.EndObj(); } public static string Write(BountyLadderData d, bool pretty = false) { d = d ?? new BountyLadderData(); Writer writer = new Writer(pretty); writer.BeginObj(); writer.Field("seasonId", d.seasonId); writer.Key("hunters"); writer.BeginArr(); foreach (BountyHunterRecord item in d.hunters ?? new List()) { if (item != null) { writer.BeginObj(); writer.Field("ownerId", item.ownerId); writer.Field("ownerName", item.ownerName); writer.Field("points", item.points); writer.Field("kills", item.kills); writer.Field("communes", item.communes); writer.Field("bestTier", item.bestTier); writer.Field("lastResolvedTicks", item.lastResolvedTicks); writer.Field("seasonId", item.seasonId); writer.EndObj(); } } writer.EndArr(); writer.EndObj(); return writer.ToString(); } public static BountyLadderData ReadBountyLadder(string json) { Dictionary dictionary = Parse(json) as Dictionary; BountyLadderData bountyLadderData = new BountyLadderData(); if (dictionary == null) { return bountyLadderData; } bountyLadderData.seasonId = I(dictionary, "seasonId", bountyLadderData.seasonId); foreach (object item in Arr(dictionary, "hunters")) { if (item is Dictionary d) { bountyLadderData.hunters.Add(new BountyHunterRecord { ownerId = L(d, "ownerId", 0L), ownerName = S(d, "ownerName"), points = I(d, "points"), kills = I(d, "kills"), communes = I(d, "communes"), bestTier = I(d, "bestTier"), lastResolvedTicks = L(d, "lastResolvedTicks", 0L), seasonId = I(d, "seasonId", 1) }); } } return bountyLadderData; } public static string Write(BountyBoardData d, bool pretty = false) { d = d ?? new BountyBoardData(); Writer writer = new Writer(pretty); writer.BeginObj(); writer.Key("postings"); writer.BeginArr(); foreach (BountyPosting item in d.postings ?? new List()) { if (item != null) { writer.BeginObj(); writer.Field("id", item.id); writer.Field("tier", item.tier); writer.Field("biome", item.biome); writer.Field("x", item.x); writer.Field("y", item.y); writer.Field("z", item.z); writer.Field("acceptedBy", item.acceptedBy); writer.Field("acceptedByName", item.acceptedByName); writer.Field("spawned", item.spawned); writer.Field("postedTicks", item.postedTicks); writer.Field("tutorial", item.tutorial); writer.EndObj(); } } writer.EndArr(); writer.EndObj(); return writer.ToString(); } public static BountyBoardData ReadBountyBoard(string json) { Dictionary dictionary = Parse(json) as Dictionary; BountyBoardData bountyBoardData = new BountyBoardData(); if (dictionary == null) { return bountyBoardData; } foreach (object item in Arr(dictionary, "postings")) { if (item is Dictionary d) { bountyBoardData.postings.Add(new BountyPosting { id = S(d, "id"), tier = I(d, "tier", 1), biome = I(d, "biome"), x = F(d, "x"), y = F(d, "y"), z = F(d, "z"), acceptedBy = L(d, "acceptedBy", 0L), acceptedByName = S(d, "acceptedByName"), spawned = B(d, "spawned"), postedTicks = L(d, "postedTicks", 0L), tutorial = B(d, "tutorial") }); } } return bountyBoardData; } public static string Write(ChampionsData d, bool pretty = false) { d = d ?? new ChampionsData(); Writer writer = new Writer(pretty); writer.BeginObj(); writer.Key("champions"); writer.BeginArr(); foreach (ChampionRecord item in d.champions ?? new List()) { writer.BeginObj(); writer.Field("mode", item.mode); writer.Field("championLabel", item.championLabel); writer.Field("ownerName", item.ownerName); writer.Field("bracketSize", item.bracketSize); writer.Field("seasonId", item.seasonId); writer.Field("dateTicks", item.dateTicks); writer.EndObj(); } writer.EndArr(); writer.EndObj(); return writer.ToString(); } private static void WriteCompanion(Writer w, CompanionRecord c) { w.BeginObj(); w.Field("companionId", c.companionId); w.Field("ownerId", c.ownerId); w.Field("ownerName", c.ownerName); w.Field("companionName", c.companionName); w.Field("caste", c.caste); w.Field("wins", c.wins); w.Field("losses", c.losses); w.Field("winStreak", c.winStreak); w.Field("bestStreak", c.bestStreak); w.Field("rating", c.rating); w.Field("lastMatchTicks", c.lastMatchTicks); w.Field("seasonId", c.seasonId); w.EndObj(); } private static void WriteParty(Writer w, PartyRecord p) { w.BeginObj(); w.Field("ownerId", p.ownerId); w.Field("ownerName", p.ownerName); w.Field("partyName", p.partyName); w.Field("wins", p.wins); w.Field("losses", p.losses); w.Field("rating", p.rating); w.Field("bestTeamSize", p.bestTeamSize); w.Field("lastMatchTicks", p.lastMatchTicks); w.Field("seasonId", p.seasonId); w.Key("memberSnapshot"); w.BeginArr(); foreach (PartyMemberSnap item in p.memberSnapshot ?? new List()) { w.BeginObj(); w.Field("companionId", item.companionId); w.Field("caste", item.caste); w.Field("level", item.level); w.EndObj(); } w.EndArr(); w.EndObj(); } private static void WriteEntrant(Writer w, TournamentEntrant e) { w.BeginObj(); w.Field("entrantId", e.entrantId); w.Field("ownerId", e.ownerId); w.Field("ownerName", e.ownerName); w.Field("label", e.label); w.Field("caste", e.caste); w.Field("seedRating", e.seedRating); w.Field("eliminated", e.eliminated); w.Field("level", e.level); w.Field("losses", e.losses); w.Field("wins", e.wins); w.Field("totemPayload", e.totemPayload); w.Field("feePaid", e.feePaid); w.Key("teamPayloads"); w.BeginArr(); foreach (string item in e.teamPayloads ?? new List()) { w.ArrString(item); } w.EndArr(); w.EndObj(); } private static void WriteMatch(Writer w, TournamentMatch m) { w.BeginObj(); w.Field("round", m.round); w.Field("aId", m.aId); w.Field("bId", m.bId); w.Field("aLabel", m.aLabel); w.Field("bLabel", m.bLabel); w.Field("aLevel", m.aLevel); w.Field("bLevel", m.bLevel); w.Field("winnerId", m.winnerId); w.Field("bracket", m.bracket); w.Field("aReady", m.aReady); w.Field("bReady", m.bReady); w.Field("activated", m.activated); w.EndObj(); } public static LeaderboardData ReadLeaderboard(string json) { Dictionary dictionary = Parse(json) as Dictionary; LeaderboardData leaderboardData = new LeaderboardData(); if (dictionary == null) { return leaderboardData; } leaderboardData.seasonId = I(dictionary, "seasonId", leaderboardData.seasonId); foreach (object item in Arr(dictionary, "companions")) { if (item is Dictionary d) { leaderboardData.companions.Add(new CompanionRecord { companionId = S(d, "companionId"), ownerId = L(d, "ownerId", 0L), ownerName = S(d, "ownerName"), companionName = S(d, "companionName"), caste = I(d, "caste"), wins = I(d, "wins"), losses = I(d, "losses"), winStreak = I(d, "winStreak"), bestStreak = I(d, "bestStreak"), rating = I(d, "rating", 1000), lastMatchTicks = L(d, "lastMatchTicks", 0L), seasonId = I(d, "seasonId", 1) }); } } foreach (object item2 in Arr(dictionary, "parties")) { if (!(item2 is Dictionary d2)) { continue; } PartyRecord partyRecord = new PartyRecord { ownerId = L(d2, "ownerId", 0L), ownerName = S(d2, "ownerName"), partyName = S(d2, "partyName"), wins = I(d2, "wins"), losses = I(d2, "losses"), rating = I(d2, "rating", 1000), bestTeamSize = I(d2, "bestTeamSize"), lastMatchTicks = L(d2, "lastMatchTicks", 0L), seasonId = I(d2, "seasonId", 1) }; foreach (object item3 in Arr(d2, "memberSnapshot")) { if (item3 is Dictionary d3) { partyRecord.memberSnapshot.Add(new PartyMemberSnap { companionId = S(d3, "companionId"), caste = I(d3, "caste"), level = I(d3, "level") }); } } leaderboardData.parties.Add(partyRecord); } return leaderboardData; } public static TournamentState ReadTournament(string json) { if (Parse(json) is Dictionary root) { return ReadTournamentObj(root); } return new TournamentState(); } private static TournamentState ReadTournamentObj(Dictionary root) { TournamentState tournamentState = new TournamentState(); if (root == null) { return tournamentState; } tournamentState.key = S(root, "key"); tournamentState.currency = S(root, "currency"); tournamentState.entryFee = I(root, "entryFee"); tournamentState.prize = I(root, "prize"); tournamentState.hostId = L(root, "hostId", 0L); tournamentState.hostName = S(root, "hostName"); tournamentState.openedTicks = L(root, "openedTicks", 0L); tournamentState.hostCredit = I(root, "hostCredit"); tournamentState.completedTicks = L(root, "completedTicks", 0L); tournamentState.active = B(root, "active"); tournamentState.mode = S(root, "mode", tournamentState.mode); tournamentState.phase = S(root, "phase", tournamentState.phase); tournamentState.size = I(root, "size"); tournamentState.bracketSize = I(root, "bracketSize"); tournamentState.currentRound = I(root, "currentRound"); tournamentState.championId = S(root, "championId"); tournamentState.championLabel = S(root, "championLabel"); tournamentState.seasonId = I(root, "seasonId", 1); tournamentState.eliminationType = S(root, "eliminationType", tournamentState.eliminationType); tournamentState.totalRounds = I(root, "totalRounds"); foreach (object item2 in Arr(root, "entrants")) { if (!(item2 is Dictionary d)) { continue; } TournamentEntrant tournamentEntrant = new TournamentEntrant { entrantId = S(d, "entrantId"), ownerId = L(d, "ownerId", 0L), ownerName = S(d, "ownerName"), label = S(d, "label"), caste = I(d, "caste"), seedRating = I(d, "seedRating"), eliminated = B(d, "eliminated"), level = I(d, "level"), losses = I(d, "losses"), wins = I(d, "wins"), totemPayload = S(d, "totemPayload"), feePaid = I(d, "feePaid") }; foreach (object item3 in Arr(d, "teamPayloads")) { if (item3 is string item) { tournamentEntrant.teamPayloads.Add(item); } } tournamentState.entrants.Add(tournamentEntrant); } foreach (object item4 in Arr(root, "matches")) { if (item4 is Dictionary d2) { tournamentState.matches.Add(new TournamentMatch { round = I(d2, "round"), aId = S(d2, "aId"), bId = S(d2, "bId"), aLabel = S(d2, "aLabel"), bLabel = S(d2, "bLabel"), aLevel = I(d2, "aLevel"), bLevel = I(d2, "bLevel"), winnerId = S(d2, "winnerId"), bracket = S(d2, "bracket", "W"), aReady = B(d2, "aReady"), bReady = B(d2, "bReady"), activated = B(d2, "activated") }); } } return tournamentState; } public static string Write(DuelInviteBoard b, bool pretty = false) { b = b ?? new DuelInviteBoard(); Writer writer = new Writer(pretty); writer.BeginObj(); writer.Key("invites"); writer.BeginArr(); foreach (DuelInvite item in b.invites ?? new List()) { if (item != null) { writer.BeginObj(); writer.Field("id", item.id); writer.Field("currency", item.currency); writer.Field("stake", item.stake); writer.Field("phase", item.phase); writer.Field("hostId", item.hostId); writer.Field("hostName", item.hostName); writer.Field("hostEntrantId", item.hostEntrantId); writer.Field("hostLabel", item.hostLabel); writer.Field("hostLevel", item.hostLevel); writer.Field("hostCaste", item.hostCaste); writer.Field("hostPayload", item.hostPayload); writer.Field("hostReady", item.hostReady); writer.Field("oppId", item.oppId); writer.Field("oppName", item.oppName); writer.Field("oppEntrantId", item.oppEntrantId); writer.Field("oppLabel", item.oppLabel); writer.Field("oppLevel", item.oppLevel); writer.Field("oppCaste", item.oppCaste); writer.Field("oppPayload", item.oppPayload); writer.Field("oppReady", item.oppReady); writer.Field("createdTicks", item.createdTicks); writer.Field("completedTicks", item.completedTicks); writer.EndObj(); } } writer.EndArr(); writer.EndObj(); return writer.ToString(); } public static DuelInviteBoard ReadInvites(string json) { DuelInviteBoard duelInviteBoard = new DuelInviteBoard(); if (!(Parse(json) is Dictionary d)) { return duelInviteBoard; } foreach (object item in Arr(d, "invites")) { if (item is Dictionary d2) { duelInviteBoard.invites.Add(new DuelInvite { id = S(d2, "id"), currency = S(d2, "currency"), stake = I(d2, "stake"), phase = S(d2, "phase", "open"), hostId = L(d2, "hostId", 0L), hostName = S(d2, "hostName"), hostEntrantId = S(d2, "hostEntrantId"), hostLabel = S(d2, "hostLabel"), hostLevel = I(d2, "hostLevel"), hostCaste = I(d2, "hostCaste"), hostPayload = S(d2, "hostPayload"), hostReady = B(d2, "hostReady"), oppId = L(d2, "oppId", 0L), oppName = S(d2, "oppName"), oppEntrantId = S(d2, "oppEntrantId"), oppLabel = S(d2, "oppLabel"), oppLevel = I(d2, "oppLevel"), oppCaste = I(d2, "oppCaste"), oppPayload = S(d2, "oppPayload"), oppReady = B(d2, "oppReady"), createdTicks = L(d2, "createdTicks", 0L), completedTicks = L(d2, "completedTicks", 0L) }); } } return duelInviteBoard; } public static ChampionsData ReadChampions(string json) { Dictionary dictionary = Parse(json) as Dictionary; ChampionsData championsData = new ChampionsData(); if (dictionary == null) { return championsData; } foreach (object item in Arr(dictionary, "champions")) { if (item is Dictionary d) { championsData.champions.Add(new ChampionRecord { mode = S(d, "mode"), championLabel = S(d, "championLabel"), ownerName = S(d, "ownerName"), bracketSize = I(d, "bracketSize"), seasonId = I(d, "seasonId", 1), dateTicks = L(d, "dateTicks", 0L) }); } } return championsData; } private static string S(Dictionary d, string k, string def = "") { if (!d.TryGetValue(k, out var value) || !(value is string result)) { return def; } return result; } private static int I(Dictionary d, string k, int def = 0) { if (!d.TryGetValue(k, out var value)) { return def; } if (!(value is long num)) { if (!(value is double num2)) { return def; } return (int)num2; } return (int)num; } private static long L(Dictionary d, string k, long def = 0L) { if (!d.TryGetValue(k, out var value)) { return def; } if (value is long) { return (long)value; } if (!(value is double num)) { return def; } return (long)num; } private static bool B(Dictionary d, string k, bool def = false) { if (d.TryGetValue(k, out var value) && value is bool) { return (bool)value; } return def; } private static float F(Dictionary d, string k, float def = 0f) { if (!d.TryGetValue(k, out var value)) { return def; } if (!(value is double num)) { if (!(value is long num2)) { return def; } return num2; } return (float)num; } private static List Arr(Dictionary d, string k) { if (!d.TryGetValue(k, out var value) || !(value is List result)) { return new List(); } return result; } private static object Parse(string json) { if (string.IsNullOrEmpty(json)) { return null; } int i = 0; try { return ParseValue(json, ref i); } catch { return null; } } private static object ParseValue(string s, ref int i) { SkipWs(s, ref i); switch (s[i]) { case '{': return ParseObject(s, ref i); case '[': return ParseArray(s, ref i); case '"': return ParseString(s, ref i); case 't': Expect(s, ref i, "true"); return true; case 'f': Expect(s, ref i, "false"); return false; case 'n': Expect(s, ref i, "null"); return null; default: return ParseNumber(s, ref i); } } private static Dictionary ParseObject(string s, ref int i) { Dictionary dictionary = new Dictionary(); i++; SkipWs(s, ref i); if (s[i] == '}') { i++; return dictionary; } while (true) { SkipWs(s, ref i); string key = ParseString(s, ref i); SkipWs(s, ref i); if (s[i] != ':') { throw new FormatException("expected ':'"); } i++; dictionary[key] = ParseValue(s, ref i); SkipWs(s, ref i); if (s[i] != ',') { break; } i++; } if (s[i] == '}') { i++; return dictionary; } throw new FormatException("expected ',' or '}'"); } private static List ParseArray(string s, ref int i) { List list = new List(); i++; SkipWs(s, ref i); if (s[i] == ']') { i++; return list; } while (true) { list.Add(ParseValue(s, ref i)); SkipWs(s, ref i); if (s[i] != ',') { break; } i++; } if (s[i] == ']') { i++; return list; } throw new FormatException("expected ',' or ']'"); } private static string ParseString(string s, ref int i) { if (s[i] != '"') { throw new FormatException("expected '\"'"); } i++; StringBuilder stringBuilder = new StringBuilder(); while (true) { char c = s[i++]; switch (c) { case '"': return stringBuilder.ToString(); case '\\': switch (s[i++]) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'u': stringBuilder.Append((char)int.Parse(s.Substring(i, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture)); i += 4; break; default: throw new FormatException("bad escape"); } break; default: stringBuilder.Append(c); break; } } } private static object ParseNumber(string s, ref int i) { int num = i; while (i < s.Length && (char.IsDigit(s[i]) || s[i] == '-' || s[i] == '+' || s[i] == '.' || s[i] == 'e' || s[i] == 'E')) { i++; } string text = s.Substring(num, i - num); if (text.IndexOf('.') < 0 && text.IndexOf('e') < 0 && text.IndexOf('E') < 0 && long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } return double.Parse(text, CultureInfo.InvariantCulture); } private static void Expect(string s, ref int i, string word) { if (string.CompareOrdinal(s, i, word, 0, word.Length) != 0) { throw new FormatException("expected '" + word + "'"); } i += word.Length; } private static void SkipWs(string s, ref int i) { while (i < s.Length && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r')) { i++; } } } [Serializable] public class DuelInvite { public string id = ""; public string currency = ""; public int stake; public string phase = "open"; public long hostId; public string hostName = ""; public string hostEntrantId = ""; public string hostLabel = ""; public int hostLevel; public int hostCaste; public string hostPayload = ""; public bool hostReady; public long oppId; public string oppName = ""; public string oppEntrantId = ""; public string oppLabel = ""; public int oppLevel; public int oppCaste; public string oppPayload = ""; public bool oppReady; public long createdTicks; public long completedTicks; public bool Involves(long ownerId) { if (ownerId != 0L) { if (hostId != ownerId) { return oppId == ownerId; } return true; } return false; } public bool HasEntrant(string entrantId) { if (!string.IsNullOrEmpty(entrantId)) { if (!(hostEntrantId == entrantId)) { return oppEntrantId == entrantId; } return true; } return false; } public bool IsHostEntrant(string entrantId) { if (!string.IsNullOrEmpty(entrantId)) { return hostEntrantId == entrantId; } return false; } } [Serializable] public class DuelInviteBoard { public List invites = new List(); public DuelInvite Find(string id) { if (!string.IsNullOrEmpty(id)) { return invites.Find((DuelInvite i) => i != null && i.id == id); } return null; } public DuelInvite ForOwner(long ownerId) { if (ownerId != 0L) { return invites.Find((DuelInvite i) => i?.Involves(ownerId) ?? false); } return null; } public DuelInvite ForEntrant(string entrantId) { if (!string.IsNullOrEmpty(entrantId)) { return invites.Find((DuelInvite i) => i?.HasEntrant(entrantId) ?? false); } return null; } } public static class DuelInviteService { private static DuelInviteBoard _board = new DuelInviteBoard(); private static DuelInviteBoard _snapshot = new DuelInviteBoard(); private const double CompletionGraceSeconds = 6.0; public static DuelInviteBoard Snapshot => _snapshot; public static IEnumerable All => (_snapshot?.invites ?? new List()).Where((DuelInvite i) => i != null); public static DuelInvite MineIn(long ownerId) { return _snapshot?.ForOwner(ownerId); } public static DuelInvite FindByEntrant(string entrantId) { return _snapshot?.ForEntrant(entrantId); } private static string BoardPath() { return Path.Combine(LeaderboardStore.DataDir(), "duelinvites." + LeaderboardStore.WorldTag() + ".json"); } public static void LoadForCurrentWorld() { try { string path = BoardPath(); _board = (File.Exists(path) ? (CompetitiveJson.ReadInvites(File.ReadAllText(path)) ?? new DuelInviteBoard()) : new DuelInviteBoard()); if (_board.invites == null) { _board.invites = new List(); } Plugin.Log.LogInfo((object)$"[invite] resumed {_board.invites.Count} duel invite(s)."); } catch (Exception ex) { _board = new DuelInviteBoard(); Plugin.Log.LogWarning((object)("[invite] load failed: " + ex.Message)); } _snapshot = _board; } private static void Save() { try { File.WriteAllText(BoardPath(), CompetitiveJson.Write(_board, pretty: true)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[invite] save failed: " + ex.Message)); } } public static string SerializeSnapshot() { return CompetitiveJson.Write(_board ?? new DuelInviteBoard()); } public static void ApplySnapshot(string json) { try { DuelInviteBoard duelInviteBoard = CompetitiveJson.ReadInvites(json) ?? new DuelInviteBoard(); if (duelInviteBoard.invites == null) { duelInviteBoard.invites = new List(); } _snapshot = duelInviteBoard; } catch (Exception ex) { Plugin.Log.LogWarning((object)("[invite] snapshot apply failed: " + ex.Message)); } } public static void Post(WagerCurrency currency, long hostId, string hostName, string entrantId, string label, int level, int caste, string payload, int clientPaidCoins, Action done) { if (!WagerService.IsAvailable(currency, out var reason)) { done?.Invoke(arg1: false, reason); return; } if (string.IsNullOrEmpty(entrantId)) { done?.Invoke(arg1: false, "That totem has no ladder identity and can't be staked."); return; } if (string.IsNullOrEmpty(payload)) { done?.Invoke(arg1: false, "No companion totem was locked in."); return; } DuelInvite duelInvite = _board.ForOwner(hostId); if (duelInvite != null) { done?.Invoke(arg1: false, (duelInvite.hostId == hostId) ? "You already have a duel invite posted — withdraw it first." : "You have already accepted someone else's duel invite."); return; } int stake = Wager.DuelStake(currency); WagerService.Charge(currency, hostName, stake, "ls_duel_stake", "posted a duel invite", clientPaidCoins, delegate(bool ok, string msg) { if (!ok) { done?.Invoke(arg1: false, msg ?? "The stake could not be taken."); } else if (_board.ForOwner(hostId) != null) { WagerService.RollBackOwnCharge(currency, hostId, hostName, stake, "you were already in another duel invite"); done?.Invoke(arg1: false, "You joined another duel invite while your stake was being processed — it is returned."); } else { DuelInvite duelInvite2 = new DuelInvite { id = Guid.NewGuid().ToString("N").Substring(0, 12), currency = Wager.Key(currency), stake = stake, phase = "open", hostId = hostId, hostName = (hostName ?? string.Empty), hostEntrantId = entrantId, hostLabel = (label ?? string.Empty), hostLevel = level, hostCaste = caste, hostPayload = payload, createdTicks = DateTime.UtcNow.Ticks }; _board.invites.Add(duelInvite2); Save(); Broadcast(); ServerGuideBridge.AnnounceDiscord("\ud83d\udce3 **" + hostName + "** has posted a duel invite — **" + label + "**" + ((level > 0) ? $" (Lv{level})" : "") + $" · stake **{stake} {Wager.Display(currency)}**, winner takes **{stake * 2}**. " + "Accept it from the Tournament panel (F7)."); Plugin.Log.LogInfo((object)$"[invite] {hostName} posted '{duelInvite2.id}' ({stake} {duelInvite2.currency})."); done?.Invoke(arg1: true, $"Duel invite posted — {stake} {Wager.Display(currency)} staked. " + "The winner takes both stakes."); } }); } public static void Accept(string inviteId, long oppId, string oppName, string entrantId, string label, int level, int caste, string payload, int clientPaidCoins, Action done) { DuelInvite invite = _board.Find(inviteId); if (invite == null || invite.phase != "open") { done?.Invoke(arg1: false, "That duel invite is no longer open."); return; } if (invite.hostId == oppId) { done?.Invoke(arg1: false, "You cannot accept your own invite."); return; } if (_board.ForOwner(oppId) != null) { done?.Invoke(arg1: false, "You are already tied up in a duel invite."); return; } if (string.IsNullOrEmpty(entrantId)) { done?.Invoke(arg1: false, "That totem has no ladder identity and can't be staked."); return; } if (string.IsNullOrEmpty(payload)) { done?.Invoke(arg1: false, "No companion totem was locked in."); return; } WagerCurrency currency = Wager.Parse(invite.currency); if (!WagerService.IsAvailable(currency, out var reason)) { done?.Invoke(arg1: false, reason); return; } int stake = invite.stake; WagerService.Charge(currency, oppName, stake, "ls_duel_stake", "accepted a duel invite", clientPaidCoins, delegate(bool ok, string msg) { if (!ok) { done?.Invoke(arg1: false, msg ?? "The stake could not be taken."); } else { invite = _board.Find(inviteId); if (invite == null || invite.phase != "open" || _board.ForOwner(oppId) != null) { WagerService.RollBackOwnCharge(currency, oppId, oppName, stake, "the invite was already taken"); done?.Invoke(arg1: false, "That duel invite was taken while your stake was being processed."); } else { invite.phase = "matched"; invite.oppId = oppId; invite.oppName = oppName ?? string.Empty; invite.oppEntrantId = entrantId; invite.oppLabel = label ?? string.Empty; invite.oppLevel = level; invite.oppCaste = caste; invite.oppPayload = payload; Save(); Broadcast(); ServerGuideBridge.AnnounceDiscord("\ud83e\udd1d **" + oppName + "** has accepted **" + invite.hostName + "**'s duel invite — " + $"**{invite.hostLabel}** vs **{label}** for **{invite.stake * 2} {Wager.Display(currency)}**. " + "The two of them choose where to fight; the duel starts once both are ready."); done?.Invoke(arg1: true, "Challenge accepted — meet " + invite.hostName + " and press Ready to Fight."); } } }); } public static string SetReady(string inviteId, long ownerId) { DuelInvite duelInvite = _board.Find(inviteId); if (duelInvite == null) { return "that duel invite no longer exists."; } if (duelInvite.phase == "running") { return "the duel is already under way."; } if (duelInvite.phase != "matched") { return "nobody has accepted that invite yet."; } if (duelInvite.hostId == ownerId) { duelInvite.hostReady = true; } else { if (duelInvite.oppId != ownerId) { return "you are not part of that duel."; } duelInvite.oppReady = true; } if (!duelInvite.hostReady || !duelInvite.oppReady) { Save(); Broadcast(); string text = ((duelInvite.hostId == ownerId) ? duelInvite.oppName : duelInvite.hostName); return "Ready. Waiting for " + text + "."; } duelInvite.phase = "running"; Save(); Broadcast(); LeaderboardSync.SummonForMatch(duelInvite.hostId, duelInvite.hostEntrantId, "1v1", duelInvite.oppEntrantId, duelInvite.oppLabel, 0, new List { duelInvite.hostPayload }, "invite"); LeaderboardSync.SummonForMatch(duelInvite.oppId, duelInvite.oppEntrantId, "1v1", duelInvite.hostEntrantId, duelInvite.hostLabel, 0, new List { duelInvite.oppPayload }, "invite"); ServerGuideBridge.AnnounceDiscord("\ud83e\udd4a **The staked duel begins:** " + duelInvite.hostLabel + " (" + duelInvite.hostName + ") vs " + $"{duelInvite.oppLabel} ({duelInvite.oppName}) — **{duelInvite.stake * 2} {Wager.Display(Wager.Parse(duelInvite.currency))}** on the line."); return "Both fighters are ready — the duel begins!"; } public static List> Withdraw(string inviteId, long ownerId, out string status) { List> list = new List>(); status = "that duel invite no longer exists."; DuelInvite duelInvite = _board.Find(inviteId); if (duelInvite == null) { return list; } if (!duelInvite.Involves(ownerId)) { status = "that is not your duel invite."; return list; } if (duelInvite.phase == "running") { status = "the duel is already under way — fight it out."; return list; } WagerCurrency currency = Wager.Parse(duelInvite.currency); if (duelInvite.hostId == ownerId) { if (!string.IsNullOrEmpty(duelInvite.hostPayload)) { list.Add(new KeyValuePair(duelInvite.hostId, duelInvite.hostPayload)); } WagerService.Refund(currency, duelInvite.hostId, duelInvite.hostName, duelInvite.stake, "withdrew a duel invite"); if (duelInvite.phase == "matched") { if (!string.IsNullOrEmpty(duelInvite.oppPayload)) { list.Add(new KeyValuePair(duelInvite.oppId, duelInvite.oppPayload)); } WagerService.Refund(currency, duelInvite.oppId, duelInvite.oppName, duelInvite.stake, "the poster withdrew"); } _board.invites.Remove(duelInvite); status = "Duel invite withdrawn — your stake and totem are returned."; ServerGuideBridge.AnnounceDiscord("\ud83d\udeab **" + duelInvite.hostName + "** withdrew their duel invite. All stakes returned."); } else { if (!string.IsNullOrEmpty(duelInvite.oppPayload)) { list.Add(new KeyValuePair(duelInvite.oppId, duelInvite.oppPayload)); } WagerService.Refund(currency, duelInvite.oppId, duelInvite.oppName, duelInvite.stake, "backed out of a duel"); string oppName = duelInvite.oppName; duelInvite.phase = "open"; duelInvite.oppId = 0L; duelInvite.oppName = ""; duelInvite.oppEntrantId = ""; duelInvite.oppLabel = ""; duelInvite.oppPayload = ""; duelInvite.oppLevel = 0; duelInvite.oppCaste = 0; duelInvite.hostReady = false; duelInvite.oppReady = false; status = "You have backed out — your stake and totem are returned."; ServerGuideBridge.AnnounceDiscord("↩\ufe0f **" + oppName + "** backed out of **" + duelInvite.hostName + "**'s duel invite — it is open again."); } Save(); Broadcast(); return list; } public static bool NotifyDuelResult(string winnerEntrantId, string loserEntrantId) { DuelInvite duelInvite = _board.ForEntrant(winnerEntrantId); if (duelInvite == null || duelInvite.phase != "running") { return false; } if (!duelInvite.HasEntrant(loserEntrantId)) { return false; } bool num = duelInvite.IsHostEntrant(winnerEntrantId); long ownerId = (num ? duelInvite.hostId : duelInvite.oppId); string text = (num ? duelInvite.hostName : duelInvite.oppName); string text2 = (num ? duelInvite.hostLabel : duelInvite.oppLabel); string text3 = (num ? duelInvite.oppName : duelInvite.hostName); string text4 = (num ? duelInvite.oppLabel : duelInvite.hostLabel); WagerCurrency wagerCurrency = Wager.Parse(duelInvite.currency); int num2 = duelInvite.stake * 2; WagerService.Pay(wagerCurrency, ownerId, text, num2, "ls_duel_purse", "won a staked duel"); ServerGuideBridge.AnnounceDiscord("\ud83c\udfc6 **" + text + "** wins the staked duel — **" + text2 + "** defeats **" + text4 + "** (" + text3 + ") " + $"and takes **{num2} {Wager.Display(wagerCurrency)}**."); Plugin.Log.LogInfo((object)$"[invite] '{duelInvite.id}' settled: {text} takes {num2} {duelInvite.currency}."); duelInvite.phase = "complete"; duelInvite.completedTicks = DateTime.UtcNow.Ticks; Save(); Broadcast(); return true; } public static bool UpdateEscrow(string entrantId, List payloads) { if (payloads == null || payloads.Count == 0) { return false; } DuelInvite duelInvite = _board.ForEntrant(entrantId); if (duelInvite == null) { return false; } if (duelInvite.IsHostEntrant(entrantId)) { duelInvite.hostPayload = payloads[0]; } else { duelInvite.oppPayload = payloads[0]; } Save(); return true; } public static void Tick() { if (_board?.invites == null) { return; } bool flag = false; foreach (DuelInvite item in _board.invites.ToList()) { Dictionary> byOwner; if (item == null) { _board.invites.Remove(item); flag = true; } else if (item.phase == "complete") { if (item.completedTicks <= 0 || !((DateTime.UtcNow - new DateTime(item.completedTicks, DateTimeKind.Utc)).TotalSeconds < 6.0)) { byOwner = new Dictionary>(); Add(item.hostId, item.hostPayload); Add(item.oppId, item.oppPayload); LeaderboardSync.ReturnEscrowToOwners(byOwner); _board.invites.Remove(item); flag = true; } } else { if (item.phase != "open") { continue; } float value = Plugin.WageredRegistrationMinutes.Value; if (!(value <= 0f) && item.createdTicks > 0 && !((DateTime.UtcNow - new DateTime(item.createdTicks, DateTimeKind.Utc)).TotalMinutes < (double)value)) { WagerService.Refund(Wager.Parse(item.currency), item.hostId, item.hostName, item.stake, "the duel invite expired"); if (!string.IsNullOrEmpty(item.hostPayload)) { LeaderboardSync.ReturnEscrowToOwners(new Dictionary> { { item.hostId, new List { item.hostPayload } } }); } _board.invites.Remove(item); flag = true; ServerGuideBridge.AnnounceDiscord("⌛ **" + item.hostName + "**'s duel invite expired unanswered — stake and totem returned."); } } void Add(long owner, string payload) { if (owner != 0L && !string.IsNullOrEmpty(payload)) { if (!byOwner.TryGetValue(owner, out var value2)) { value2 = new List(); byOwner[owner] = value2; } value2.Add(payload); } } } if (flag) { Save(); Broadcast(); } } private static void Broadcast() { LeaderboardSync.BroadcastInvites(); } } [Serializable] public class CompanionRecord { public string companionId; public long ownerId; public string ownerName; public string companionName; public int caste; public int wins; public int losses; public int winStreak; public int bestStreak; public int rating = 1000; public long lastMatchTicks; public int seasonId = 1; } [Serializable] public class PartyMemberSnap { public string companionId; public int caste; public int level; } [Serializable] public class PartyRecord { public long ownerId; public string ownerName; public string partyName; public int wins; public int losses; public int rating = 1000; public int bestTeamSize; public List memberSnapshot = new List(); public long lastMatchTicks; public int seasonId = 1; } [Serializable] public class LeaderboardData { public int seasonId = 1; public List companions = new List(); public List parties = new List(); } public class PartyDuelResult { public long WinnerOwnerId; public string WinnerOwnerName; public long LoserOwnerId; public string LoserOwnerName; public int MvpCaste; public List WinnerMembers = new List(); public List LoserMembers = new List(); public string WinnerPartyName; public string Encode() { return string.Join("|", WinnerOwnerId.ToString(), S(WinnerOwnerName), LoserOwnerId.ToString(), S(LoserOwnerName), MvpCaste.ToString(), Members(WinnerMembers), Members(LoserMembers), S(WinnerPartyName)); static string Members(List ms) { if (ms == null || ms.Count == 0) { return string.Empty; } List list = new List(ms.Count); foreach (PartyMemberSnap m in ms) { list.Add($"{m.companionId}:{m.caste}:{m.level}"); } return string.Join(",", list); } static string S(string s) { return (s ?? string.Empty).Replace('|', '/').Replace(',', ' ').Replace(':', ' '); } } public static PartyDuelResult Decode(string s) { if (string.IsNullOrEmpty(s)) { return null; } string[] array = s.Split(new char[1] { '|' }); if (array.Length < 7) { return null; } long.TryParse(array[0], out var result); long.TryParse(array[2], out var result2); int.TryParse(array[4], out var result3); return new PartyDuelResult { WinnerOwnerId = result, WinnerOwnerName = array[1], LoserOwnerId = result2, LoserOwnerName = array[3], MvpCaste = result3, WinnerMembers = ParseMembers(array[5]), LoserMembers = ParseMembers(array[6]), WinnerPartyName = ((array.Length > 7) ? array[7] : string.Empty) }; static List ParseMembers(string raw) { List list = new List(); if (string.IsNullOrEmpty(raw)) { return list; } string[] array2 = raw.Split(new char[1] { ',' }); for (int i = 0; i < array2.Length; i++) { string[] array3 = array2[i].Split(new char[1] { ':' }); if (array3.Length >= 3) { int.TryParse(array3[1], out var result4); int.TryParse(array3[2], out var result5); list.Add(new PartyMemberSnap { companionId = array3[0], caste = result4, level = result5 }); } } return list; } } } public class DuelResult { public string WinnerId; public long WinnerOwnerId; public string WinnerOwnerName; public string WinnerName; public int WinnerCaste; public string LoserId; public long LoserOwnerId; public string LoserOwnerName; public string LoserName; public int LoserCaste; public string Encode() { return string.Join("|", S(WinnerId), WinnerOwnerId.ToString(), S(WinnerOwnerName), S(WinnerName), WinnerCaste.ToString(), S(LoserId), LoserOwnerId.ToString(), S(LoserOwnerName), S(LoserName), LoserCaste.ToString()); static string S(string s) { return (s ?? string.Empty).Replace('|', '/'); } } public static DuelResult Decode(string s) { if (string.IsNullOrEmpty(s)) { return null; } string[] array = s.Split(new char[1] { '|' }); if (array.Length < 10) { return null; } long.TryParse(array[1], out var result); int.TryParse(array[4], out var result2); long.TryParse(array[6], out var result3); int.TryParse(array[9], out var result4); return new DuelResult { WinnerId = array[0], WinnerOwnerId = result, WinnerOwnerName = array[2], WinnerName = array[3], WinnerCaste = result2, LoserId = array[5], LoserOwnerId = result3, LoserOwnerName = array[7], LoserName = array[8], LoserCaste = result4 }; } } public static class LeaderboardStore { private static LeaderboardData _data; private static LeaderboardData _snapshot = new LeaderboardData(); private static readonly Dictionary _pairCooldown = new Dictionary(); private static string _loadedWorld; public static LeaderboardData Snapshot => _snapshot; public static string DataDir() { string text; try { text = Path.Combine(Utils.GetSaveDataPath((FileSource)1), "LostScrollsII"); } catch { text = Path.Combine(Paths.ConfigPath, "LostScrollsII"); } Directory.CreateDirectory(text); return text; } private static string Sanitize(string s) { if (string.IsNullOrEmpty(s)) { return "world"; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { s = s.Replace(oldChar, '_'); } return s; } private static string CurrentWorldName() { try { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.GetWorldName(); } } catch { } return "world"; } public static string WorldTag() { return Sanitize(CurrentWorldName()); } private static string StorePath(string world) { return Path.Combine(DataDir(), "ladder." + Sanitize(world) + ".json"); } public static void LoadForCurrentWorld() { string text = (_loadedWorld = CurrentWorldName()); string text2 = StorePath(text); try { if (File.Exists(text2)) { _data = CompetitiveJson.ReadLeaderboard(File.ReadAllText(text2)) ?? new LeaderboardData(); if (_data.companions == null) { _data.companions = new List(); } if (_data.parties == null) { _data.parties = new List(); } Plugin.Log.LogInfo((object)$"[ladder] loaded {_data.companions.Count} duel + {_data.parties.Count} party record(s) for world '{text}'."); } else { _data = new LeaderboardData(); Plugin.Log.LogInfo((object)("[ladder] no ladder file for world '" + text + "' — starting fresh.")); } } catch (Exception ex) { _data = new LeaderboardData(); Plugin.Log.LogWarning((object)("[ladder] failed to load '" + text2 + "': " + ex.Message + ". Starting fresh.")); } _snapshot = _data; } public static void Save() { if (_data == null) { return; } try { File.WriteAllText(StorePath(_loadedWorld ?? CurrentWorldName()), CompetitiveJson.Write(_data, pretty: true)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[ladder] save failed: " + ex.Message)); } } public static string SerializeSnapshot() { return CompetitiveJson.Write(_data ?? new LeaderboardData()); } public static void ApplySnapshot(string json) { try { LeaderboardData leaderboardData = CompetitiveJson.ReadLeaderboard(json) ?? new LeaderboardData(); if (leaderboardData.companions == null) { leaderboardData.companions = new List(); } if (leaderboardData.parties == null) { leaderboardData.parties = new List(); } _snapshot = leaderboardData; } catch (Exception ex) { Plugin.Log.LogWarning((object)("[ladder] failed to apply snapshot: " + ex.Message)); } } public static List Ranked(int caste = -1) { return (from r in _snapshot?.companions ?? new List() where r != null && (caste < 0 || r.caste == caste) orderby r.rating descending, r.wins descending, r.companionName ?? "" select r).ToList(); } public static int RankOf(string companionId) { if (string.IsNullOrEmpty(companionId)) { return 0; } List list = Ranked(); for (int i = 0; i < list.Count; i++) { if (list[i].companionId == companionId) { return i + 1; } } return 0; } public static CompanionRecord Find(string companionId) { return (_snapshot?.companions ?? new List()).FirstOrDefault((CompanionRecord r) => r != null && r.companionId == companionId); } public static PartyRecord FindParty(long ownerId) { return (_snapshot?.parties ?? new List()).FirstOrDefault((PartyRecord r) => r != null && r.ownerId == ownerId); } public static List RankedParties() { return (from r in _snapshot?.parties ?? new List() where r != null orderby r.rating descending, r.wins descending, r.ownerName ?? "" select r).ToList(); } public static bool ApplyDuel(DuelResult r, int kFactor, float pairCooldownSeconds, out int winnerRank, out int winnerRating, out bool crossedThreshold) { winnerRank = 0; winnerRating = 1000; crossedThreshold = false; if (_data == null) { LoadForCurrentWorld(); } if (r == null || string.IsNullOrEmpty(r.WinnerId) || string.IsNullOrEmpty(r.LoserId)) { return false; } if (r.WinnerId == r.LoserId) { return false; } if (r.WinnerOwnerId == 0L || r.LoserOwnerId == 0L || r.WinnerOwnerId == r.LoserOwnerId) { return false; } CompanionRecord orCreate = GetOrCreate(r.WinnerId, r.WinnerOwnerId, r.WinnerOwnerName, r.WinnerName, r.WinnerCaste); CompanionRecord orCreate2 = GetOrCreate(r.LoserId, r.LoserOwnerId, r.LoserOwnerName, r.LoserName, r.LoserCaste); int num = RankOfLive(orCreate.companionId); orCreate.wins++; orCreate.winStreak++; if (orCreate.winStreak > orCreate.bestStreak) { orCreate.bestStreak = orCreate.winStreak; } orCreate2.losses++; orCreate2.winStreak = 0; orCreate2.lastMatchTicks = (orCreate.lastMatchTicks = DateTime.UtcNow.Ticks); string key = PairKey(r.WinnerId, r.LoserId); if (!_pairCooldown.TryGetValue(key, out var value) || !(Time.time - value < pairCooldownSeconds)) { Rating.Apply(orCreate.rating, orCreate2.rating, kFactor, out var newWinner, out var newLoser); orCreate.rating = newWinner; orCreate2.rating = newLoser; _pairCooldown[key] = Time.time; } Save(); winnerRating = orCreate.rating; winnerRank = RankOfLive(orCreate.companionId); crossedThreshold = winnerRank >= 1 && winnerRank <= 3 && (num == 0 || winnerRank < num); return true; } public static bool ApplyPartyDuel(PartyDuelResult r, int kFactor, out int winnerRank, out int winnerRating, out bool crossedThreshold) { winnerRank = 0; winnerRating = 1000; crossedThreshold = false; if (_data == null) { LoadForCurrentWorld(); } if (r == null) { return false; } if (r.WinnerOwnerId == 0L || r.LoserOwnerId == 0L || r.WinnerOwnerId == r.LoserOwnerId) { return false; } PartyRecord orCreateParty = GetOrCreateParty(r.WinnerOwnerId, r.WinnerOwnerName); PartyRecord orCreateParty2 = GetOrCreateParty(r.LoserOwnerId, r.LoserOwnerName); if (!string.IsNullOrEmpty(r.WinnerPartyName)) { orCreateParty.partyName = r.WinnerPartyName; } int num = RankOfLiveParty(orCreateParty.ownerId); orCreateParty.wins++; orCreateParty2.losses++; orCreateParty2.lastMatchTicks = (orCreateParty.lastMatchTicks = DateTime.UtcNow.Ticks); if (r.WinnerMembers != null && r.WinnerMembers.Count > 0) { orCreateParty.memberSnapshot = r.WinnerMembers; if (r.WinnerMembers.Count > orCreateParty.bestTeamSize) { orCreateParty.bestTeamSize = r.WinnerMembers.Count; } } if (r.LoserMembers != null && r.LoserMembers.Count > 0) { orCreateParty2.memberSnapshot = r.LoserMembers; } Rating.Apply(orCreateParty.rating, orCreateParty2.rating, kFactor, out var newWinner, out var newLoser); orCreateParty.rating = newWinner; orCreateParty2.rating = newLoser; Save(); winnerRating = orCreateParty.rating; winnerRank = RankOfLiveParty(orCreateParty.ownerId); crossedThreshold = winnerRank >= 1 && winnerRank <= 3 && (num == 0 || winnerRank < num); return true; } public static void SetPartyName(long ownerId, string ownerName, string name) { if (ownerId != 0L) { if (_data == null) { LoadForCurrentWorld(); } GetOrCreateParty(ownerId, ownerName).partyName = (name ?? string.Empty).Trim(); Save(); } } public static int SeasonReset() { if (_data == null) { LoadForCurrentWorld(); } int count = _data.companions.Count; try { File.WriteAllText(StorePath(_loadedWorld ?? CurrentWorldName()).Replace(".json", $".season{_data.seasonId}.json"), CompetitiveJson.Write(_data, pretty: true)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[ladder] season archive failed: " + ex.Message)); } int num = _data.seasonId + 1; _data = new LeaderboardData { seasonId = num }; _snapshot = _data; _pairCooldown.Clear(); Save(); Plugin.Log.LogInfo((object)$"[ladder] season reset: archived {count} record(s), now season {num}."); return count; } private static CompanionRecord GetOrCreate(string id, long ownerId, string ownerName, string name, int caste) { CompanionRecord companionRecord = _data.companions.FirstOrDefault((CompanionRecord c) => c != null && c.companionId == id); if (companionRecord == null) { companionRecord = new CompanionRecord { companionId = id, rating = 1000, seasonId = _data.seasonId }; _data.companions.Add(companionRecord); } companionRecord.ownerId = ownerId; if (!string.IsNullOrEmpty(ownerName)) { companionRecord.ownerName = ownerName; } if (!string.IsNullOrEmpty(name)) { companionRecord.companionName = name; } companionRecord.caste = caste; return companionRecord; } private static int RankOfLive(string companionId) { List list = (from r in _data?.companions ?? new List() where r != null orderby r.rating descending, r.wins descending select r).ToList(); for (int num = 0; num < list.Count; num++) { if (list[num].companionId == companionId) { return num + 1; } } return 0; } private static PartyRecord GetOrCreateParty(long ownerId, string ownerName) { PartyRecord partyRecord = _data.parties.FirstOrDefault((PartyRecord p) => p != null && p.ownerId == ownerId); if (partyRecord == null) { partyRecord = new PartyRecord { ownerId = ownerId, rating = 1000, seasonId = _data.seasonId }; _data.parties.Add(partyRecord); } if (!string.IsNullOrEmpty(ownerName)) { partyRecord.ownerName = ownerName; } return partyRecord; } private static int RankOfLiveParty(long ownerId) { List list = (from r in _data?.parties ?? new List() where r != null orderby r.rating descending, r.wins descending select r).ToList(); for (int num = 0; num < list.Count; num++) { if (list[num].ownerId == ownerId) { return num + 1; } } return 0; } private static string PairKey(string a, string b) { if (string.CompareOrdinal(a, b) > 0) { return b + "|" + a; } return a + "|" + b; } } public static class LeaderboardSync { [HarmonyPatch(typeof(ZNet), "Awake")] private static class ZNetAwakePatch { private static void Postfix(ZNet __instance) { EnsureRegistered(); if (__instance.IsServer()) { LeaderboardStore.LoadForCurrentWorld(); TournamentService.LoadForCurrentWorld(); DuelInviteService.LoadForCurrentWorld(); TournamentService.SerializerSelfTest(); } } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] private static class ZNetOnDestroyPatch { private static void Postfix() { _bound = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private static class PeerInfoPatch { private static void Postfix(ZNet __instance, ZRpc rpc) { if (__instance.IsServer()) { ZNetPeer peer = __instance.GetPeer(rpc); if (peer != null) { EnsureRegistered(); SendTableToPeer(peer.m_uid); SendTournamentToPeer(peer.m_uid); SendInvitesToPeer(peer.m_uid); } } } } [HarmonyPatch(typeof(Player), "OnSpawned")] private static class PlayerSpawnedPatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { RequestTable(); RequestTournament(); RequestInvites(); RequestAdminStatus(); } } } private const string RpcReportDuel = "LSII_ReportDuel"; private const string RpcLadderPush = "LSII_LadderPush"; private const string RpcLadderRequest = "LSII_LadderReq"; private const string RpcRankEvent = "LSII_RankEvent"; private const string RpcReportParty = "LSII_ReportParty"; private const string RpcPartyRankEvent = "LSII_PartyRankEvt"; private const string RpcTourJoinAck = "LSII_TourJoinAck"; private const string RpcTourPush = "LSII_TourPush"; private const string RpcTourRequest = "LSII_TourReq"; private const string RpcTourMatch = "LSII_TourMatch"; private const string RpcTourWon = "LSII_TourWon"; private const string RpcAdminCmd = "LSII_AdminCmd"; private const string RpcAdminAck = "LSII_AdminAck"; private const string RpcSetPartyName = "LSII_SetPartyName"; private const string RpcTourJoinEsc = "LSII_TourJoinEsc"; private const string RpcTourWithdraw = "LSII_TourWithdraw"; private const string RpcTourReturn = "LSII_TourReturn"; private const string RpcTourSummon = "LSII_TourSummon"; private const string RpcTourReseal = "LSII_TourReseal"; private const string RpcAdminChk = "LSII_AdminChk"; private const string RpcAdminChkResp = "LSII_AdminChkR"; private const string RpcWagerStart = "LSII_WagerStart"; private const string RpcTourReady = "LSII_TourReady"; private const string RpcWagerPay = "LSII_WagerPay"; private const string RpcWagerPrize = "LSII_WagerPrize"; private const string RpcInvite = "LSII_Invite"; private const string RpcInvitePush = "LSII_InvitePush"; private const string RpcInviteReq = "LSII_InviteReq"; private static bool _bound; public const string ContextTournament = "tourney"; public const string ContextInvite = "invite"; public static bool LocalIsAdmin { get; private set; } private static void EnsureRegistered() { if (!_bound && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register("LSII_ReportDuel", (Action)OnReportDuel); ZRoutedRpc.instance.Register("LSII_LadderPush", (Action)OnLadderPush); ZRoutedRpc.instance.Register("LSII_LadderReq", (Action)OnLadderRequest); ZRoutedRpc.instance.Register("LSII_RankEvent", (Action)OnRankEvent); ZRoutedRpc.instance.Register("LSII_ReportParty", (Action)OnReportParty); ZRoutedRpc.instance.Register("LSII_PartyRankEvt", (Action)OnPartyRankEvent); ZRoutedRpc.instance.Register("LSII_TourJoinAck", (Action)OnTourJoinAck); ZRoutedRpc.instance.Register("LSII_TourPush", (Action)OnTourPush); ZRoutedRpc.instance.Register("LSII_TourReq", (Action)OnTourRequest); ZRoutedRpc.instance.Register("LSII_TourMatch", (Action)OnTourMatch); ZRoutedRpc.instance.Register("LSII_TourWon", (Action)OnTourWon); ZRoutedRpc.instance.Register("LSII_AdminCmd", (Action)OnAdminCmd); ZRoutedRpc.instance.Register("LSII_AdminAck", (Action)OnAdminAck); ZRoutedRpc.instance.Register("LSII_SetPartyName", (Action)OnSetPartyName); ZRoutedRpc.instance.Register("LSII_TourJoinEsc", (Action)OnTourJoinEsc); ZRoutedRpc.instance.Register("LSII_TourWithdraw", (Action)OnTourWithdraw); ZRoutedRpc.instance.Register("LSII_TourReturn", (Action)OnTourReturn); ZRoutedRpc.instance.Register("LSII_TourSummon", (Action)OnTourSummon); ZRoutedRpc.instance.Register("LSII_TourReseal", (Action)OnTourReseal); ZRoutedRpc.instance.Register("LSII_AdminChk", (Action)OnAdminChk); ZRoutedRpc.instance.Register("LSII_AdminChkR", (Action)OnAdminChkResp); ZRoutedRpc.instance.Register("LSII_WagerStart", (Action)OnWagerStart); ZRoutedRpc.instance.Register("LSII_TourReady", (Action)OnTourReady); ZRoutedRpc.instance.Register("LSII_WagerPay", (Action)OnWagerPay); ZRoutedRpc.instance.Register("LSII_WagerPrize", (Action)OnWagerPrize); ZRoutedRpc.instance.Register("LSII_Invite", (Action)OnInvite); ZRoutedRpc.instance.Register("LSII_InvitePush", (Action)OnInvitePush); ZRoutedRpc.instance.Register("LSII_InviteReq", (Action)OnInviteRequest); _bound = true; Plugin.Log.LogInfo((object)"[ladder] RPCs registered."); } } public static void ReportDuel(DuelResult result) { if (result != null && ZRoutedRpc.instance != null) { long serverPeerID = ZRoutedRpc.instance.GetServerPeerID(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "LSII_ReportDuel", new object[1] { result.Encode() }); } } private static void OnReportDuel(long sender, string encoded) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } DuelResult duelResult = DuelResult.Decode(encoded); if (duelResult == null) { return; } TournamentService.NotifyDuelResult(duelResult.WinnerId, duelResult.LoserId); DuelInviteService.NotifyDuelResult(duelResult.WinnerId, duelResult.LoserId); if (LeaderboardStore.ApplyDuel(duelResult, Plugin.RankingKFactor.Value, Plugin.RankingPairCooldown.Value, out var winnerRank, out var winnerRating, out var crossedThreshold)) { Plugin.Log.LogInfo((object)("[ladder] duel recorded: '" + duelResult.WinnerName + "' beat '" + duelResult.LoserName + "' " + $"(rank #{winnerRank}, {winnerRating}).")); BroadcastTable(); if (crossedThreshold) { string text = string.Join("|", duelResult.WinnerCaste.ToString(), winnerRank.ToString(), winnerRating.ToString(), (duelResult.WinnerName ?? string.Empty).Replace('|', '/'), (duelResult.WinnerOwnerName ?? string.Empty).Replace('|', '/')); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "LSII_RankEvent", new object[1] { text }); } } } public static void ReportPartyDuel(PartyDuelResult result) { if (result != null && ZRoutedRpc.instance != null) { long serverPeerID = ZRoutedRpc.instance.GetServerPeerID(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "LSII_ReportParty", new object[1] { result.Encode() }); } } private static void OnReportParty(long sender, string encoded) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } PartyDuelResult partyDuelResult = PartyDuelResult.Decode(encoded); if (partyDuelResult == null) { return; } TournamentService.NotifyPartyResult(partyDuelResult.WinnerOwnerId, partyDuelResult.LoserOwnerId); if (LeaderboardStore.ApplyPartyDuel(partyDuelResult, Plugin.RankingKFactor.Value, out var winnerRank, out var winnerRating, out var crossedThreshold)) { Plugin.Log.LogInfo((object)("[ladder] party duel recorded: '" + partyDuelResult.WinnerOwnerName + "' beat " + $"'{partyDuelResult.LoserOwnerName}' (party rank #{winnerRank}, {winnerRating}).")); BroadcastTable(); if (crossedThreshold) { string text = LeaderboardStore.FindParty(partyDuelResult.WinnerOwnerId)?.partyName; string text2 = string.Join("|", (partyDuelResult.WinnerOwnerName ?? string.Empty).Replace('|', '/'), winnerRank.ToString(), winnerRating.ToString(), (text ?? string.Empty).Replace('|', '/')); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "LSII_PartyRankEvt", new object[1] { text2 }); } } } private static void OnPartyRankEvent(long sender, string payload) { if ((Object)(object)Player.m_localPlayer == (Object)null || string.IsNullOrEmpty(payload)) { return; } string[] array = payload.Split(new char[1] { '|' }); if (array.Length >= 3) { int.TryParse(array[1], out var result); int.TryParse(array[2], out var result2); string partyName = ((array.Length > 3) ? array[3] : null); ServerGuideBridge.RaisePartyRankChanged(array[0], result, result2, partyName); if (result == 1) { ServerGuideBridge.RaisePartyRankFirst(array[0], result2, partyName); } } } private static string TourSubject(int caste) { if (caste >= 0) { DvergrCaste dvergrCaste = (DvergrCaste)caste; return dvergrCaste.ToString(); } return "party"; } private static void OnTourJoinAck(long sender, string payload) { if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } string[] array = payload.Split(new char[1] { '|' }); if (array.Length >= 3) { if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, array[2], 0, (Sprite)null, false); } if (array[0] == "1") { ServerGuideBridge.RaiseTournamentJoined(array[1]); } } } public static void BroadcastTournament() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown if (ZRoutedRpc.instance != null) { string text = TournamentService.SerializeSnapshot(); Plugin.Log.LogInfo((object)string.Format("[tourney] broadcast: jsonLen={0}, hasEntrants={1}.", text.Length, text.Contains("entrantId"))); ZPackage val = new ZPackage(); val.Write(text); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "LSII_TourPush", new object[1] { val }); } } private static void SendTournamentToPeer(long peer) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(TournamentService.SerializeSnapshot()); ZRoutedRpc.instance.InvokeRoutedRPC(peer, "LSII_TourPush", new object[1] { val }); } } private static void OnTourPush(long sender, ZPackage pkg) { if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()) { TournamentService.ApplySnapshot(pkg.ReadString()); } } public static void RequestTournament() { if (ZRoutedRpc.instance != null && (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer())) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "LSII_TourReq", new object[1] { string.Empty }); } } private static void OnTourRequest(long sender, string _) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { SendTournamentToPeer(sender); } } public static void SendTournamentMatch(string targetName, int round, string opponentLabel, int caste) { if (ZRoutedRpc.instance != null) { string text = string.Join("|", S(targetName), round.ToString(), S(opponentLabel), caste.ToString()); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "LSII_TourMatch", new object[1] { text }); } static string S(string s) { return (s ?? string.Empty).Replace('|', '/'); } } private static void OnTourMatch(long sender, string payload) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { string[] array = payload.Split(new char[1] { '|' }); if (array.Length >= 4 && string.Equals(array[0], localPlayer.GetPlayerName(), StringComparison.Ordinal)) { int.TryParse(array[1], out var result); int.TryParse(array[3], out var result2); ServerGuideBridge.RaiseTournamentMatch(TourSubject(result2), result, array[2]); } } } public static void SendTournamentWon(string targetName, string mode, int bracketSize, int caste) { if (ZRoutedRpc.instance != null) { string text = string.Join("|", S(targetName), S(mode), bracketSize.ToString(), caste.ToString()); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "LSII_TourWon", new object[1] { text }); } static string S(string s) { return (s ?? string.Empty).Replace('|', '/'); } } private static void OnTourWon(long sender, string payload) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { string[] array = payload.Split(new char[1] { '|' }); if (array.Length >= 4 && string.Equals(array[0], localPlayer.GetPlayerName(), StringComparison.Ordinal)) { int.TryParse(array[2], out var result); int.TryParse(array[3], out var result2); ServerGuideBridge.RaiseTournamentWon(TourSubject(result2), array[1], result); } } } public static void SendAdminCommand(string cmd) { if (!string.IsNullOrEmpty(cmd)) { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { ShowLocal(ExecuteAdmin(cmd)); } else if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "LSII_AdminCmd", new object[1] { cmd }); } } } private static void OnAdminCmd(long sender, string cmd) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } ZNetPeer peer = ZNet.instance.GetPeer(sender); object obj; if (peer == null) { obj = null; } else { ISocket socket = peer.m_socket; obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (string.IsNullOrEmpty(text) || !ZNet.instance.IsAdmin(text)) { Plugin.Log.LogWarning((object)$"[tourney] non-admin sender ({sender}, host='{text}') tried admin cmd '{cmd}' — denied."); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "LSII_AdminAck", new object[1] { "Only a server admin can do that." }); return; } string text2 = ExecuteAdmin(cmd); if (!string.IsNullOrEmpty(text2)) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "LSII_AdminAck", new object[1] { text2 }); } } private static void OnAdminAck(long sender, string msg) { ShowLocal(msg); } public static void RequestAdminStatus() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { LocalIsAdmin = true; } else if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "LSII_AdminChk", new object[1] { string.Empty }); } } private static void OnAdminChk(long sender, string _) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { ZNetPeer peer = ZNet.instance.GetPeer(sender); object obj; if (peer == null) { obj = null; } else { ISocket socket = peer.m_socket; obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; bool flag = !string.IsNullOrEmpty(text) && ZNet.instance.IsAdmin(text); Plugin.Log.LogInfo((object)$"[tourney] admin check for sender {sender} (host='{text}') => {flag}."); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "LSII_AdminChkR", new object[1] { flag ? "1" : "0" }); } } private static void OnAdminChkResp(long sender, string v) { LocalIsAdmin = v == "1"; Plugin.Log.LogInfo((object)$"[tourney] server says local player admin = {LocalIsAdmin}."); } public static void SendPartyName(string name) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { string text = string.Join("|", localPlayer.GetPlayerID().ToString(), S(localPlayer.GetPlayerName()), S(name)); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { ApplyPartyName(text); } else if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "LSII_SetPartyName", new object[1] { text }); } } static string S(string s) { return (s ?? string.Empty).Replace('|', '/'); } } private static void OnSetPartyName(long sender, string payload) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { ApplyPartyName(payload); } } private static void ApplyPartyName(string payload) { string[] array = (payload ?? string.Empty).Split(new char[1] { '|' }); if (array.Length >= 3) { long.TryParse(array[0], out var result); LeaderboardStore.SetPartyName(result, array[1], array[2]); BroadcastTable(); } } public static void SendTournamentJoinEscrow(string key, string entrantId, long ownerId, string ownerName, string label, int caste, int seedRating, List payloads, int level = 0, int clientPaidCoins = 0) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (ZRoutedRpc.instance == null) { return; } ZPackage val = new ZPackage(); val.Write(entrantId ?? string.Empty); val.Write(ownerId); val.Write(ownerName ?? string.Empty); val.Write(label ?? string.Empty); val.Write(caste); val.Write(seedRating); val.Write(payloads?.Count ?? 0); if (payloads != null) { foreach (string payload in payloads) { val.Write(payload ?? string.Empty); } } val.Write(level); val.Write(key ?? string.Empty); val.Write(clientPaidCoins); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { HandleJoinEscrow(0L, val); return; } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "LSII_TourJoinEsc", new object[1] { val }); } private static void OnTourJoinEsc(long sender, ZPackage pkg) { HandleJoinEscrow(sender, pkg); } private static void HandleJoinEscrow(long sender, ZPackage pkg) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } pkg.SetPos(0); string entrantId = pkg.ReadString(); long ownerId = pkg.ReadLong(); string ownerName = pkg.ReadString(); string label = pkg.ReadString(); int caste = pkg.ReadInt(); int seedRating = pkg.ReadInt(); int num = pkg.ReadInt(); List payloads = new List(num); for (int i = 0; i < num; i++) { payloads.Add(pkg.ReadString()); } int level = 0; string key = string.Empty; int clientPaidCoins = 0; try { level = pkg.ReadInt(); key = pkg.ReadString(); clientPaidCoins = pkg.ReadInt(); } catch { } string totemPayload = ((caste >= 0 && payloads.Count > 0) ? payloads[0] : null); List teamPayloads = ((caste < 0) ? payloads : null); TournamentService.Join(key, entrantId, ownerId, ownerName, label, caste, seedRating, totemPayload, teamPayloads, level, clientPaidCoins, delegate(bool ok, string status) { Plugin.Log.LogInfo((object)($"[tourney] join from sender={sender} into slot '{key}': owner={ownerId}/'{ownerName}', " + $"label='{label}', level={level}, paidCoins={clientPaidCoins}, payloadLen={totemPayload?.Length ?? 0} => \"{status}\" " + $"(entrants now {TournamentService.Get(key)?.entrants?.Count ?? (-1)}).")); string text = string.Join("|", ok ? "1" : "0", TourSubject(caste), status ?? string.Empty); if (sender == 0L) { ShowLocal(status); } else { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "LSII_TourJoinAck", new object[1] { text }); } if (ok && sender == 0L) { ServerGuideBridge.RaiseTournamentJoined(TourSubject(caste)); } if (!ok) { if (payloads.Count > 0) { ReturnEscrowToOwners(new Dictionary> { { ownerId, payloads } }); } if (clientPaidCoins > 0) { SendWagerPayout(ownerId, clientPaidCoins, "entry refunded"); } } }); } public static void SendTournamentWithdraw(string key) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && ZRoutedRpc.instance != null) { string text = localPlayer.GetPlayerID() + "|" + (key ?? string.Empty); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { HandleWithdraw(0L, text); return; } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "LSII_TourWithdraw", new object[1] { text }); } } private static void OnTourWithdraw(long sender, string payload) { HandleWithdraw(sender, payload); } private static void HandleWithdraw(long sender, string payload) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { string[] array = (payload ?? string.Empty).Split(new char[1] { '|' }); long.TryParse(array[0], out var result); string status; List list = TournamentService.Withdraw((array.Length > 1) ? array[1] : string.Empty, result, out status); if (sender == 0L) { ShowLocal(status); } else { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "LSII_AdminAck", new object[1] { status }); } if (list.Count > 0) { ReturnEscrowToOwners(new Dictionary> { { result, list } }); } } } public static void SendStartWagered(string currency, int clientPaidCoins) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && ZRoutedRpc.instance != null) { string text = string.Join("|", currency ?? string.Empty, localPlayer.GetPlayerID().ToString(), (localPlayer.GetPlayerName() ?? string.Empty).Replace('|', '/'), clientPaidCoins.ToString()); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { HandleStartWagered(0L, text); return; } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "LSII_WagerStart", new object[1] { text }); } } private static void OnWagerStart(long sender, string payload) { HandleStartWagered(sender, payload); } private static void HandleStartWagered(long sender, string payload) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } string[] array = (payload ?? string.Empty).Split(new char[1] { '|' }); if (array.Length < 4) { return; } WagerCurrency wagerCurrency = Wager.Parse(array[0]); long.TryParse(array[1], out var hostId); string hostName = array[2]; int.TryParse(array[3], out var paidCoins); if (wagerCurrency == WagerCurrency.Coins && paidCoins < Wager.TournamentFee(wagerCurrency)) { Reply(sender, "You do not have the Coins to open a tournament."); if (paidCoins > 0) { SendWagerPayout(hostId, paidCoins, "tournament not opened"); } return; } TournamentService.StartWagered(wagerCurrency, hostId, hostName, delegate(bool ok, string msg) { Reply(sender, msg); if (!ok && paidCoins > 0) { SendWagerPayout(hostId, paidCoins, "tournament not opened"); } }); } public static void SendTournamentReady(string key) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && ZRoutedRpc.instance != null) { string text = localPlayer.GetPlayerID() + "|" + (key ?? string.Empty); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { HandleReady(0L, text); return; } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "LSII_TourReady", new object[1] { text }); } } private static void OnTourReady(long sender, string payload) { HandleReady(sender, payload); } private static void HandleReady(long sender, string payload) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { string[] array = (payload ?? string.Empty).Split(new char[1] { '|' }); long.TryParse(array[0], out var result); string key = ((array.Length > 1) ? array[1] : string.Empty); Reply(sender, TournamentService.SetReady(key, result)); } } public static void SendWagerPayout(long ownerId, int amount, string reason) { if (ZRoutedRpc.instance != null && amount > 0) { string text = string.Join("|", ownerId.ToString(), amount.ToString(), (reason ?? string.Empty).Replace('|', '/')); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "LSII_WagerPay", new object[1] { text }); } } private static void OnWagerPay(long sender, string payload) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } string[] array = (payload ?? string.Empty).Split(new char[1] { '|' }); if (array.Length < 2) { return; } long.TryParse(array[0], out var result); if (result == localPlayer.GetPlayerID()) { int.TryParse(array[1], out var result2); if (result2 > 0) { Wager.GiveCoins(localPlayer, result2); string text = ((array.Length > 2) ? array[2] : string.Empty); ShowLocal(string.IsNullOrEmpty(text) ? $"+{result2} Coins" : $"+{result2} Coins — {text}"); } } } public static void SendTournamentPrize(string targetName, string currency, int amount) { if (ZRoutedRpc.instance != null) { string text = string.Join("|", (targetName ?? string.Empty).Replace('|', '/'), currency ?? string.Empty, amount.ToString(), "tournament"); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "LSII_WagerPrize", new object[1] { text }); } } private static void OnWagerPrize(long sender, string payload) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { string[] array = (payload ?? string.Empty).Split(new char[1] { '|' }); if (array.Length >= 4 && string.Equals(array[0], localPlayer.GetPlayerName(), StringComparison.Ordinal)) { int.TryParse(array[2], out var result); ServerGuideBridge.RaiseTournamentPrize(array[1], result); } } } private static void Reply(long sender, string message) { if (!string.IsNullOrEmpty(message)) { if (sender == 0L) { ShowLocal(message); } else if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "LSII_AdminAck", new object[1] { message }); } } } public static void ReturnEscrowToOwners(Dictionary> byOwner) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown if (ZRoutedRpc.instance == null || byOwner == null) { return; } foreach (KeyValuePair> item in byOwner) { if (item.Value == null || item.Value.Count == 0) { continue; } ZPackage val = new ZPackage(); val.Write(item.Key); val.Write(item.Value.Count); foreach (string item2 in item.Value) { val.Write(item2 ?? string.Empty); } ZRoutedRpc.instance.InvokeRoutedRPC(0L, "LSII_TourReturn", new object[1] { val }); } } private static void OnTourReturn(long sender, ZPackage pkg) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } pkg.SetPos(0); if (pkg.ReadLong() != localPlayer.GetPlayerID()) { return; } int num = pkg.ReadInt(); int num2 = 0; for (int i = 0; i < num; i++) { ItemData val = TotemConversionService.BuildTotemFromPayload(pkg.ReadString()); if (val != null && (((Humanoid)localPlayer).GetInventory().AddItem(val) || !((Object)(object)ItemDrop.DropItem(val, 1, ((Component)localPlayer).transform.position + Vector3.up, ((Component)localPlayer).transform.rotation) == (Object)null))) { num2++; } } if (num2 > 0 && (Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, (num2 == 1) ? "Your companion totem is returned." : $"{num2} companion totems returned.", 0, (Sprite)null, false); } } public static void SummonForMatch(long ownerId, string entrantId, string mode, string opponentEntrantId, string opponentLabel, int round, List payloads, string context = "tourney") { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (ZRoutedRpc.instance == null) { return; } ZPackage val = new ZPackage(); val.Write(ownerId); val.Write(entrantId ?? string.Empty); val.Write(mode ?? "1v1"); val.Write(opponentEntrantId ?? string.Empty); val.Write(opponentLabel ?? string.Empty); val.Write(round); val.Write(payloads?.Count ?? 0); if (payloads != null) { foreach (string payload in payloads) { val.Write(payload ?? string.Empty); } } val.Write(context ?? "tourney"); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "LSII_TourSummon", new object[1] { val }); } private static void OnTourSummon(long sender, ZPackage pkg) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } pkg.SetPos(0); if (pkg.ReadLong() == localPlayer.GetPlayerID()) { string entrantId = pkg.ReadString(); string mode = pkg.ReadString(); string opponentEntrantId = pkg.ReadString(); string opponentLabel = pkg.ReadString(); int round = pkg.ReadInt(); int num = pkg.ReadInt(); List list = new List(num); for (int i = 0; i < num; i++) { list.Add(pkg.ReadString()); } string context = "tourney"; try { context = pkg.ReadString(); } catch { } TournamentClient.SummonForMatch(localPlayer, entrantId, mode, opponentEntrantId, opponentLabel, round, list, context); } } public static void SendReseal(string entrantId, List payloads) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (ZRoutedRpc.instance == null) { return; } ZPackage val = new ZPackage(); val.Write(entrantId ?? string.Empty); val.Write(payloads?.Count ?? 0); if (payloads != null) { foreach (string payload in payloads) { val.Write(payload ?? string.Empty); } } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { HandleReseal(val); return; } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "LSII_TourReseal", new object[1] { val }); } private static void OnTourReseal(long sender, ZPackage pkg) { HandleReseal(pkg); } private static void HandleReseal(ZPackage pkg) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { pkg.SetPos(0); string entrantId = pkg.ReadString(); int num = pkg.ReadInt(); List list = new List(num); for (int i = 0; i < num; i++) { list.Add(pkg.ReadString()); } if (TournamentService.FindEntrant(entrantId) != null) { TournamentService.UpdateEscrow(entrantId, list); } else { DuelInviteService.UpdateEscrow(entrantId, list); } } } public static void SendInviteAction(string action, string inviteId, string currency, string entrantId, string label, int level, int caste, string payload, int clientPaidCoins) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(action ?? string.Empty); val.Write(inviteId ?? string.Empty); val.Write(currency ?? string.Empty); val.Write(localPlayer.GetPlayerID()); val.Write(localPlayer.GetPlayerName() ?? string.Empty); val.Write(entrantId ?? string.Empty); val.Write(label ?? string.Empty); val.Write(level); val.Write(caste); val.Write(payload ?? string.Empty); val.Write(clientPaidCoins); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { HandleInvite(0L, val); return; } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "LSII_Invite", new object[1] { val }); } } private static void OnInvite(long sender, ZPackage pkg) { HandleInvite(sender, pkg); } private static void HandleInvite(long sender, ZPackage pkg) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } pkg.SetPos(0); string text = pkg.ReadString(); string inviteId = pkg.ReadString(); string s = pkg.ReadString(); long ownerId = pkg.ReadLong(); string text2 = pkg.ReadString(); string entrantId = pkg.ReadString(); string label = pkg.ReadString(); int level = pkg.ReadInt(); int caste = pkg.ReadInt(); string payload = pkg.ReadString(); int paidCoins = pkg.ReadInt(); switch (text) { case "post": DuelInviteService.Post(Wager.Parse(s), ownerId, text2, entrantId, label, level, caste, payload, paidCoins, delegate(bool ok, string msg) { if (ok) { Reply(sender, msg); } else { Reject(msg); } }); break; case "accept": DuelInviteService.Accept(inviteId, ownerId, text2, entrantId, label, level, caste, payload, paidCoins, delegate(bool ok, string msg) { if (ok) { Reply(sender, msg); } else { Reject(msg); } }); break; case "ready": Reply(sender, DuelInviteService.SetReady(inviteId, ownerId)); break; case "withdraw": { string status; List> list = DuelInviteService.Withdraw(inviteId, ownerId, out status); Reply(sender, status); Dictionary> dictionary = new Dictionary>(); foreach (KeyValuePair item in list) { if (!dictionary.TryGetValue(item.Key, out var value)) { value = new List(); dictionary[item.Key] = value; } value.Add(item.Value); } if (dictionary.Count > 0) { ReturnEscrowToOwners(dictionary); } break; } default: Reply(sender, "Unknown invite action '" + text + "'."); break; } void Reject(string message) { Reply(sender, message); if (!string.IsNullOrEmpty(payload)) { ReturnEscrowToOwners(new Dictionary> { { ownerId, new List { payload } } }); } if (paidCoins > 0) { SendWagerPayout(ownerId, paidCoins, "stake refunded"); } } } public static void BroadcastInvites() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(DuelInviteService.SerializeSnapshot()); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "LSII_InvitePush", new object[1] { val }); } } private static void SendInvitesToPeer(long peer) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(DuelInviteService.SerializeSnapshot()); ZRoutedRpc.instance.InvokeRoutedRPC(peer, "LSII_InvitePush", new object[1] { val }); } } private static void OnInvitePush(long sender, ZPackage pkg) { if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()) { DuelInviteService.ApplySnapshot(pkg.ReadString()); } } public static void RequestInvites() { if (ZRoutedRpc.instance != null && (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer())) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "LSII_InviteReq", new object[1] { string.Empty }); } } private static void OnInviteRequest(long sender, string _) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { SendInvitesToPeer(sender); } } private static void ShowLocal(string msg) { if (!string.IsNullOrEmpty(msg) && (Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, msg, 0, (Sprite)null, false); } } private static string ExecuteAdmin(string cmd) { string[] array = (cmd ?? string.Empty).Split(new char[1] { '|' }); switch ((array.Length != 0) ? array[0] : string.Empty) { case "start": { string mode = ((array.Length > 1) ? array[1] : "1v1"); int result = 0; if (array.Length > 2) { int.TryParse(array[2], out result); } return TournamentService.Start(eliminationType: (array.Length > 3) ? array[3] : "single", mode: mode, size: result); } case "begin": return TournamentService.Begin(Slot(array, 1)); case "cancel": return TournamentService.Cancel(Slot(array, 1)); case "forfeit": if (array.Length <= 1) { return "Usage: forfeit ."; } return TournamentService.Forfeit(Slot(array, 2), array[1]); case "release": { if (array.Length < 2) { return "Usage: release ."; } long ownerId; string status; List list = TournamentService.ReleaseEntrant(Slot(array, 2), array[1], out ownerId, out status); if (list.Count > 0) { ReturnEscrowToOwners(new Dictionary> { { ownerId, list } }); } return status; } case "activate": return TournamentService.ActivateCurrentRound(Slot(array, 1)); case "season": { int num2 = LeaderboardStore.SeasonReset(); BroadcastTable(); return $"Duel season reset — archived {num2} record(s)."; } case "bountyseason": { int num = BountyLeaderboardStore.SeasonReset(); BountySync.BroadcastLadder(); return $"Bounty season reset — archived {num} hunter record(s)."; } default: return "Unknown admin action '" + ((array.Length != 0) ? array[0] : "") + "'."; } } private static string Slot(string[] parts, int index) { if (parts == null || parts.Length <= index) { return string.Empty; } return parts[index]; } public static void BroadcastTable() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(LeaderboardStore.SerializeSnapshot()); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "LSII_LadderPush", new object[1] { val }); } } public static void SendTableToPeer(long peer) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(LeaderboardStore.SerializeSnapshot()); ZRoutedRpc.instance.InvokeRoutedRPC(peer, "LSII_LadderPush", new object[1] { val }); } } private static void OnLadderPush(long sender, ZPackage pkg) { if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()) { LeaderboardStore.ApplySnapshot(pkg.ReadString()); } } public static void RequestTable() { if (ZRoutedRpc.instance != null && (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer())) { long serverPeerID = ZRoutedRpc.instance.GetServerPeerID(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "LSII_LadderReq", new object[1] { string.Empty }); } } private static void OnLadderRequest(long sender, string _) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { SendTableToPeer(sender); } } private static void OnRankEvent(long sender, string payload) { if ((Object)(object)Player.m_localPlayer == (Object)null || string.IsNullOrEmpty(payload)) { return; } string[] array = payload.Split(new char[1] { '|' }); if (array.Length >= 5) { int.TryParse(array[0], out var result); int.TryParse(array[1], out var result2); int.TryParse(array[2], out var result3); ServerGuideBridge.RaiseRankChanged(result, result2, result3, array[3], array[4]); if (result2 == 1) { ServerGuideBridge.RaiseRankFirst(result, result3, array[3], array[4]); } } } } public static class Rating { public const int StartRating = 1000; public const int Floor = 100; public static double Expected(int a, int b) { return 1.0 / (1.0 + Math.Pow(10.0, (double)(b - a) / 400.0)); } public static void Apply(int winner, int loser, int kFactor, out int newWinner, out int newLoser) { double num = Expected(winner, loser); double num2 = 1.0 - num; newWinner = winner + (int)Math.Round((double)kFactor * (1.0 - num)); newLoser = loser + (int)Math.Round((double)kFactor * (0.0 - num2)); if (newLoser < 100) { newLoser = 100; } if (newWinner < 100) { newWinner = 100; } } } [Serializable] public class TournamentEntrant { public string entrantId; public long ownerId; public string ownerName; public string label; public int caste; public int seedRating; public bool eliminated; public int level; public int losses; public int wins; public string totemPayload; public List teamPayloads = new List(); public int feePaid; } [Serializable] public class TournamentMatch { public int round; public string aId; public string bId; public string aLabel; public string bLabel; public int aLevel; public int bLevel; public string winnerId; public string bracket = "W"; public bool aReady; public bool bReady; public bool activated; } [Serializable] public class TournamentState { public string key = ""; public string currency = ""; public int entryFee; public int prize; public long hostId; public string hostName = ""; public long openedTicks; public int hostCredit; public long completedTicks; public bool active; public string mode = "1v1"; public string phase = "idle"; public int size; public int bracketSize; public int currentRound; public List entrants = new List(); public List matches = new List(); public string championId = ""; public string championLabel = ""; public int seasonId = 1; public string eliminationType = "single"; public int totalRounds; public TournamentEntrant Find(string id) { return entrants.Find((TournamentEntrant e) => e != null && e.entrantId == id); } public TournamentMatch ActiveMatchBetween(string x, string y) { foreach (TournamentMatch match in matches) { if (match.round == currentRound && string.IsNullOrEmpty(match.winnerId) && ((match.aId == x && match.bId == y) || (match.aId == y && match.bId == x))) { return match; } } return null; } } [Serializable] public class TournamentBook { public List tournaments = new List(); public TournamentState Get(string key) { return tournaments.Find((TournamentState t) => t != null && (t.key ?? "") == (key ?? "")); } public TournamentState ForEntrant(string entrantId) { if (string.IsNullOrEmpty(entrantId)) { return null; } foreach (TournamentState tournament in tournaments) { if (tournament != null && tournament.Find(entrantId) != null) { return tournament; } } return null; } } [Serializable] public class ChampionRecord { public string mode; public string championLabel; public string ownerName; public int bracketSize; public int seasonId; public long dateTicks; } [Serializable] public class ChampionsData { public List champions = new List(); } public static class TournamentService { private static TournamentBook _book = new TournamentBook(); private static TournamentBook _snapshot = new TournamentBook(); public const string FreeSlot = ""; public const string CoinSlot = "coins"; public const string ValcoinSlot = "valcoin"; public static float MatchTimeoutSeconds = 0f; private const double CompletionGraceSeconds = 6.0; public static TournamentBook Snapshot => _snapshot; public static IEnumerable All => (_snapshot?.tournaments ?? new List()).Where((TournamentState t) => t?.active ?? false); public static bool AnyActive => All.Any(); public static TournamentState Legacy => Get(""); public static TournamentState Get(string key) { return _snapshot?.Get(key ?? ""); } public static TournamentState FindByEntrant(string entrantId) { return _snapshot?.ForEntrant(entrantId); } private static string StatePath() { return Path.Combine(LeaderboardStore.DataDir(), "tournament." + LeaderboardStore.WorldTag() + ".json"); } private static string ChampionsPath() { return Path.Combine(LeaderboardStore.DataDir(), "champions." + LeaderboardStore.WorldTag() + ".json"); } public static void LoadForCurrentWorld() { try { string path = StatePath(); if (File.Exists(path)) { _book = CompetitiveJson.ReadTournamentBook(File.ReadAllText(path)) ?? new TournamentBook(); if (_book.tournaments == null) { _book.tournaments = new List(); } foreach (TournamentState tournament in _book.tournaments) { if (tournament != null) { if (tournament.entrants == null) { tournament.entrants = new List(); } if (tournament.matches == null) { tournament.matches = new List(); } } } Plugin.Log.LogInfo((object)($"[tourney] resumed {_book.tournaments.Count} tournament slot(s): " + string.Join(", ", _book.tournaments.Select((TournamentState t) => $"'{t.key}':{t.phase}/{t.entrants.Count}")))); } else { _book = new TournamentBook(); } } catch (Exception ex) { _book = new TournamentBook(); Plugin.Log.LogWarning((object)("[tourney] load failed: " + ex.Message)); } _snapshot = _book; } private static void Save() { try { File.WriteAllText(StatePath(), CompetitiveJson.Write(_book, pretty: true)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[tourney] save failed: " + ex.Message)); } } public static string SerializeSnapshot() { return CompetitiveJson.Write(_book ?? new TournamentBook()); } public static void SerializerSelfTest() { try { TournamentState tournamentState = new TournamentState { key = "valcoin", currency = "valcoin", active = true, mode = "1v1", phase = "registration", size = 4, entryFee = 10 }; tournamentState.entrants.Add(new TournamentEntrant { entrantId = "probe-id", ownerId = 42L, ownerName = "probe", label = "Probe", level = 3, feePaid = 10 }); tournamentState.matches.Add(new TournamentMatch { round = 1, aId = "probe-id", aLabel = "Probe" }); string text = CompetitiveJson.Write(new TournamentBook { tournaments = { tournamentState } }); TournamentBook tournamentBook = CompetitiveJson.ReadTournamentBook(text); TournamentState tournamentState2 = tournamentBook?.Get("valcoin"); bool flag = text.Contains("entrantId"); Plugin.Log.LogInfo((object)($"[tourney] serializer self-test: jsonHasLists={flag}, " + $"slots={tournamentBook?.tournaments?.Count ?? (-1)}, entrants={tournamentState2?.entrants?.Count ?? (-1)}, " + $"matches={tournamentState2?.matches?.Count ?? (-1)}, fee={tournamentState2?.entryFee ?? (-1)}, jsonLen={text.Length}.")); if (!flag || tournamentState2 == null || tournamentState2.entrants.Count != 1 || tournamentState2.matches.Count != 1 || tournamentState2.entryFee != 10) { Plugin.Log.LogWarning((object)("[tourney] serializer self-test FAILED — data is being dropped. json=" + text)); } } catch (Exception arg) { Plugin.Log.LogWarning((object)$"[tourney] serializer self-test threw: {arg}"); } } public static void ApplySnapshot(string json) { try { TournamentBook tournamentBook = CompetitiveJson.ReadTournamentBook(json) ?? new TournamentBook(); if (tournamentBook.tournaments == null) { tournamentBook.tournaments = new List(); } foreach (TournamentState tournament in tournamentBook.tournaments) { if (tournament != null) { if (tournament.entrants == null) { tournament.entrants = new List(); } if (tournament.matches == null) { tournament.matches = new List(); } } } _snapshot = tournamentBook; } catch (Exception ex) { Plugin.Log.LogWarning((object)("[tourney] snapshot apply failed: " + ex.Message)); } } private static TournamentState Live(string key) { return _book?.Get(key ?? ""); } private static TournamentState LiveForEntrant(string entrantId) { return _book?.ForEntrant(entrantId); } private static void Put(TournamentState s) { if (s != null) { TournamentState tournamentState = _book.Get(s.key); if (tournamentState != null) { _book.tournaments.Remove(tournamentState); } _book.tournaments.Add(s); } } private static void Drop(string key) { TournamentState tournamentState = _book.Get(key ?? ""); if (tournamentState != null) { _book.tournaments.Remove(tournamentState); } } public static string Start(string mode, int size, string eliminationType = "single") { mode = (mode ?? "1v1").ToLowerInvariant(); if (mode != "1v1" && mode != "party") { return "mode must be 1v1 or party."; } eliminationType = (eliminationType ?? "single").ToLowerInvariant(); if (eliminationType != "single" && eliminationType != "double" && eliminationType != "round_robin") { return "elimination type must be single, double, or round_robin."; } TournamentState tournamentState = Live(""); if (tournamentState != null && tournamentState.active) { return "a tournament is already running — cancel it first."; } int num = Mathf.Max(1, Plugin.MaxEntrants.Value); int num2 = ((size <= 0) ? num : Mathf.Min(size, num)); Put(new TournamentState { key = "", active = true, mode = mode, phase = "registration", size = num2, eliminationType = eliminationType, seasonId = 1, openedTicks = DateTime.UtcNow.Ticks }); Save(); Broadcast(); Announce($"\ud83c\udfdf\ufe0f A **{mode}** tournament ({DisplayType(eliminationType)}) is open for registration — {num2} slots. " + "Seal a companion into a Communion Totem and enter from the Tournament panel (F7)."); return $"Tournament ({mode}, {eliminationType}) open for registration — {num2} slots."; } public static void StartWagered(WagerCurrency currency, long hostId, string hostName, Action done) { string key = Wager.Key(currency); if (currency != WagerCurrency.Coins && currency != WagerCurrency.Valcoin) { done?.Invoke(arg1: false, "Unknown currency."); return; } if (!WagerService.IsAvailable(currency, out var reason)) { done?.Invoke(arg1: false, reason); return; } TournamentState tournamentState = Live(key); if (tournamentState != null && tournamentState.active) { done?.Invoke(arg1: false, "A " + Wager.Display(currency) + " tournament is already running — only one at a time."); return; } int fee = Wager.TournamentFee(currency); int size = Mathf.Max(2, Plugin.WageredBracketSize.Value); WagerService.Charge(currency, hostName, fee, "ls_tourney_entry", "opened a " + Wager.Display(currency) + " tournament", CoinsAlreadyPaid(currency, fee), delegate(bool ok, string msg) { if (!ok) { done?.Invoke(arg1: false, msg ?? "The stake could not be taken."); } else { TournamentState tournamentState2 = new TournamentState { key = key, currency = key, entryFee = fee, prize = Wager.TournamentPrize(currency), hostId = hostId, hostName = (hostName ?? string.Empty), hostCredit = fee, active = true, mode = "1v1", phase = "registration", size = size, eliminationType = "single", seasonId = 1, openedTicks = DateTime.UtcNow.Ticks }; Put(tournamentState2); Save(); Broadcast(); Announce($"\ud83c\udfdf\ufe0f **{hostName}** has opened a **{size}-player {Wager.Display(currency)} tournament**! " + $"Entry: **{fee} {Wager.Display(currency)}** · Champion's purse: **{tournamentState2.prize} {Wager.Display(currency)}**. " + "Seal a companion into a Communion Totem and enter from the Tournament panel (F7) — the bracket starts the moment it fills."); Plugin.Log.LogInfo((object)$"[tourney] {hostName} opened the {key} tournament ({fee} fee, {size} slots)."); done?.Invoke(arg1: true, $"Your {Wager.Display(currency)} tournament is open — {size} slots. " + "Lock your own totem to claim your place; your entry is already paid."); } }); } private static int CoinsAlreadyPaid(WagerCurrency c, int fee) { if (c != WagerCurrency.Coins) { return 0; } return fee; } public static void Join(string key, string entrantId, long ownerId, string ownerName, string label, int caste, int seedRating, string totemPayload, List teamPayloads, int level, int clientPaidCoins, Action done) { TournamentState s = Live(key); if (s == null || !s.active || s.phase != "registration") { done?.Invoke(arg1: false, "no tournament is accepting entries."); return; } if (string.IsNullOrEmpty(entrantId)) { done?.Invoke(arg1: false, "could not identify your entry."); return; } if (s.Find(entrantId) != null) { done?.Invoke(arg1: false, "already registered."); return; } if (s.size > 0 && s.entrants.Count >= s.size) { done?.Invoke(arg1: false, "the tournament is full."); return; } if (s.entrants.Any((TournamentEntrant e) => e.ownerId == ownerId)) { done?.Invoke(arg1: false, "you already have an entry in this tournament."); return; } int value = Plugin.RequiredEntrantLevel.Value; if (value > 0 && caste >= 0 && level != value) { done?.Invoke(arg1: false, $"only Level {value} companions may enter this tournament."); return; } if (s.entryFee <= 0) { Register(0); return; } if (ownerId == s.hostId && s.hostCredit > 0) { int hostCredit = s.hostCredit; s.hostCredit = 0; Register(hostCredit, fromHostCredit: true); return; } WagerService.Charge(Wager.Parse(s.currency), ownerName, s.entryFee, "ls_tourney_entry", "tournament entry", clientPaidCoins, delegate(bool ok, string msg) { if (!ok) { done?.Invoke(arg1: false, msg ?? "The entry fee could not be taken."); } else { Register(s.entryFee); } }); void Register(int feePaid, bool fromHostCredit = false) { if (Live(key) != s || !s.active || s.phase != "registration" || (s.size > 0 && s.entrants.Count >= s.size) || s.entrants.Any((TournamentEntrant e) => e.ownerId == ownerId)) { if (fromHostCredit) { s.hostCredit = feePaid; done?.Invoke(arg1: false, "Your entry could not be placed — your paid slot is still held."); } else { if (feePaid > 0) { WagerService.RollBackOwnCharge(Wager.Parse(s.currency), ownerId, ownerName, feePaid, "the tournament closed before your entry landed"); } done?.Invoke(arg1: false, "The tournament closed before your entry landed — your stake is returned."); } } else { s.entrants.Add(new TournamentEntrant { entrantId = entrantId, ownerId = ownerId, ownerName = ownerName, label = label, caste = caste, seedRating = seedRating, level = level, totemPayload = (totemPayload ?? string.Empty), teamPayloads = (teamPayloads ?? new List()), feePaid = feePaid }); Save(); Broadcast(); if (s.entryFee > 0) { Announce("⚔\ufe0f **" + ownerName + "** enters the " + Wager.Display(Wager.Parse(s.currency)) + " tournament with **" + label + "**" + ((level > 0) ? $" (Lv{level})" : "") + $" — {s.entrants.Count}/{s.size} entered."); } done?.Invoke(arg1: true, $"Registered '{label}' ({s.entrants.Count}" + ((s.size > 0) ? $"/{s.size}" : "") + ")."); if (s.entryFee > 0 && s.size > 0 && s.entrants.Count >= s.size) { Begin(key); } } } } public static List PayloadsOf(TournamentEntrant e) { List list = new List(); if (e == null) { return list; } if (!string.IsNullOrEmpty(e.totemPayload)) { list.Add(e.totemPayload); } if (e.teamPayloads != null) { list.AddRange(e.teamPayloads.Where((string p) => !string.IsNullOrEmpty(p))); } return list; } public static List Withdraw(string key, long ownerId, out string status) { status = "no tournament is accepting entries."; TournamentState tournamentState = Live(key); if (tournamentState == null || !tournamentState.active || tournamentState.phase != "registration") { return new List(); } TournamentEntrant tournamentEntrant = tournamentState.entrants.FirstOrDefault((TournamentEntrant e) => e.ownerId == ownerId); if (tournamentEntrant == null) { status = "you have no entry to withdraw."; return new List(); } List result = PayloadsOf(tournamentEntrant); tournamentState.entrants.Remove(tournamentEntrant); RefundEntrant(tournamentState, tournamentEntrant, "withdrew from the tournament"); Save(); Broadcast(); status = ((tournamentEntrant.feePaid > 0) ? $"Withdrew '{tournamentEntrant.label}'. Your companion totem and {tournamentEntrant.feePaid} {Wager.Display(Wager.Parse(tournamentState.currency))} are returned." : ("Withdrew '" + tournamentEntrant.label + "'. Your companion totem is returned.")); return result; } public static List ReleaseEntrant(string key, string idOrName, out long ownerId, out string status) { ownerId = 0L; status = "no tournament is running."; TournamentState tournamentState = Live(key); if (tournamentState == null || !tournamentState.active) { return new List(); } TournamentEntrant tournamentEntrant = tournamentState.entrants.FirstOrDefault((TournamentEntrant e) => e.entrantId == idOrName || string.Equals(e.ownerName, idOrName, StringComparison.OrdinalIgnoreCase) || string.Equals(e.label, idOrName, StringComparison.OrdinalIgnoreCase)); if (tournamentEntrant == null) { status = "no entrant '" + idOrName + "'."; return new List(); } ownerId = tournamentEntrant.ownerId; List result = PayloadsOf(tournamentEntrant); if (tournamentState.phase == "registration") { tournamentState.entrants.Remove(tournamentEntrant); RefundEntrant(tournamentState, tournamentEntrant, "released from the tournament"); } else { tournamentEntrant.eliminated = true; foreach (TournamentMatch match in tournamentState.matches) { if (match.round == tournamentState.currentRound && string.IsNullOrEmpty(match.winnerId) && (match.aId == tournamentEntrant.entrantId || match.bId == tournamentEntrant.entrantId)) { string text = ((match.aId == tournamentEntrant.entrantId) ? match.bId : match.aId); if (!string.IsNullOrEmpty(text)) { ResolveMatch(tournamentState, match, text); } break; } } tournamentEntrant.totemPayload = string.Empty; tournamentEntrant.teamPayloads = new List(); } Save(); Broadcast(); status = "Released '" + tournamentEntrant.label + "' — totem returned to " + tournamentEntrant.ownerName + "."; return result; } public static Dictionary> DrainAllEscrow(string key) { Dictionary> dictionary = new Dictionary>(); TournamentState tournamentState = Live(key); if (tournamentState == null) { return dictionary; } foreach (TournamentEntrant entrant in tournamentState.entrants) { List list = PayloadsOf(entrant); if (list.Count != 0) { if (!dictionary.TryGetValue(entrant.ownerId, out var value)) { value = new List(); dictionary[entrant.ownerId] = value; } value.AddRange(list); entrant.totemPayload = string.Empty; entrant.teamPayloads = new List(); } } return dictionary; } public static string Begin(string key) { TournamentState tournamentState = Live(key); if (tournamentState == null || !tournamentState.active || tournamentState.phase != "registration") { return "no tournament is in registration."; } if (tournamentState.entrants.Count < 2) { return "need at least 2 entrants."; } tournamentState.phase = "running"; tournamentState.bracketSize = tournamentState.entrants.Count; tournamentState.currentRound = 1; tournamentState.matches.Clear(); List list = tournamentState.entrants.Select((TournamentEntrant e) => e.entrantId).ToList(); string eliminationType = tournamentState.eliminationType; if (!(eliminationType == "round_robin")) { if (eliminationType == "double") { BuildDoubleRound(tournamentState, 1); } else { BuildRound(tournamentState, list, 1); } } else { BuildRoundRobinSchedule(tournamentState, list); } Save(); Broadcast(); Announce($"\ud83d\udd14 The {Label(tournamentState)} bracket is set — **{tournamentState.entrants.Count} entrants**, round 1 begins!" + ((tournamentState.entryFee > 0) ? " Each pair fights wherever they choose: both owners open the Tournament panel (F7) and press **Ready to Fight** once they have met up." : "")); AnnounceRoundMatches(tournamentState, 1); return $"Tournament begun ({tournamentState.eliminationType}) — {tournamentState.entrants.Count} entrants, round 1."; } public static string Cancel(string key) { TournamentState tournamentState = Live(key); if (tournamentState == null || !tournamentState.active) { return "no tournament is running."; } LeaderboardSync.ReturnEscrowToOwners(DrainAllEscrow(key)); RefundEveryone(tournamentState, "the tournament was cancelled"); string text = Label(tournamentState); Drop(key); Save(); Broadcast(); Announce("\ud83d\udeab The " + text + " has been cancelled — every companion totem and stake has been returned."); return "Tournament cancelled — companion totems and stakes returned."; } public static string Forfeit(string key, string playerName) { TournamentState tournamentState = Live(key); if (tournamentState == null || !tournamentState.active || tournamentState.phase != "running") { return "no tournament round is running."; } TournamentEntrant tournamentEntrant = tournamentState.entrants.FirstOrDefault((TournamentEntrant e) => string.Equals(e.ownerName, playerName, StringComparison.OrdinalIgnoreCase)); if (tournamentEntrant == null) { return "'" + playerName + "' is not an entrant."; } foreach (TournamentMatch match in tournamentState.matches) { if (match.round == tournamentState.currentRound && string.IsNullOrEmpty(match.winnerId) && (match.aId == tournamentEntrant.entrantId || match.bId == tournamentEntrant.entrantId)) { string text = ((match.aId == tournamentEntrant.entrantId) ? match.bId : match.aId); if (string.IsNullOrEmpty(text)) { return "opponent not set — cannot forfeit a bye."; } Announce("\ud83c\udff3\ufe0f **" + tournamentEntrant.ownerName + "** forfeits in the " + Label(tournamentState) + "."); ResolveMatch(tournamentState, match, text); return playerName + " forfeits — opponent advances."; } } return playerName + " has no undecided match this round."; } public static string ActivateCurrentRound(string key) { TournamentState tournamentState = Live(key); if (tournamentState == null || !tournamentState.active || tournamentState.phase != "running") { return "no tournament round is running."; } int num = 0; foreach (TournamentMatch item in tournamentState.matches.ToList()) { if (item.round == tournamentState.currentRound && string.IsNullOrEmpty(item.winnerId) && !string.IsNullOrEmpty(item.bId) && ActivateMatch(tournamentState, item)) { num++; } } Save(); Broadcast(); if (num != 0) { return $"Activated {num} match(es) — companions summoned to duel."; } return "No pairings to activate this round."; } public static string SetReady(string key, long ownerId) { TournamentState tournamentState = Live(key); if (tournamentState == null || !tournamentState.active || tournamentState.phase != "running") { return "no tournament round is running."; } TournamentEntrant tournamentEntrant = tournamentState.entrants.FirstOrDefault((TournamentEntrant e) => e.ownerId == ownerId); if (tournamentEntrant == null) { return "you are not in this tournament."; } foreach (TournamentMatch match in tournamentState.matches) { if (match.round != tournamentState.currentRound || !string.IsNullOrEmpty(match.winnerId) || match.activated) { continue; } bool flag = match.aId == tournamentEntrant.entrantId; bool flag2 = match.bId == tournamentEntrant.entrantId; if (flag || flag2) { if (string.IsNullOrEmpty(match.bId)) { return "you have a bye this round."; } if (flag) { match.aReady = true; } else { match.bReady = true; } Save(); Broadcast(); if (match.aReady && match.bReady) { ActivateMatch(tournamentState, match); Save(); Broadcast(); return "Both fighters are ready — the duel begins!"; } return "Ready. Waiting for " + (tournamentState.Find(flag ? match.bId : match.aId)?.ownerName ?? "your opponent") + " to be ready too."; } } return "you have no match awaiting a start this round."; } private static bool ActivateMatch(TournamentState s, TournamentMatch m) { if (m == null || m.activated || !string.IsNullOrEmpty(m.winnerId) || string.IsNullOrEmpty(m.bId)) { return false; } TournamentEntrant tournamentEntrant = s.Find(m.aId); TournamentEntrant tournamentEntrant2 = s.Find(m.bId); if (tournamentEntrant == null || tournamentEntrant2 == null) { return false; } m.activated = true; LeaderboardSync.SummonForMatch(tournamentEntrant.ownerId, tournamentEntrant.entrantId, s.mode, tournamentEntrant2.entrantId, tournamentEntrant2.label, s.currentRound, PayloadsOf(tournamentEntrant)); LeaderboardSync.SummonForMatch(tournamentEntrant2.ownerId, tournamentEntrant2.entrantId, s.mode, tournamentEntrant.entrantId, tournamentEntrant.label, s.currentRound, PayloadsOf(tournamentEntrant2)); Announce($"\ud83e\udd4a **Round {s.currentRound} — the duel is on:** {tournamentEntrant.label} ({tournamentEntrant.ownerName}) vs {tournamentEntrant2.label} ({tournamentEntrant2.ownerName}) " + "in the " + Label(s) + "."); return true; } public static void UpdateEscrow(string entrantId, List payloads) { TournamentState tournamentState = LiveForEntrant(entrantId); TournamentEntrant tournamentEntrant = tournamentState?.Find(entrantId); if (tournamentEntrant != null && payloads != null && payloads.Count != 0) { if (tournamentState.mode == "party") { tournamentEntrant.teamPayloads = payloads; } else { tournamentEntrant.totemPayload = payloads[0]; } Save(); Plugin.Log.LogInfo((object)$"[tourney] escrow updated for '{tournamentEntrant.label}' ({payloads.Count} totem(s))."); } } public static TournamentEntrant FindEntrant(string entrantId) { return LiveForEntrant(entrantId)?.Find(entrantId); } public static void NotifyDuelResult(string winnerCompanionId, string loserCompanionId) { foreach (TournamentState item in _book.tournaments.ToList()) { if (item != null && item.active && !(item.phase != "running") && !(item.mode != "1v1")) { TournamentMatch tournamentMatch = item.ActiveMatchBetween(winnerCompanionId, loserCompanionId); if (tournamentMatch != null) { ResolveMatch(item, tournamentMatch, winnerCompanionId); break; } } } } public static void NotifyPartyResult(long winnerOwnerId, long loserOwnerId) { foreach (TournamentState item in _book.tournaments.ToList()) { if (item != null && item.active && !(item.phase != "running") && !(item.mode != "party")) { TournamentMatch tournamentMatch = item.ActiveMatchBetween(winnerOwnerId.ToString(), loserOwnerId.ToString()); if (tournamentMatch != null) { ResolveMatch(item, tournamentMatch, winnerOwnerId.ToString()); break; } } } } public static void Tick() { if (_book?.tournaments == null) { return; } foreach (TournamentState item in _book.tournaments.ToList()) { if (item != null && item.active && !(item.phase != "complete") && item.completedTicks > 0 && !((DateTime.UtcNow - new DateTime(item.completedTicks, DateTimeKind.Utc)).TotalSeconds < 6.0)) { LeaderboardSync.ReturnEscrowToOwners(DrainAllEscrow(item.key)); item.completedTicks = 0L; if (item.entryFee > 0) { Drop(item.key); } Save(); Broadcast(); } } float value = Plugin.WageredRegistrationMinutes.Value; if (value <= 0f) { return; } foreach (TournamentState item2 in _book.tournaments.ToList()) { if (item2 != null && item2.active && !(item2.phase != "registration") && item2.entryFee > 0 && item2.openedTicks > 0 && !((DateTime.UtcNow - new DateTime(item2.openedTicks, DateTimeKind.Utc)).TotalMinutes < (double)value)) { Plugin.Log.LogInfo((object)$"[tourney] '{item2.key}' expired with {item2.entrants.Count}/{item2.size} entrants — refunding."); Announce($"⌛ The {Label(item2)} never filled ({item2.entrants.Count}/{item2.size}) and has been cancelled. " + "Every stake is refunded and every companion totem returned."); LeaderboardSync.ReturnEscrowToOwners(DrainAllEscrow(item2.key)); RefundEveryone(item2, "the tournament never filled"); Drop(item2.key); Save(); Broadcast(); } } } private static void RefundEntrant(TournamentState s, TournamentEntrant e, string why) { if (s != null && e != null && e.feePaid > 0) { WagerService.Refund(Wager.Parse(s.currency), e.ownerId, e.ownerName, e.feePaid, why); e.feePaid = 0; } } private static void RefundEveryone(TournamentState s, string why) { if (s == null) { return; } foreach (TournamentEntrant entrant in s.entrants) { RefundEntrant(s, entrant, why); } if (s.hostCredit > 0) { WagerService.Refund(Wager.Parse(s.currency), s.hostId, s.hostName, s.hostCredit, why); s.hostCredit = 0; } } private static void ResolveMatch(TournamentState s, TournamentMatch m, string winnerId) { m.winnerId = winnerId; string id = ((m.aId == winnerId) ? m.bId : m.aId); TournamentEntrant tournamentEntrant = s.Find(id); TournamentEntrant tournamentEntrant2 = s.Find(winnerId); if (tournamentEntrant2 != null) { tournamentEntrant2.wins++; } if (!string.IsNullOrEmpty(m.bId) && tournamentEntrant2 != null) { Announce("\ud83c\udfc5 **" + tournamentEntrant2.label + "** (" + tournamentEntrant2.ownerName + ") defeats **" + (tournamentEntrant?.label ?? "their rival") + "**" + ((tournamentEntrant != null) ? (" (" + tournamentEntrant.ownerName + ")") : "") + $" in round {m.round} of the {Label(s)}."); } string eliminationType = s.eliminationType; if (!(eliminationType == "round_robin")) { if (eliminationType == "double") { ResolveDoubleMatch(s, m, tournamentEntrant2, tournamentEntrant); } else { ResolveSingleMatch(s, m, tournamentEntrant2, tournamentEntrant); } } else { ResolveRoundRobinMatch(s, m, tournamentEntrant2, tournamentEntrant); } } private static void ResolveSingleMatch(TournamentState s, TournamentMatch m, TournamentEntrant winnerEnt, TournamentEntrant loser) { if (loser != null) { loser.eliminated = true; loser.losses++; } Plugin.Log.LogInfo((object)$"[tourney] round {m.round}: '{winnerEnt?.label}' beat '{loser?.label}'."); Save(); Broadcast(); if (s.matches.Where((TournamentMatch x) => x.round == s.currentRound).All((TournamentMatch x) => !string.IsNullOrEmpty(x.winnerId))) { List list = (from x in s.matches where x.round == s.currentRound select x.winnerId into id where !string.IsNullOrEmpty(id) select id).ToList(); if (list.Count <= 1) { Complete(s, (list.Count == 1) ? list[0] : winnerEnt?.entrantId); return; } s.currentRound++; BuildRound(s, list, s.currentRound); Save(); Broadcast(); AnnounceRoundMatches(s, s.currentRound); } else { AnnounceStillToFight(s); } } private static void ResolveRoundRobinMatch(TournamentState s, TournamentMatch m, TournamentEntrant winnerEnt, TournamentEntrant loser) { Plugin.Log.LogInfo((object)$"[tourney] round {m.round} (round robin): '{winnerEnt?.label}' beat '{loser?.label}'."); Save(); Broadcast(); if (!s.matches.Where((TournamentMatch x) => x.round == s.currentRound).All((TournamentMatch x) => !string.IsNullOrEmpty(x.winnerId))) { AnnounceStillToFight(s); } else if (s.currentRound >= s.totalRounds) { TournamentEntrant tournamentEntrant = (from e in s.entrants orderby e.wins descending, e.seedRating descending, e.label select e).FirstOrDefault(); if (tournamentEntrant != null) { Complete(s, tournamentEntrant.entrantId); } } else { s.currentRound++; Save(); Broadcast(); AnnounceRoundMatches(s, s.currentRound); } } private static void ResolveDoubleMatch(TournamentState s, TournamentMatch m, TournamentEntrant winnerEnt, TournamentEntrant loser) { if (loser != null) { loser.losses++; if (loser.losses >= 2) { loser.eliminated = true; } } Plugin.Log.LogInfo((object)$"[tourney] round {m.round} ({m.bracket}): '{winnerEnt?.label}' beat '{loser?.label}' (losses={loser?.losses ?? 0})."); Save(); Broadcast(); if (!s.matches.Where((TournamentMatch x) => x.round == s.currentRound).All((TournamentMatch x) => !string.IsNullOrEmpty(x.winnerId))) { AnnounceStillToFight(s); return; } if (m.bracket == "GF") { Complete(s, winnerEnt?.entrantId); return; } s.currentRound++; BuildDoubleRound(s, s.currentRound); Save(); Broadcast(); AnnounceRoundMatches(s, s.currentRound); } private static void Complete(TournamentState s, string championId) { s.phase = "complete"; s.championId = championId; TournamentEntrant tournamentEntrant = s.Find(championId); s.championLabel = tournamentEntrant?.label ?? championId; Save(); Broadcast(); Plugin.Log.LogInfo((object)("[tourney] '" + s.key + "' complete — champion: " + s.championLabel + ".")); ArchiveChampion(s, tournamentEntrant); if (tournamentEntrant != null) { LeaderboardSync.SendTournamentWon(tournamentEntrant.ownerName, s.mode, s.bracketSize, tournamentEntrant.caste); Announce("\ud83c\udfc6 **" + tournamentEntrant.ownerName + "** is the champion of the " + Label(s) + " with **" + tournamentEntrant.label + "**" + ((tournamentEntrant.level > 0) ? $" (Lv{tournamentEntrant.level})" : "") + "!"); PayChampion(s, tournamentEntrant); } AnnounceSummary(s, tournamentEntrant); s.completedTicks = DateTime.UtcNow.Ticks; Save(); Broadcast(); } private static void PayChampion(TournamentState s, TournamentEntrant champ) { if (s.prize > 0 && champ != null) { switch (Wager.Parse(s.currency)) { case WagerCurrency.Coins: WagerService.Pay(WagerCurrency.Coins, champ.ownerId, champ.ownerName, s.prize, "ls_tourney_entry", "tournament champion"); break; case WagerCurrency.Valcoin: LeaderboardSync.SendTournamentPrize(champ.ownerName, s.currency, s.prize); break; } } } private static void BuildRound(TournamentState s, List survivorIds, int round) { List ents = (from id in survivorIds select s.Find(id) into e where e != null orderby e.seedRating descending, e.label select e).ToList(); PairPool(s, ents, round, "W"); } private static void PairPool(TournamentState s, List ents, int round, string bracket) { int num = 0; if (ents.Count % 2 == 1) { TournamentEntrant tournamentEntrant = ents[0]; s.matches.Add(new TournamentMatch { round = round, bracket = bracket, aId = tournamentEntrant.entrantId, aLabel = tournamentEntrant.label, aLevel = tournamentEntrant.level, bId = "", bLabel = "(bye)", winnerId = tournamentEntrant.entrantId }); num = 1; } int num2 = num; int num3 = ents.Count - 1; while (num2 < num3) { TournamentEntrant tournamentEntrant2 = ents[num2]; TournamentEntrant tournamentEntrant3 = ents[num3]; s.matches.Add(new TournamentMatch { round = round, bracket = bracket, aId = tournamentEntrant2.entrantId, aLabel = tournamentEntrant2.label, aLevel = tournamentEntrant2.level, bId = tournamentEntrant3.entrantId, bLabel = tournamentEntrant3.label, bLevel = tournamentEntrant3.level, winnerId = "" }); num2++; num3--; } } private static void BuildRoundRobinSchedule(TournamentState s, List ids) { List list = new List(ids); if (list.Count % 2 == 1) { list.Add(""); } int count = list.Count; int num = (s.totalRounds = Mathf.Max(1, count - 1)); for (int i = 1; i <= num; i++) { for (int j = 0; j < count / 2; j++) { string text = list[j]; string text2 = list[count - 1 - j]; if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text2)) { TournamentEntrant tournamentEntrant = s.Find(text); TournamentEntrant tournamentEntrant2 = s.Find(text2); if (tournamentEntrant != null && tournamentEntrant2 != null) { s.matches.Add(new TournamentMatch { round = i, bracket = "W", aId = text, aLabel = tournamentEntrant.label, aLevel = tournamentEntrant.level, bId = text2, bLabel = tournamentEntrant2.label, bLevel = tournamentEntrant2.level, winnerId = "" }); } } } string value = list[count - 1]; for (int num2 = count - 1; num2 > 1; num2--) { list[num2] = list[num2 - 1]; } list[1] = value; } } private static void BuildDoubleRound(TournamentState s, int round) { List list = (from e in s.entrants where e.losses == 0 && !e.eliminated orderby e.seedRating descending, e.label select e).ToList(); List list2 = (from e in s.entrants where e.losses == 1 && !e.eliminated orderby e.seedRating descending, e.label select e).ToList(); if (list.Count == 1 && list2.Count == 1) { TournamentEntrant tournamentEntrant = list[0]; TournamentEntrant tournamentEntrant2 = list2[0]; s.matches.Add(new TournamentMatch { round = round, bracket = "GF", aId = tournamentEntrant.entrantId, aLabel = tournamentEntrant.label, aLevel = tournamentEntrant.level, bId = tournamentEntrant2.entrantId, bLabel = tournamentEntrant2.label, bLevel = tournamentEntrant2.level, winnerId = "" }); } else if (list.Count + list2.Count <= 1) { TournamentEntrant tournamentEntrant3 = list.Concat(list2).FirstOrDefault(); if (tournamentEntrant3 != null) { Complete(s, tournamentEntrant3.entrantId); } } else { if (list.Count >= 2) { PairPool(s, list, round, "W"); } if (list2.Count >= 2) { PairPool(s, list2, round, "L"); } } } private static void AnnounceRoundMatches(TournamentState s, int round) { List list = new List(); foreach (TournamentMatch match in s.matches) { if (match.round != round) { continue; } TournamentEntrant tournamentEntrant = s.Find(match.aId); if (string.IsNullOrEmpty(match.bId)) { if (tournamentEntrant != null) { list.Add("• " + tournamentEntrant.label + " (" + tournamentEntrant.ownerName + ") — bye"); } continue; } TournamentEntrant tournamentEntrant2 = s.Find(match.bId); if (tournamentEntrant != null) { LeaderboardSync.SendTournamentMatch(tournamentEntrant.ownerName, round, match.bLabel, tournamentEntrant.caste); } if (tournamentEntrant2 != null) { LeaderboardSync.SendTournamentMatch(tournamentEntrant2.ownerName, round, match.aLabel, tournamentEntrant2.caste); } if (tournamentEntrant != null && tournamentEntrant2 != null) { list.Add("• " + tournamentEntrant.label + " (" + tournamentEntrant.ownerName + ") vs " + tournamentEntrant2.label + " (" + tournamentEntrant2.ownerName + ")"); } } if (list.Count != 0) { Announce($"\ud83d\udccb **{Label(s)} — round {round} draw:**\n" + string.Join("\n", list)); } } private static void AnnounceStillToFight(TournamentState s) { List list = s.matches.Where((TournamentMatch m) => m.round == s.currentRound && string.IsNullOrEmpty(m.winnerId) && !string.IsNullOrEmpty(m.bId)).ToList(); if (list.Count != 0) { IEnumerable values = list.Select(delegate(TournamentMatch m) { TournamentEntrant tournamentEntrant = s.Find(m.aId); TournamentEntrant tournamentEntrant2 = s.Find(m.bId); return "• " + (tournamentEntrant?.label ?? m.aLabel) + " (" + tournamentEntrant?.ownerName + ") vs " + (tournamentEntrant2?.label ?? m.bLabel) + " (" + tournamentEntrant2?.ownerName + ")"; }); Announce($"➡\ufe0f **Still to fight in round {s.currentRound}** of the {Label(s)}:\n" + string.Join("\n", values)); } } private static void AnnounceSummary(TournamentState s, TournamentEntrant champ) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("\ud83d\udcdc **" + Label(s) + " — final summary**\n"); stringBuilder.Append($"Format: {s.mode} · {DisplayType(s.eliminationType)} · {s.bracketSize} entrants"); if (s.entryFee > 0) { stringBuilder.Append($" · entry {s.entryFee} {Wager.Display(Wager.Parse(s.currency))} · purse {s.prize} {Wager.Display(Wager.Parse(s.currency))}"); } stringBuilder.Append('\n'); if (champ != null) { stringBuilder.Append("Champion: **" + champ.label + "** (" + champ.ownerName + ")\n"); } stringBuilder.Append("\n__Standings__\n"); foreach (TournamentEntrant item in from e in s.entrants orderby e.wins descending, e.losses, e.label select e) { stringBuilder.Append(string.Format("• {0} ({1}) — {2}W {3}L{4}\n", item.label, item.ownerName, item.wins, item.losses, item.eliminated ? " · out" : "")); } stringBuilder.Append("\n__Results__\n"); foreach (TournamentMatch item2 in s.matches.OrderBy((TournamentMatch m) => m.round)) { if (string.IsNullOrEmpty(item2.bId)) { stringBuilder.Append($"• R{item2.round}: {item2.aLabel} — bye\n"); continue; } TournamentEntrant tournamentEntrant = s.Find(item2.winnerId); stringBuilder.Append($"• R{item2.round}: {item2.aLabel} vs {item2.bLabel}" + ((tournamentEntrant != null) ? (" → **" + tournamentEntrant.label + "**") : " → undecided") + "\n"); } Announce(stringBuilder.ToString()); } private static string Label(TournamentState s) { if (s == null) { return "tournament"; } WagerCurrency wagerCurrency = Wager.Parse(s.currency); if (wagerCurrency == WagerCurrency.None) { return "server tournament"; } return Wager.Display(wagerCurrency) + " tournament"; } private static string DisplayType(string t) { if (!(t == "double")) { if (t == "round_robin") { return "round robin"; } return "single elimination"; } return "double elimination"; } private static void Announce(string message) { ServerGuideBridge.AnnounceDiscord(message); } private static void ArchiveChampion(TournamentState s, TournamentEntrant champ) { if (champ == null) { return; } try { string path = ChampionsPath(); ChampionsData championsData = (File.Exists(path) ? CompetitiveJson.ReadChampions(File.ReadAllText(path)) : new ChampionsData()); if (championsData.champions == null) { championsData.champions = new List(); } championsData.champions.Add(new ChampionRecord { mode = s.mode, championLabel = champ.label, ownerName = champ.ownerName, bracketSize = s.bracketSize, seasonId = s.seasonId, dateTicks = DateTime.UtcNow.Ticks }); File.WriteAllText(path, CompetitiveJson.Write(championsData, pretty: true)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[tourney] champion archive failed: " + ex.Message)); } } public static List LoadChampions() { try { string path = ChampionsPath(); if (!File.Exists(path)) { return new List(); } return CompetitiveJson.ReadChampions(File.ReadAllText(path))?.champions ?? new List(); } catch { return new List(); } } private static void Broadcast() { LeaderboardSync.BroadcastTournament(); } } } namespace LostScrollsII.Integration { public static class ServerGuideBridge { private const string ServerGuideGuid = "com.valheimserverguide"; public static bool IsLoaded => Chainloader.PluginInfos.ContainsKey("com.valheimserverguide"); public static void AnnounceDiscord(string message) { if (!IsLoaded || string.IsNullOrEmpty(message) || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } try { AnnounceDiscordInternal(message); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (discord announce) failed: {arg}"); } } private static void AnnounceDiscordInternal(string message) { DiscordAnnouncer.AnnounceRaw(message); } public static void RaiseTournamentPrize(string currency, int amount) { if (!IsLoaded) { return; } try { RaiseSimple("dvergr_tournament_prize", currency ?? string.Empty, new Dictionary { { "mode", currency ?? string.Empty }, { "bracketSize", amount } }); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_tournament_prize) failed: {arg}"); } } public static void RaiseRecruited(DvergrCaste caste) { if (!IsLoaded) { return; } try { RaiseRecruitedInternal(caste); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_recruited) failed: {arg}"); } } public static void RaiseLevelUp(DvergrCaste caste, int level) { if (!IsLoaded) { return; } try { RaiseLevelUpInternal(caste, level); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_level_up) failed: {arg}"); } } public static void RaiseDuelWon(DvergrCaste winner, DvergrCaste loser, string winnerName = null, string loserName = null) { if (!IsLoaded) { return; } try { RaiseDuelWonInternal(winner, loser, winnerName, loserName); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_duel_won) failed: {arg}"); } } public static void RaiseRankChanged(int caste, int rank, int rating, string companionName, string ownerName) { if (!IsLoaded) { return; } try { RaiseRankChangedInternal(caste, rank, rating, companionName, ownerName); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_rank_changed) failed: {arg}"); } } public static void RaiseRankFirst(int caste, int rating, string companionName, string ownerName) { if (!IsLoaded) { return; } try { DvergrCaste dvergrCaste = (DvergrCaste)caste; RaiseSimple("dvergr_rank_first", dvergrCaste.ToString(), new Dictionary { { "rank", 1 }, { "rating", rating }, { "companionName", companionName ?? string.Empty }, { "ownerName", ownerName ?? string.Empty } }); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_rank_first) failed: {arg}"); } } public static void RaisePartyRankFirst(string ownerName, int rating, string partyName) { if (!IsLoaded) { return; } try { RaiseSimple("dvergr_party_rank_first", ownerName ?? string.Empty, new Dictionary { { "rank", 1 }, { "rating", rating }, { "ownerName", ownerName ?? string.Empty }, { "partyName", string.IsNullOrEmpty(partyName) ? (ownerName ?? string.Empty) : partyName } }); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_party_rank_first) failed: {arg}"); } } public static void RaisePartyDuelWon(string winnerOwnerName, int winSize, string loserOwnerName, int mvpCaste, string partyName = null) { if (!IsLoaded) { return; } try { RaisePartyDuelWonInternal(winnerOwnerName, winSize, loserOwnerName, mvpCaste, partyName); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_party_duel_won) failed: {arg}"); } } public static void RaisePartyRankChanged(string ownerName, int rank, int rating, string partyName = null) { if (!IsLoaded) { return; } try { RaisePartyRankChangedInternal(ownerName, rank, rating, partyName); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_party_rank_changed) failed: {arg}"); } } public static void RaiseBountyResolved(int tier, string tierName, string biome, string method) { if (!IsLoaded) { return; } try { RaiseSimple("dvergr_bounty_resolved", tier.ToString(), new Dictionary { { "tier", tier }, { "tierName", tierName ?? string.Empty }, { "bountyBiome", biome ?? string.Empty }, { "method", method ?? string.Empty } }); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_bounty_resolved) failed: {arg}"); } } public static void RaiseBountyValcoin(int tier, string tierName) { if (!IsLoaded) { return; } try { RaiseSimple("dvergr_bounty_valcoin", tier.ToString(), new Dictionary { { "tier", tier }, { "tierName", tierName ?? string.Empty } }); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_bounty_valcoin) failed: {arg}"); } } public static void RaiseTournamentJoined(string subject) { if (!IsLoaded) { return; } try { RaiseSimple("dvergr_tournament_joined", subject, null); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_tournament_joined) failed: {arg}"); } } public static void RaiseTournamentMatch(string subject, int round, string opponent) { if (!IsLoaded) { return; } try { RaiseSimple("dvergr_tournament_match", subject, new Dictionary { { "round", round }, { "opponent", opponent ?? string.Empty } }); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_tournament_match) failed: {arg}"); } } public static void RaiseTournamentWon(string subject, string mode, int bracketSize) { if (!IsLoaded) { return; } try { RaiseSimple("dvergr_tournament_won", subject, new Dictionary { { "mode", mode ?? string.Empty }, { "bracketSize", bracketSize } }); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"ServerGuide integration (dvergr_tournament_won) failed: {arg}"); } } private static void RaiseRecruitedInternal(DvergrCaste caste) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown GuidanceDispatcher.Raise(new TriggerEvent { Type = "dvergr_recruited", Subject = caste.ToString() }); } private static void RaiseLevelUpInternal(DvergrCaste caste, int level) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown GuidanceDispatcher.Raise(new TriggerEvent { Type = "dvergr_level_up", Subject = $"{caste}:{level}" }); } private static void RaiseDuelWonInternal(DvergrCaste winner, DvergrCaste loser, string winnerName, string loserName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown GuidanceDispatcher.Raise(new TriggerEvent { Type = "dvergr_duel_won", Subject = winner.ToString(), Extra = new Dictionary { { "loserCaste", loser.ToString() }, { "companionName", winnerName ?? string.Empty }, { "opponent", loserName ?? string.Empty } } }); } private static void RaiseRankChangedInternal(int caste, int rank, int rating, string companionName, string ownerName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown TriggerEvent val = new TriggerEvent { Type = "dvergr_rank_changed" }; DvergrCaste dvergrCaste = (DvergrCaste)caste; val.Subject = dvergrCaste.ToString(); val.Extra = new Dictionary { { "rank", rank }, { "rating", rating }, { "companionName", companionName ?? string.Empty }, { "ownerName", ownerName ?? string.Empty } }; GuidanceDispatcher.Raise(val); } private static void RaisePartyDuelWonInternal(string winnerOwnerName, int winSize, string loserOwnerName, int mvpCaste, string partyName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown TriggerEvent val = new TriggerEvent { Type = "dvergr_party_duel_won", Subject = (winnerOwnerName ?? string.Empty) }; Dictionary obj = new Dictionary { { "winSize", winSize }, { "ownerName", winnerOwnerName ?? string.Empty }, { "opponentOwner", loserOwnerName ?? string.Empty } }; DvergrCaste dvergrCaste = (DvergrCaste)mvpCaste; obj.Add("mvpCaste", dvergrCaste.ToString()); obj.Add("partyName", string.IsNullOrEmpty(partyName) ? (winnerOwnerName ?? string.Empty) : partyName); val.Extra = obj; GuidanceDispatcher.Raise(val); } private static void RaiseSimple(string type, string subject, Dictionary extra) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown GuidanceDispatcher.Raise(new TriggerEvent { Type = type, Subject = (subject ?? string.Empty), Extra = extra }); } private static void RaisePartyRankChangedInternal(string ownerName, int rank, int rating, string partyName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown GuidanceDispatcher.Raise(new TriggerEvent { Type = "dvergr_party_rank_changed", Subject = (ownerName ?? string.Empty), Extra = new Dictionary { { "rank", rank }, { "rating", rating }, { "ownerName", ownerName ?? string.Empty }, { "partyName", string.IsNullOrEmpty(partyName) ? (ownerName ?? string.Empty) : partyName } } }); } } } namespace LostScrollsII.Economy { public static class ValcoinBridge { private const string DonationsGuid = "com.taeguk.valheimdonations"; private const string WalletTypeName = "ValcoinWallet"; public const string SkuTournamentEntry = "ls_tourney_entry"; public const string SkuDuelStake = "ls_duel_stake"; public const string SkuRefund = "ls_wager_refund"; public const string SkuDuelPurse = "ls_duel_purse"; private static bool _probed; private static Type _wallet; private static MethodInfo _charge; private static MethodInfo _credit; private static PropertyInfo _ready; private static PropertyInfo _reason; public static bool Available { get { Probe(); if (_wallet == null || _charge == null || _credit == null) { return false; } try { return _ready == null || (bool)_ready.GetValue(null); } catch { return false; } } } public static string UnavailableReason { get { Probe(); if (_wallet == null || _charge == null || _credit == null) { return "Valcoin wagers need the Valheim Donations mod on this server."; } try { if (_ready != null && !(bool)_ready.GetValue(null)) { return (_reason?.GetValue(null) as string) ?? "Valcoin wagers are not available on this server right now."; } } catch { return "Valcoin wagers are not available on this server right now."; } return null; } } public static void Charge(string playerName, string sku, int coins, string reason, Action done) { if (!Available) { done?.Invoke(arg1: false, UnavailableReason); } else { Invoke(_charge, playerName, sku, coins, reason, done); } } public static void Credit(string playerName, string sku, int coins, string reason, Action done) { if (!Available) { done?.Invoke(arg1: false, UnavailableReason); } else { Invoke(_credit, playerName, sku, coins, reason, done); } } private static void Invoke(MethodInfo m, string playerName, string sku, int coins, string reason, Action done) { try { m.Invoke(null, new object[5] { playerName, sku, coins, reason, done }); } catch (Exception ex) { Exception ex2 = ex.InnerException ?? ex; Plugin.Log.LogWarning((object)$"[wager] Valcoin call failed ({sku}, {coins} for {playerName}): {ex2.Message}"); done?.Invoke(arg1: false, "The Valcoin ledger could not be reached."); } } private static void Probe() { if (_probed) { return; } _probed = true; try { if (!Chainloader.PluginInfos.ContainsKey("com.taeguk.valheimdonations")) { foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { PluginInfo value = pluginInfo.Value; object obj; if (value == null) { obj = null; } else { BepInPlugin metadata = value.Metadata; obj = ((metadata != null) ? metadata.Name : null); } if (obj == null) { obj = string.Empty; } if (((string)obj).IndexOf("donation", StringComparison.OrdinalIgnoreCase) >= 0) { Bind(((object)pluginInfo.Value.Instance)?.GetType().Assembly); break; } } } else { PluginInfo obj2 = Chainloader.PluginInfos["com.taeguk.valheimdonations"]; Bind((obj2 == null) ? null : ((object)obj2.Instance)?.GetType().Assembly); } Plugin.Log.LogInfo((object)((_wallet != null) ? "[wager] Valcoin wallet API found — Valcoin wagers are available." : "[wager] No Valcoin wallet API found — Valcoin wagers will be refused (Coins still work).")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[wager] Valcoin bridge probe failed: " + ex.Message)); } } private static void Bind(Assembly asm) { if (asm == null) { return; } _wallet = asm.GetType("ValcoinWallet", throwOnError: false); if (!(_wallet == null)) { Type[] types = new Type[5] { typeof(string), typeof(string), typeof(int), typeof(string), typeof(Action) }; _charge = _wallet.GetMethod("Charge", BindingFlags.Static | BindingFlags.Public, null, types, null); _credit = _wallet.GetMethod("Credit", BindingFlags.Static | BindingFlags.Public, null, types, null); _ready = _wallet.GetProperty("Ready", BindingFlags.Static | BindingFlags.Public); _reason = _wallet.GetProperty("UnavailableReason", BindingFlags.Static | BindingFlags.Public); if (_charge == null || _credit == null) { Plugin.Log.LogWarning((object)"[wager] Found ValcoinWallet but not its Charge/Credit(string,string,int,string,Action) methods — the donations plugin is probably older than this build. Valcoin wagers stay off."); _wallet = null; } } } } public enum WagerCurrency { None, Coins, Valcoin } public static class Wager { public const string CoinPrefab = "Coins"; private static string _coinSharedName; public static string Key(WagerCurrency c) { if (c != WagerCurrency.Valcoin) { return "coins"; } return "valcoin"; } public static WagerCurrency Parse(string s) { if (string.IsNullOrEmpty(s)) { return WagerCurrency.None; } switch (s.ToLowerInvariant()) { case "valcoin": case "valcoins": return WagerCurrency.Valcoin; case "coins": case "coin": return WagerCurrency.Coins; default: return WagerCurrency.None; } } public static string Display(WagerCurrency c) { if (c != WagerCurrency.Valcoin) { return "Coins"; } return "Valcoins"; } public static int TournamentFee(WagerCurrency c) { if (c != WagerCurrency.Valcoin) { return Mathf.Max(0, Plugin.TournamentCoinFee.Value); } return Mathf.Max(0, Plugin.TournamentValcoinFee.Value); } public static int TournamentPrize(WagerCurrency c) { if (c != WagerCurrency.Valcoin) { return Mathf.Max(0, Plugin.TournamentCoinPrize.Value); } return Mathf.Max(0, Plugin.TournamentValcoinPrize.Value); } public static int DuelStake(WagerCurrency c) { if (c != WagerCurrency.Valcoin) { return Mathf.Max(0, Plugin.DuelCoinStake.Value); } return Mathf.Max(0, Plugin.DuelValcoinStake.Value); } public static string CoinSharedName() { if (!string.IsNullOrEmpty(_coinSharedName)) { return _coinSharedName; } GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab("Coins") : null); SharedData val2 = ((!((Object)(object)val != (Object)null)) ? null : val.GetComponent()?.m_itemData?.m_shared); if (val2 != null) { _coinSharedName = val2.m_name; } return _coinSharedName; } public static int CoinsHeld(Player player) { string text = CoinSharedName(); if ((Object)(object)player == (Object)null || string.IsNullOrEmpty(text)) { return 0; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory != null) { return inventory.CountItems(text, -1, true); } return 0; } public static bool TryTakeCoins(Player player, int amount) { if ((Object)(object)player == (Object)null || amount <= 0) { return false; } string text = CoinSharedName(); if (string.IsNullOrEmpty(text)) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null || inventory.CountItems(text, -1, true) < amount) { return false; } inventory.RemoveItem(text, amount, -1, true); return true; } public static void GiveCoins(Player player, int amount) { //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || amount <= 0) { return; } GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab("Coins") : null); ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)val2 == (Object)null) { Plugin.Log.LogWarning((object)string.Format("[wager] '{0}' prefab missing — cannot pay {1} coins.", "Coins", amount)); return; } int num = Mathf.Max(1, val2.m_itemData.m_shared.m_maxStackSize); Inventory inventory = ((Humanoid)player).GetInventory(); int num2 = amount; while (num2 > 0) { int num3 = Mathf.Min(num2, num); num2 -= num3; ItemData val3 = val2.m_itemData.Clone(); val3.m_dropPrefab = val; val3.m_stack = num3; val3.m_quality = 1; val3.m_worldLevel = (byte)Game.m_worldLevel; if (inventory == null || !inventory.AddItem(val3)) { ItemDrop.DropItem(val3, num3, ((Component)player).transform.position + Vector3.up, ((Component)player).transform.rotation); } } } } public static class WagerService { public static bool IsAvailable(WagerCurrency currency, out string reason) { reason = null; if (!Plugin.WagersEnabled.Value) { reason = "Wagered events are disabled on this server."; return false; } switch (currency) { case WagerCurrency.Valcoin: reason = ValcoinBridge.UnavailableReason; return reason == null; case WagerCurrency.Coins: return true; default: reason = "Unknown currency."; return false; } } public static string ClientHint(WagerCurrency currency) { if (!Plugin.WagersEnabled.Value) { return "Wagered events are disabled."; } if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()) { return null; } IsAvailable(currency, out var reason); return reason; } public static void Charge(WagerCurrency currency, string playerName, int amount, string sku, string reason, int clientReportedPaid, Action done) { if (amount <= 0) { done?.Invoke(arg1: true, null); return; } if (!IsAvailable(currency, out var reason2)) { done?.Invoke(arg1: false, reason2); return; } if (currency == WagerCurrency.Coins) { if (clientReportedPaid < amount) { done?.Invoke(arg1: false, $"You need {amount} Coins to enter."); return; } Plugin.Log.LogInfo((object)$"[wager] {playerName} staked {amount} Coins ({sku})."); done?.Invoke(arg1: true, null); return; } ValcoinBridge.Charge(playerName, sku, amount, reason, delegate(bool ok, string msg) { if (ok) { Plugin.Log.LogInfo((object)$"[wager] {playerName} staked {amount} Valcoins ({sku})."); } else { Plugin.Log.LogInfo((object)$"[wager] {playerName} could not stake {amount} Valcoins ({sku}): {msg}"); } done?.Invoke(ok, msg); }); } public static void Pay(WagerCurrency currency, long ownerId, string playerName, int amount, string sku, string reason) { if (amount <= 0) { return; } switch (currency) { case WagerCurrency.None: Plugin.Log.LogWarning((object)$"[wager] ignoring a {amount} payout to {playerName} with no currency ({reason})."); return; case WagerCurrency.Coins: LeaderboardSync.SendWagerPayout(ownerId, amount, reason); Plugin.Log.LogInfo((object)$"[wager] paid {amount} Coins to {playerName} ({reason})."); return; } if (!ValcoinBridge.Available) { Plugin.Log.LogWarning((object)($"[wager] OWED {amount} Valcoins to {playerName} ({reason}) but the " + "Valcoin wallet is unavailable — settle this by hand.")); return; } ValcoinBridge.Credit(playerName, sku, amount, reason, delegate(bool ok, string msg) { if (ok) { Plugin.Log.LogInfo((object)$"[wager] paid {amount} Valcoins to {playerName} ({reason})."); } else { Plugin.Log.LogWarning((object)($"[wager] FAILED to pay {amount} Valcoins to {playerName} ({reason}): {msg} " + "— settle this by hand.")); } }); } public static void Refund(WagerCurrency currency, long ownerId, string playerName, int amount, string what) { Pay(currency, ownerId, playerName, amount, "ls_wager_refund", "refund: " + what); } public static void RollBackOwnCharge(WagerCurrency currency, long ownerId, string playerName, int amount, string what) { if (currency == WagerCurrency.Valcoin) { Refund(currency, ownerId, playerName, amount, what); } } } } namespace LostScrollsII.Companions { public class ChoreAI : MonoBehaviour { public enum ChoreKind { None, Smelter, Farm, FeedAnimals, Fermenter, Cooking, Haul } public const string ZdoKeyChoreKind = "DE_ChoreKind"; public const string ZdoKeyChorePos = "DE_ChorePos"; private const float RestoreResolveRadius = 3f; private static readonly Dictionary s_claims = new Dictionary(); private const float ArrivalRange = 4.5f; private const float ContainerSearchRadius = 8f; private const float WorkRadius = 10f; private const float FeedInterval = 5f; private const float NotifyInterval = 60f; private const float NotifyRange = 20f; private Humanoid _humanoid; private MonsterAI _ai; private DvergrCompanion _companion; private ZNetView _znv; private float _lastSayTime = -999f; private bool _pendingRestore; private ChoreKind _restoreKind; private Vector3 _restorePos; private float _restoreTimer; private Container _openChest; private float _closeChestTime; private ChoreKind _kind; private Smelter _station; private Fermenter _fermenter; private CookingStation _cooker; private Container _haulChest; private GameObject _anchorObject; private GameObject _claimedAnchor; private float _feedTimer; public string WorkerName { get { if (!((Object)(object)_companion != (Object)null)) { if (!((Object)(object)_humanoid != (Object)null)) { return "An ally"; } return ((Character)_humanoid).GetHoverName(); } return _companion.DisplayName; } } public bool IsAssigned => _kind != ChoreKind.None; public static ChoreAI ClaimantOf(GameObject anchor) { if ((Object)(object)anchor == (Object)null) { return null; } if (s_claims.TryGetValue(anchor, out var value)) { if ((Object)(object)value != (Object)null && value.IsAssigned) { return value; } s_claims.Remove(anchor); } return null; } public static ChoreAI FeederCovering(Vector3 pos) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) foreach (DvergrCompanion item in DvergrCompanion.All) { if (!((Object)(object)item == (Object)null)) { ChoreAI component = ((Component)item).GetComponent(); if ((Object)(object)component != (Object)null && component.IsAssigned && component._kind == ChoreKind.FeedAnimals && Vector3.Distance(component.CurrentCenter(), pos) <= 10f) { return component; } } } return null; } private void Awake() { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) _humanoid = ((Component)this).GetComponent(); _ai = ((Component)this).GetComponent(); _companion = ((Component)this).GetComponent(); _znv = ((Component)this).GetComponent(); if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { int num = _znv.GetZDO().GetInt("DE_ChoreKind", 0); if (num != 0) { _restoreKind = (ChoreKind)num; _restorePos = _znv.GetZDO().GetVec3("DE_ChorePos", ((Component)this).transform.position); _pendingRestore = true; } } } public static bool HasPersistedChore(ZNetView znv) { if ((Object)(object)znv != (Object)null && znv.IsValid()) { return znv.GetZDO().GetInt("DE_ChoreKind", 0) != 0; } return false; } public void AssignToSmelter(Smelter station) { _kind = ChoreKind.Smelter; _station = station; BeginChore(((Object)(object)station != (Object)null) ? ((Component)station).gameObject : null); } public void AssignToFarm(GameObject cropAnchor) { _kind = ChoreKind.Farm; BeginChore(cropAnchor); } public void AssignToFeedAnimals(GameObject animalAnchor) { _kind = ChoreKind.FeedAnimals; BeginChore(animalAnchor); } public void AssignToFermenter(Fermenter fermenter) { _kind = ChoreKind.Fermenter; _fermenter = fermenter; BeginChore(((Object)(object)fermenter != (Object)null) ? ((Component)fermenter).gameObject : null); } public void AssignToCooking(CookingStation cooker) { _kind = ChoreKind.Cooking; _cooker = cooker; BeginChore(((Object)(object)cooker != (Object)null) ? ((Component)cooker).gameObject : null); } public void AssignToHaul(Container chest) { _kind = ChoreKind.Haul; _haulChest = chest; BeginChore(((Object)(object)chest != (Object)null) ? ((Component)chest).gameObject : null); } private void BeginChore(GameObject anchor) { _anchorObject = anchor; _feedTimer = 0f; Claim(anchor); PersistChore(anchor); if ((Object)(object)_ai != (Object)null && (Object)(object)anchor != (Object)null) { _ai.SetFollowTarget(anchor); } if ((Object)(object)_companion != (Object)null) { _companion.ChoreActive = true; _companion.SetPassive(passive: true); } } private void PersistChore(GameObject anchor) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_znv == (Object)null) && _znv.IsValid()) { ZDO zDO = _znv.GetZDO(); zDO.Set("DE_ChoreKind", (int)_kind); zDO.Set("DE_ChorePos", ((Object)(object)anchor != (Object)null) ? anchor.transform.position : ((Component)this).transform.position); } } private void ClearPersistedChore() { if (!((Object)(object)_znv == (Object)null) && _znv.IsValid()) { _znv.GetZDO().Set("DE_ChoreKind", 0); } } private void TryRestore() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNetScene.instance == (Object)null) { return; } GameObject val = FindRestoreTarget(_restoreKind, _restorePos); if ((Object)(object)val != (Object)null) { _pendingRestore = false; ResumeChore(_restoreKind, val); return; } _restoreTimer += Time.deltaTime; if (_restoreTimer > 60f) { _pendingRestore = false; ClearPersistedChore(); } } private GameObject FindRestoreTarget(ChoreKind kind, Vector3 pos) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) float num = ((kind == ChoreKind.Farm || kind == ChoreKind.FeedAnimals) ? 10f : 3f); Collider[] array = Physics.OverlapSphere(pos, num); foreach (Collider val in array) { switch (kind) { case ChoreKind.Smelter: { Smelter componentInParent2 = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null) { return ((Component)componentInParent2).gameObject; } break; } case ChoreKind.Fermenter: { Fermenter componentInParent4 = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent4 != (Object)null) { return ((Component)componentInParent4).gameObject; } break; } case ChoreKind.Cooking: { CookingStation componentInParent8 = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent8 != (Object)null) { return ((Component)componentInParent8).gameObject; } break; } case ChoreKind.Haul: { Container componentInParent3 = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent3 != (Object)null) { return ((Component)componentInParent3).gameObject; } break; } case ChoreKind.Farm: { ItemStand componentInParent5 = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent5 != (Object)null && componentInParent5.HaveAttachment() && componentInParent5.GetAttachedItem() == "Cultivator") { return ((Component)componentInParent5).gameObject; } Pickable componentInParent6 = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent6 != (Object)null) { return ((Component)componentInParent6).gameObject; } Plant componentInParent7 = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent7 != (Object)null) { return ((Component)componentInParent7).gameObject; } break; } case ChoreKind.FeedAnimals: { Character componentInParent = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && componentInParent.IsTamed() && (Object)(object)((Component)componentInParent).GetComponent() == (Object)null) { return ((Component)componentInParent).gameObject; } break; } } } return null; } private void ResumeChore(ChoreKind kind, GameObject go) { switch (kind) { case ChoreKind.Smelter: { Smelter componentInParent4 = go.GetComponentInParent(); if ((Object)(object)componentInParent4 != (Object)null) { AssignToSmelter(componentInParent4); } break; } case ChoreKind.Fermenter: { Fermenter componentInParent2 = go.GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null) { AssignToFermenter(componentInParent2); } break; } case ChoreKind.Cooking: { CookingStation componentInParent3 = go.GetComponentInParent(); if ((Object)(object)componentInParent3 != (Object)null) { AssignToCooking(componentInParent3); } break; } case ChoreKind.Haul: { Container componentInParent = go.GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { AssignToHaul(componentInParent); } break; } case ChoreKind.Farm: AssignToFarm(go); break; case ChoreKind.FeedAnimals: AssignToFeedAnimals(go); break; } if (!IsAssigned) { ClearPersistedChore(); } } private void Claim(GameObject anchor) { ReleaseClaim(); if (!((Object)(object)anchor == (Object)null)) { s_claims[anchor] = this; _claimedAnchor = anchor; } } private void ReleaseClaim() { if (!((Object)(object)_claimedAnchor == (Object)null)) { if (s_claims.TryGetValue(_claimedAnchor, out var value) && (Object)(object)value == (Object)(object)this) { s_claims.Remove(_claimedAnchor); } _claimedAnchor = null; } } private void OnDisable() { ReleaseClaim(); } public void Unassign() { _kind = ChoreKind.None; _station = null; _fermenter = null; _cooker = null; _haulChest = null; _anchorObject = null; _pendingRestore = false; ReleaseClaim(); ClearPersistedChore(); ClearSpeech(); CloseChest(); if ((Object)(object)_companion != (Object)null) { _companion.ChoreActive = false; DvergrCompanion companion = _companion; Player obj = _companion.OwnerPlayer(); companion.SetStance(CompanionStance.Follow, (obj != null) ? ((Component)obj).gameObject : null); } else if ((Object)(object)_ai != (Object)null) { _ai.SetFollowTarget((GameObject)null); } } private void FaceToward(Vector3 pos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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) Vector3 val = pos - ((Component)this).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude > 0.01f && (Object)(object)_humanoid != (Object)null) { ((Character)_humanoid).SetLookDir(((Vector3)(ref val)).normalized, 0f); } } private void OpenChest(Container c) { if (!((Object)(object)c == (Object)null)) { if ((Object)(object)_openChest != (Object)(object)c) { CloseChest(); c.SetInUse(true); _openChest = c; } _closeChestTime = Time.time + 1.5f; } } private void CloseChest() { if ((Object)(object)_openChest != (Object)null) { _openChest.SetInUse(false); _openChest = null; } } private Vector3 CurrentCenter() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) if (_kind == ChoreKind.Smelter && (Object)(object)_station != (Object)null) { return ((Component)_station).transform.position; } if (_kind == ChoreKind.Fermenter && (Object)(object)_fermenter != (Object)null) { return ((Component)_fermenter).transform.position; } if (_kind == ChoreKind.Cooking && (Object)(object)_cooker != (Object)null) { return ((Component)_cooker).transform.position; } if (_kind == ChoreKind.Haul && (Object)(object)_haulChest != (Object)null) { return ((Component)_haulChest).transform.position; } if ((Object)(object)_anchorObject != (Object)null) { return _anchorObject.transform.position; } return ((Component)this).transform.position; } private void Update() { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_openChest != (Object)null && Time.time > _closeChestTime) { CloseChest(); } if (_pendingRestore) { TryRestore(); } else { if (_kind == ChoreKind.None || (Object)(object)_humanoid == (Object)null || ((Object)(object)_znv != (Object)null && _znv.IsValid() && !_znv.IsOwner())) { return; } if ((Object)(object)_anchorObject == (Object)null) { Unassign(); return; } _feedTimer += Time.deltaTime; if (_feedTimer < 5f) { return; } _feedTimer = 0f; if (Vector3.Distance(((Component)this).transform.position, CurrentCenter()) > 4.5f) { Say((_kind == ChoreKind.Farm) ? "I can't reach the field!" : ((_kind == ChoreKind.FeedAnimals) ? "I can't reach the pen!" : ((_kind == ChoreKind.Haul) ? "I can't reach the chest!" : "I can't reach my station!"))); return; } switch (_kind) { case ChoreKind.Smelter: ServiceStation(); break; case ChoreKind.Farm: ServiceFarm(); break; case ChoreKind.FeedAnimals: ServiceAnimals(); break; case ChoreKind.Fermenter: ServiceFermenter(); break; case ChoreKind.Cooking: ServiceCooking(); break; case ChoreKind.Haul: ServiceHaul(); break; } } } private void ServiceStation() { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) bool flag = _station.GetQueueSize() < _station.m_maxOre; bool flag2 = (Object)(object)_station.m_fuelItem != (Object)null && _station.GetFuel() < (float)_station.m_maxFuel; if (!flag && !flag2) { ClearSpeech(); return; } string text = (((Object)(object)_station.m_fuelItem != (Object)null) ? ((Object)_station.m_fuelItem).name : null); bool fuelMissing = flag2; bool oreMissing = flag; Inventory inventory = _humanoid.GetInventory(); Collider[] array = Physics.OverlapSphere(((Component)_station).transform.position, 8f); bool flag3 = false; Collider[] array2 = array; for (int i = 0; i < array2.Length; i++) { Container componentInParent = ((Component)array2[i]).GetComponentInParent(); Inventory val = (((Object)(object)componentInParent != (Object)null) ? componentInParent.GetInventory() : null); if (val == null) { continue; } flag3 = true; foreach (ItemData item in new List(val.GetAllItems())) { string text2 = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null); if (text2 == null) { continue; } if (flag2 && text2 == text) { if (val.RemoveItem(item, 1)) { inventory.AddItem(item.Clone()); OpenChest(componentInParent); FaceToward(((Component)_station).transform.position); _station.SetFuel(_station.GetFuel() + 1f); PlayEffects(_station.m_fuelAddedEffects, ((Component)_station).transform); flag2 = false; fuelMissing = false; } } else if (flag && _station.GetItemConversion(text2) != null && val.RemoveItem(item, 1)) { inventory.AddItem(item.Clone()); OpenChest(componentInParent); FaceToward(((Component)_station).transform.position); _station.QueueOre(text2); PlayEffects(_station.m_oreAddedEffects, ((Component)_station).transform); flag = false; oreMissing = false; } } if (!flag && !flag2) { break; } } if (!flag3) { Say("I have no chest to draw from!"); } else { ReportMissing(oreMissing, fuelMissing); } } private string InputNames() { HashSet hashSet = new HashSet(); if ((Object)(object)_station != (Object)null && _station.m_conversion != null) { foreach (ItemConversion item in _station.m_conversion) { if (item != null && (Object)(object)item.m_from != (Object)null && item.m_from.m_itemData != null && item.m_from.m_itemData.m_shared != null) { hashSet.Add(Localization.instance.Localize(item.m_from.m_itemData.m_shared.m_name)); } } } if (hashSet.Count <= 0) { return "materials"; } return string.Join(" or ", hashSet); } private string FuelName() { if ((Object)(object)_station != (Object)null && (Object)(object)_station.m_fuelItem != (Object)null && _station.m_fuelItem.m_itemData != null && _station.m_fuelItem.m_itemData.m_shared != null) { return Localization.instance.Localize(_station.m_fuelItem.m_itemData.m_shared.m_name); } return "fuel"; } private void ServiceFarm() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000f: 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_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) Vector3 val = CurrentCenter(); Inventory val2 = FirstContainerInventory(val); Collider[] array = Physics.OverlapSphere(val, 10f); for (int i = 0; i < array.Length; i++) { Pickable componentInParent = ((Component)array[i]).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null || !componentInParent.CanBePicked()) { continue; } if (val2 == null) { Say("I have no chest for the harvest."); return; } GameObject itemPrefab = componentInParent.m_itemPrefab; int num = Mathf.Max(1, componentInParent.m_amount); if (!((Object)(object)itemPrefab == (Object)null)) { if (!val2.CanAddItem(itemPrefab, num)) { Say("The harvest chest is full!"); return; } val2.AddItem(itemPrefab, num); componentInParent.SetPicked(true); FaceToward(((Component)componentInParent).transform.position); PlayEffects(componentInParent.m_pickEffector, null, ((Component)componentInParent).transform.position); ClearSpeech(); return; } } if (val2 == null) { Say("I have no chest for the seeds."); return; } ItemData val3 = null; GameObject val4 = null; bool flag = false; foreach (ItemData allItem in val2.GetAllItems()) { if (PlantingCatalog.TryGetSapling(((Object)(object)allItem.m_dropPrefab != (Object)null) ? ((Object)allItem.m_dropPrefab).name : null, out var sapling)) { if (CanGrowInBiome(sapling.GetComponent(), val)) { val3 = allItem; val4 = sapling; break; } flag = true; } } Vector3 spot; if ((Object)(object)val4 == (Object)null) { Say(flag ? "These seeds won't grow in this land." : "No crops are ready, and no seeds to plant."); } else if (!TryFindPlantSpot(val, val4, out spot)) { Say("There's no room left to plant."); } else if (val2.RemoveItem(val3, 1)) { Object.Instantiate(val4, spot, Quaternion.Euler(0f, Random.Range(0f, 360f), 0f)); Piece component = val4.GetComponent(); if ((Object)(object)component != (Object)null) { PlayEffects(component.m_placeEffect, null, spot); } FaceToward(spot); Plugin.Log.LogInfo((object)$"[farm] planted '{((Object)val4).name}' at {spot} (biome {CurrentBiome(spot)})."); ClearSpeech(); } } private bool TryFindPlantSpot(Vector3 center, GameObject sapling, out Vector3 spot) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: 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_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) spot = center; Plant component = sapling.GetComponent(); float radius = (((Object)(object)component != (Object)null) ? Mathf.Max(component.m_growRadius, 0.5f) : 1f); ZoneSystem instance = ZoneSystem.instance; Vector3 val2 = default(Vector3); for (int i = 0; i < 24; i++) { Vector2 val = Random.insideUnitCircle * 8.5f; ((Vector3)(ref val2))..ctor(center.x + val.x, center.y, center.z + val.y); if ((Object)(object)instance != (Object)null) { val2.y = instance.GetGroundHeight(val2); } Heightmap val3 = Heightmap.FindHeightmap(val2); if (!((Object)(object)val3 == (Object)null) && val3.IsCultivated(val2) && CanGrowInBiome(component, val2) && IsSpotClear(val2, radius)) { spot = val2; return true; } } return false; } private static bool IsSpotClear(Vector3 p, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Collider[] array = Physics.OverlapSphere(p, radius); foreach (Collider val in array) { if ((Object)(object)((Component)val).GetComponentInParent() != (Object)null) { return false; } Pickable componentInParent = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && !componentInParent.GetPicked()) { return false; } } return true; } private static bool CanGrowInBiome(Plant plant, Vector3 pos) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 if ((Object)(object)plant == (Object)null) { return true; } if (WorldGenerator.instance == null) { return true; } Biome biome = WorldGenerator.instance.GetBiome(pos); return (plant.m_biome & biome) > 0; } private static Biome CurrentBiome(Vector3 pos) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (WorldGenerator.instance == null) { return (Biome)0; } return WorldGenerator.instance.GetBiome(pos); } private void ServiceAnimals() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) Vector3 val = CurrentCenter(); List list = new List(); Collider[] array = Physics.OverlapSphere(val, 10f); for (int i = 0; i < array.Length; i++) { Character componentInParent = ((Component)array[i]).GetComponentInParent(); if (!((Object)(object)componentInParent == (Object)null) && componentInParent.IsTamed() && !((Object)(object)((Component)componentInParent).GetComponent() != (Object)null)) { Tameable component = ((Component)componentInParent).GetComponent(); if ((Object)(object)component != (Object)null && component.IsHungry() && !list.Contains(componentInParent)) { list.Add(componentInParent); } } } if (list.Count == 0) { Say("The animals aren't hungry."); return; } Inventory val2 = FirstContainerInventory(val); if (val2 == null) { Say("I have no food chest nearby."); return; } foreach (Character item in list) { MonsterAI component2 = ((Component)item).GetComponent(); if (!((Object)(object)component2 == (Object)null) && component2.m_consumeItems != null && !ItemAlreadyDroppedNear(((Component)item).transform.position)) { ItemData val3 = FindAcceptedFood(val2, component2.m_consumeItems); if (val3 == null) { Say("I have no food to give."); return; } if (val2.RemoveItem(val3, 1)) { ItemData obj = val3.Clone(); obj.m_stack = 1; ItemDrop.DropItem(obj, 1, ((Component)item).transform.position + Vector3.up * 0.3f, Quaternion.identity); ClearSpeech(); return; } } } ClearSpeech(); } private static ItemData FindAcceptedFood(Inventory chest, List consumeItems) { foreach (ItemData allItem in chest.GetAllItems()) { string text = (((Object)(object)allItem.m_dropPrefab != (Object)null) ? ((Object)allItem.m_dropPrefab).name : null); if (text == null) { continue; } foreach (ItemDrop consumeItem in consumeItems) { if ((Object)(object)consumeItem != (Object)null && ((Object)consumeItem).name == text) { return allItem; } } } return null; } private static bool ItemAlreadyDroppedNear(Vector3 pos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Collider[] array = Physics.OverlapSphere(pos, 2f); for (int i = 0; i < array.Length; i++) { if ((Object)(object)((Component)array[i]).GetComponentInParent() != (Object)null) { return true; } } return false; } private void ServiceFermenter() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected I4, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) Status status = _fermenter.GetStatus(); switch (status - 1) { case 2: _fermenter.Interact(_humanoid, false, false); ClearSpeech(); return; case 0: ClearSpeech(); return; case 1: Say("The brew is exposed to the sky!"); return; } Inventory val = FirstContainerInventory(CurrentCenter()); if (val == null) { Say("I have no chest to brew from!"); return; } foreach (ItemData item in new List(val.GetAllItems())) { string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null); if (text != null && _fermenter.IsItemAllowed(text) && val.RemoveItem(item, 1)) { ItemData val2 = item.Clone(); val2.m_stack = 1; _humanoid.GetInventory().AddItem(val2); if (_fermenter.AddItem(_humanoid, val2)) { ClearSpeech(); return; } } } Say("I have nothing to brew!"); } private void ServiceCooking() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) if (_cooker.HaveDoneItem()) { _cooker.OnInteract(_humanoid); ClearSpeech(); return; } if ((Object)(object)_cooker.m_fuelItem != (Object)null && _cooker.GetFuel() < (float)_cooker.m_maxFuel) { Inventory val = FirstContainerInventory(CurrentCenter()); string name = ((Object)_cooker.m_fuelItem).name; if (val != null) { foreach (ItemData item in new List(val.GetAllItems())) { if (!((((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null) != name) && val.RemoveItem(item, 1)) { _humanoid.GetInventory().AddItem(item.Clone()); _cooker.SetFuel(_cooker.GetFuel() + 1f); ClearSpeech(); return; } } } } if (_cooker.m_requireFire && !_cooker.IsFireLit()) { Say("The cooking fire is out!"); return; } if (_cooker.GetFreeSlot() < 0) { ClearSpeech(); return; } Inventory val2 = FirstContainerInventory(CurrentCenter()); if (val2 == null) { Say("I have no chest to cook from!"); return; } foreach (ItemData item2 in new List(val2.GetAllItems())) { string text = (((Object)(object)item2.m_dropPrefab != (Object)null) ? ((Object)item2.m_dropPrefab).name : null); if (text != null && _cooker.GetItemConversion(text) != null && val2.RemoveItem(item2, 1)) { ItemData val3 = item2.Clone(); val3.m_stack = 1; _humanoid.GetInventory().AddItem(val3); if (_cooker.CookItem(_humanoid, val3)) { ClearSpeech(); return; } } } Say("I have nothing to cook!"); } private void ServiceHaul() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) Inventory val = (((Object)(object)_haulChest != (Object)null) ? _haulChest.GetInventory() : null); if (val == null) { return; } Collider[] array = Physics.OverlapSphere(CurrentCenter(), 10f); for (int i = 0; i < array.Length; i++) { ItemDrop componentInParent = ((Component)array[i]).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null || !componentInParent.CanPickup(false)) { continue; } ZNetView component = ((Component)componentInParent).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid()) { continue; } ItemData itemData = componentInParent.m_itemData; if (itemData != null) { if (!val.CanAddItem(itemData, itemData.m_stack)) { Say("The haul chest is full!"); return; } val.AddItem(itemData.Clone()); OpenChest(_haulChest); FaceToward(((Component)_haulChest).transform.position); component.ClaimOwnership(); ZNetScene.instance.Destroy(((Component)componentInParent).gameObject); ClearSpeech(); return; } } ClearSpeech(); } private Inventory FirstContainerInventory(Vector3 center) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Collider[] array = Physics.OverlapSphere(center, 8f); for (int i = 0; i < array.Length; i++) { Container componentInParent = ((Component)array[i]).GetComponentInParent(); Inventory val = (((Object)(object)componentInParent != (Object)null) ? componentInParent.GetInventory() : null); if (val != null) { return val; } } return null; } private void PlayEffects(EffectList effects, Transform parent, Vector3? at = null) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (effects != null) { Vector3 val = (Vector3)(((??)at) ?? (((Object)(object)parent != (Object)null) ? parent.position : ((Component)this).transform.position)); effects.Create(val, Quaternion.identity, parent, 1f, -1); } } private void ReportMissing(bool oreMissing, bool fuelMissing) { string text = ((oreMissing && fuelMissing) ? ("I need more " + InputNames() + " and " + FuelName() + "!") : (fuelMissing ? ("I need more " + FuelName() + "!") : (oreMissing ? ("I need more " + InputNames() + "!") : null))); if (text == null) { ClearSpeech(); } else { Say(text); } } private void Say(string text) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Chat.instance == (Object)null) && !((Object)(object)_companion == (Object)null) && _companion.IsOwnerNear(20f) && !(Time.time - _lastSayTime < 60f)) { _lastSayTime = Time.time; Chat.instance.SetNpcText(((Component)this).gameObject, Vector3.up * 2.2f, 20f, 8f, string.Empty, text, false); } } private void ClearSpeech() { if ((Object)(object)Chat.instance != (Object)null) { Chat.instance.ClearNpcText(((Component)this).gameObject); } } } public static class ChoreRules { public static DvergrCaste? RequiredCaste(Smelter station) { string text = (((Object)(object)station != (Object)null) ? ((Object)station).name.ToLowerInvariant() : string.Empty); if (text.Contains("smelter") || text.Contains("blast") || text.Contains("charcoal")) { return DvergrCaste.FireMage; } if (text.Contains("eitr") || text.Contains("spinning") || text.Contains("windmill")) { return DvergrCaste.IceMage; } return null; } public static string DisplayName(DvergrCaste caste) { return caste.Display(); } } public class CommunionRite : MonoBehaviour { public const string ZdoKeyCommuning = "DE_Communing"; private const float LashInterval = 1.25f; private const float DamageEpsilon = 0.5f; private const float ReleaseGraceSeconds = 0.5f; private const float PulseIntervalStart = 1f; private const float PulseIntervalEnd = 0.28f; private Character _target; private Player _player; private float _elapsed; private float _lashTimer; private float _lastPlayerHealth; private float _releaseGrace; private float _pulseTimer; private int _lashIndex; private StatusEffect _wishboneSE; private EffectList _pingEffect; private static readonly string[] LashLines = new string[4] { "The corruption writhes — hold fast.", "It fights the light within.", "The shadow claws to keep its hold.", "Damon's grip will not loosen easily." }; public static CommunionRite Instance { get; private set; } public bool IsActive => (Object)(object)_target != (Object)null; private void Awake() { Instance = this; } public bool Begin(Character target, Player player) { if ((Object)(object)target == (Object)null || (Object)(object)player == (Object)null) { return false; } if ((Object)(object)_target != (Object)null) { return false; } ZNetView component = ((Component)target).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return false; } if (component.GetZDO().GetBool("DE_Communing", false)) { MessageHud.instance.ShowMessage((MessageType)2, "Another is already in communion with it.", 0, (Sprite)null, false); return false; } component.ClaimOwnership(); component.GetZDO().Set("DE_Communing", true); _target = target; _player = player; _elapsed = 0f; _lashTimer = 0f; _lashIndex = 0; _releaseGrace = 0f; _pulseTimer = 0f; _lastPlayerHealth = ((Character)player).GetHealth(); PlayPulse((Character)(object)player); PlayPulse(target); MessageHud.instance.ShowMessage((MessageType)2, "You reach for the soul beneath the corruption…", 0, (Sprite)null, false); return true; } private void Update() { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_target == (Object)null) { return; } if ((Object)(object)_target == (Object)null || _target.IsDead() || (Object)(object)_player == (Object)null) { Fail("The rite is broken."); return; } if (BlockHeld()) { _releaseGrace = 0f; } else { _releaseGrace += Time.deltaTime; if (_releaseGrace >= 0.5f) { Fail("You lower your guard — the shadow reclaims it!"); return; } } float num = Mathf.Max(1f, Plugin.CommunionMaxDistance.Value); if (Vector3.Distance(((Component)_player).transform.position, ((Component)_target).transform.position) > num) { Fail("You stray too far — the rite shatters!"); return; } float health = ((Character)_player).GetHealth(); if (Plugin.CommunionBreakOnDamage.Value && health < _lastPlayerHealth - 0.5f) { Fail("Struck mid-rite — the shadow reclaims it!"); return; } _lastPlayerHealth = health; if (_target.GetHealthPercentage() > 0.5f) { Fail("It tears free of your grasp!"); return; } _elapsed += Time.deltaTime; float num2 = Mathf.Clamp01(_elapsed / Mathf.Max(0.1f, Plugin.CommunionChannelSeconds.Value)); float num3 = Mathf.Lerp(1f, 0.28f, num2); _pulseTimer += Time.deltaTime; if (_pulseTimer >= num3) { _pulseTimer = 0f; PlayPulse(_target); PlayPulse((Character)(object)_player); } _lashTimer += Time.deltaTime; if (_lashTimer >= 1.25f) { _lashTimer = 0f; MessageHud.instance.ShowMessage((MessageType)2, LashLines[_lashIndex % LashLines.Length], 0, (Sprite)null, false); _lashIndex++; } if (_elapsed >= Mathf.Max(0.1f, Plugin.CommunionChannelSeconds.Value)) { Succeed(); } } private void Succeed() { //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_005c: Unknown result type (might be due to invalid IL or missing references) Character target = _target; Player player = _player; ClearLock(target); Reset(); DvergrCaste dvergrCaste = CommunionService.DetectCaste(target); Plugin.Log.LogInfo((object)$"[recruit] rite complete — '{((Object)target).name}' detected as caste {dvergrCaste}."); if (CommunionService.TryRecruit(target, player, dvergrCaste)) { TotemConversionService.PlaySummonVfx(((Component)target).transform.position + Vector3.up); MessageHud.instance.ShowMessage((MessageType)2, "The shadow's grip loosens — a " + dvergrCaste.Display() + " joins you.", 0, (Sprite)null, false); } else { MessageHud.instance.ShowMessage((MessageType)2, "The rite falters at the last moment.", 0, (Sprite)null, false); } } private void Fail(string message) { Character target = _target; Player player = _player; ClearLock(target); Reset(); if ((Object)(object)target == (Object)null) { return; } MonsterAI component = ((Component)target).GetComponent(); if ((Object)(object)component != (Object)null) { ((BaseAI)component).SetAggravated(true, (AggravatedReason)0); ((BaseAI)component).SetAlerted(true); if ((Object)(object)player != (Object)null) { component.SetTarget(((Component)player).GetComponent()); } } MessageHud.instance.ShowMessage((MessageType)2, message, 0, (Sprite)null, false); } private static void ClearLock(Character target) { if (!((Object)(object)target == (Object)null)) { ZNetView component = ((Component)target).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid() && component.IsOwner()) { component.GetZDO().Set("DE_Communing", false); } } } private void Reset() { _target = null; _player = null; _elapsed = 0f; _lashTimer = 0f; _lashIndex = 0; _releaseGrace = 0f; } public static bool BlockHeld() { if (ZInput.instance != null) { return ZInput.GetButton("Block"); } return false; } private StatusEffect WishboneSE() { if ((Object)(object)_wishboneSE != (Object)null) { return _wishboneSE; } if ((Object)(object)ObjectDB.instance == (Object)null) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Wishbone"); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null); if ((Object)(object)val != (Object)null && val.m_itemData != null && val.m_itemData.m_shared != null) { _wishboneSE = val.m_itemData.m_shared.m_equipStatusEffect; } return _wishboneSE; } private EffectList PingEffect() { if (_pingEffect != null) { return _pingEffect; } StatusEffect obj = WishboneSE(); SE_Finder val = (SE_Finder)(object)((obj is SE_Finder) ? obj : null); if ((Object)(object)val != (Object)null) { _pingEffect = val.m_pingEffectNear; } return _pingEffect; } private void PlayPulse(Character c) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)c == (Object)null)) { PlayPulseAt(((Component)c).transform.position); } } public void PlayPulseAt(Vector3 pos) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) EffectList val = PingEffect(); if (val != null) { val.Create(pos, Quaternion.identity, (Transform)null, 1f, -1); } } } public static class CommunionService { public const float SubdueHealthThreshold = 0.2f; public static DvergrCaste DetectCaste(Character target, bool log = true) { if ((Object)(object)target == (Object)null) { return DvergrCaste.Rogue; } Humanoid component = ((Component)target).GetComponent(); if ((Object)(object)component != (Object)null) { string[] array = new string[5] { ItemName(component.GetCurrentWeapon()), ItemName(component.m_rightItem), ItemName(component.m_hiddenRightItem), ItemName(component.m_leftItem), ItemName(component.m_hiddenLeftItem) }; foreach (string text in array) { DvergrCaste? dvergrCaste = ClassifyByElement(text); if (dvergrCaste.HasValue) { if (log) { Plugin.Log.LogInfo((object)$"[recruit] caste {dvergrCaste.Value} from weapon slot '{text}'."); } return dvergrCaste.Value; } } Inventory inventory = component.GetInventory(); if (inventory != null) { foreach (ItemData allItem in inventory.GetAllItems()) { string text2 = ItemName(allItem); DvergrCaste? dvergrCaste2 = ((text2 != null && text2.ToLowerInvariant().Contains("staff")) ? ClassifyByElement(text2) : ((DvergrCaste?)null)); if (dvergrCaste2.HasValue) { if (log) { Plugin.Log.LogInfo((object)$"[recruit] caste {dvergrCaste2.Value} from inventory item '{text2}'."); } return dvergrCaste2.Value; } } } } string name = ((Object)target).name; if (!string.IsNullOrEmpty(name)) { if (name.IndexOf("Fire", StringComparison.OrdinalIgnoreCase) >= 0) { return DvergrCaste.FireMage; } if (name.IndexOf("Ice", StringComparison.OrdinalIgnoreCase) >= 0) { return DvergrCaste.IceMage; } if (name.IndexOf("Support", StringComparison.OrdinalIgnoreCase) >= 0) { return DvergrCaste.SupportMage; } } if (log) { LogDetectionMiss(target, component); } return DvergrCaste.Rogue; } private static DvergrCaste? ClassifyByElement(string name) { if (string.IsNullOrEmpty(name)) { return null; } string text = name.ToLowerInvariant(); if (text.Contains("fire")) { return DvergrCaste.FireMage; } if (text.Contains("ice") || text.Contains("frost")) { return DvergrCaste.IceMage; } if (text.Contains("support") || text.Contains("heal") || text.Contains("nova") || text.Contains("blocker") || text.Contains("shield")) { return DvergrCaste.SupportMage; } return null; } private static string ItemName(ItemData item) { if (item == null) { return null; } if ((Object)(object)item.m_dropPrefab != (Object)null) { return ((Object)item.m_dropPrefab).name; } if (item.m_shared == null) { return null; } return item.m_shared.m_name; } private static void LogDetectionMiss(Character target, Humanoid humanoid) { string text = "(none)"; if ((Object)(object)humanoid != (Object)null && humanoid.GetInventory() != null) { List list = new List(); foreach (ItemData allItem in humanoid.GetInventory().GetAllItems()) { list.Add(ItemName(allItem)); } if (list.Count > 0) { text = string.Join(", ", list); } } Plugin.Log.LogInfo((object)("[recruit] No staff detected -> Rogue. prefab='" + ((Object)target).name + "', slots[" + Slots() + "], inventory=[" + text + "].")); string Slots() { if ((Object)(object)humanoid == (Object)null) { return "(no humanoid)"; } return "cur='" + ItemName(humanoid.GetCurrentWeapon()) + "', right='" + ItemName(humanoid.m_rightItem) + "', hiddenRight='" + ItemName(humanoid.m_hiddenRightItem) + "', left='" + ItemName(humanoid.m_leftItem) + "', hiddenLeft='" + ItemName(humanoid.m_hiddenLeftItem) + "'"; } } public static GameObject SpawnRecruited(DvergrCaste caste, int level, Player owner, Vector3 pos, float xp = 0f, string companionId = null) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab(CastePrefab(caste)); if ((Object)(object)prefab == (Object)null && caste != DvergrCaste.Rogue) { prefab = ZNetScene.instance.GetPrefab("DvergerMage"); } if ((Object)(object)prefab == (Object)null) { prefab = ZNetScene.instance.GetPrefab("Dverger"); } if ((Object)(object)prefab == (Object)null) { return null; } GameObject val = Object.Instantiate(prefab, pos, Quaternion.identity); Character component = val.GetComponent(); ZNetView component2 = val.GetComponent(); if ((Object)(object)component2 != (Object)null && component2.IsValid()) { ZDO zDO = component2.GetZDO(); zDO.Set("DE_Recruited", true); zDO.Set("DE_Caste", (int)caste); zDO.Set("DE_Level", Mathf.Clamp(level, 1, 10)); zDO.Set("DE_Xp", Mathf.Max(0f, xp)); if ((Object)(object)owner != (Object)null) { zDO.Set("DE_Owner", owner.GetPlayerID()); zDO.Set("DE_OwnerName", owner.GetPlayerName()); } if (!string.IsNullOrEmpty(companionId)) { zDO.Set("DE_CompanionId", companionId); } } if ((Object)(object)component != (Object)null) { ApplyFreedState(component, ((Object)(object)owner != (Object)null) ? ((Component)owner).gameObject : null); } if ((Object)(object)val.GetComponent() == (Object)null) { val.AddComponent(); } if ((Object)(object)val.GetComponent() == (Object)null) { val.AddComponent(); } if ((Object)(object)val.GetComponent() == (Object)null) { val.AddComponent(); } Plugin.Log.LogInfo((object)string.Format("[admin] Spawned recruited {0} (lv {1}) for {2}.", caste, level, ((Object)(object)owner != (Object)null) ? owner.GetPlayerName() : "?")); return val; } private static string CastePrefab(DvergrCaste caste) { return caste switch { DvergrCaste.FireMage => "DvergerMageFire", DvergrCaste.IceMage => "DvergerMageIce", DvergrCaste.SupportMage => "DvergerMageSupport", _ => "Dverger", }; } public static bool IsSubduedDvergr(Character target) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 if ((Object)(object)target == (Object)null) { return false; } if ((int)target.m_faction != 10) { return false; } if (target.IsDead()) { return false; } return target.GetHealthPercentage() <= 0.2f; } public static bool TryRecruit(Character target, Player recruiter, DvergrCaste caste) { if (!IsSubduedDvergr(target)) { return false; } ApplyFreedState(target, ((Object)(object)recruiter != (Object)null) ? ((Component)recruiter).gameObject : null); DvergrCompanion dvergrCompanion = ((Component)target).GetComponent(); if ((Object)(object)dvergrCompanion == (Object)null) { dvergrCompanion = ((Component)target).gameObject.AddComponent(); } if ((Object)(object)((Component)target).GetComponent() == (Object)null) { ((Component)target).gameObject.AddComponent(); } if ((Object)(object)((Component)target).GetComponent() == (Object)null) { ((Component)target).gameObject.AddComponent(); } dvergrCompanion.SetCaste(caste); if ((Object)(object)recruiter != (Object)null) { dvergrCompanion.SetOwner(recruiter.GetPlayerID()); dvergrCompanion.SetOwnerName(recruiter.GetPlayerName()); } dvergrCompanion.EnsureCompanionId(); ZNetView component = ((Component)target).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { component.GetZDO().Set("DE_Recruited", true); } Plugin.Log.LogInfo((object)string.Format("Dvergr {0} recruited by {1}.", caste, ((Object)(object)recruiter != (Object)null) ? recruiter.GetPlayerName() : "unknown")); ServerGuideBridge.RaiseRecruited(caste); BountyService.NotifyCommuned(target, recruiter); dvergrCompanion.AnnounceCapability(); return true; } public static void RestoreCompanion(Character target) { if ((Object)(object)target == (Object)null) { return; } ZNetView component = ((Component)target).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid() && component.GetZDO().GetBool("DE_Recruited", false)) { ApplyFreedState(target, null); if ((Object)(object)((Component)target).GetComponent() == (Object)null) { ((Component)target).gameObject.AddComponent(); } if ((Object)(object)((Component)target).GetComponent() == (Object)null) { ((Component)target).gameObject.AddComponent(); } if ((Object)(object)((Component)target).GetComponent() == (Object)null) { ((Component)target).gameObject.AddComponent(); } if (ChoreAI.HasPersistedChore(component) && (Object)(object)((Component)target).GetComponent() == (Object)null) { ((Component)target).gameObject.AddComponent(); } } } private static void ApplyFreedState(Character target, GameObject followTarget) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) target.m_faction = (Faction)0; NpcTalk component = ((Component)target).GetComponent(); if ((Object)(object)component != (Object)null) { ((Behaviour)component).enabled = false; } MonsterAI component2 = ((Component)target).GetComponent(); if (!((Object)(object)component2 == (Object)null)) { ((BaseAI)component2).SetAggravated(false, (AggravatedReason)0); ((BaseAI)component2).m_aggravatable = false; component2.m_attackPlayerObjects = false; component2.m_targetStatic = null; ((BaseAI)component2).m_avoidWater = true; component2.SetTarget((Character)null); ((BaseAI)component2).SetAlerted(false); ((BaseAI)component2).SetHuntPlayer(false); if ((Object)(object)followTarget != (Object)null) { component2.SetFollowTarget(followTarget); } } } } public enum ConsumableKind { None, Food, HealthMead, ResistMead } public static class CompanionConsumables { public static ConsumableKind Classify(ItemData item) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if (item?.m_shared == null) { return ConsumableKind.None; } if ((int)item.m_shared.m_itemType != 2) { return ConsumableKind.None; } if (item.m_shared.m_food > 0f) { return ConsumableKind.Food; } StatusEffect consumeStatusEffect = item.m_shared.m_consumeStatusEffect; SE_Stats val = (SE_Stats)(object)((consumeStatusEffect is SE_Stats) ? consumeStatusEffect : null); if ((Object)(object)val == (Object)null) { return ConsumableKind.None; } if (val.m_healthUpFront + val.m_healthOverTime > 0f) { return ConsumableKind.HealthMead; } if (GrantsResistance(val)) { return ConsumableKind.ResistMead; } return ConsumableKind.None; } public static float HealAmount(ItemData item) { StatusEffect obj = item?.m_shared?.m_consumeStatusEffect; SE_Stats val = (SE_Stats)(object)((obj is SE_Stats) ? obj : null); if ((Object)(object)val == (Object)null) { return 0f; } return Mathf.Max(0f, val.m_healthUpFront + val.m_healthOverTime); } public static StatusEffect ConsumeEffect(ItemData item) { if (item?.m_shared == null) { return null; } return item.m_shared.m_consumeStatusEffect; } private static bool GrantsResistance(SE_Stats stats) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected I4, but got Unknown if (stats.m_mods == null) { return false; } foreach (DamageModPair mod in stats.m_mods) { DamageModifier modifier = mod.m_modifier; switch (modifier - 1) { case 0: case 2: case 4: return true; } } return false; } public static List ActiveResistEffects(Character c) { List list = new List(); SEMan val = (((Object)(object)c != (Object)null) ? c.GetSEMan() : null); if (val == null) { return list; } foreach (StatusEffect statusEffect in val.GetStatusEffects()) { SE_Stats val2 = (SE_Stats)(object)((statusEffect is SE_Stats) ? statusEffect : null); if (val2 != null && GrantsResistance(val2)) { list.Add(statusEffect); } } return list; } } [RequireComponent(typeof(DvergrCompanion))] public class CompanionInventory : MonoBehaviour { public const int Columns = 4; public const int Rows = 2; public const float MaxWeight = 150f; private Container _container; private DvergrCompanion _companion; public Container Container => _container; public Inventory Inventory { get { if (!((Object)(object)_container != (Object)null)) { return null; } return _container.GetInventory(); } } public bool IsOverCapacity => TotalWeight() > 150f; public bool IsEmpty { get { if (Inventory != null) { return Inventory.NrOfItems() == 0; } return true; } } private void Awake() { _companion = ((Component)this).GetComponent(); EnsureContainer(); if ((Object)(object)((Component)this).GetComponent() == (Object)null) { ((Component)this).gameObject.AddComponent(); } } public void EnsureContainer() { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown if (!((Object)(object)_container != (Object)null) || _container.GetInventory() == null) { _container = ((Component)this).GetComponent() ?? ((Component)this).gameObject.AddComponent(); _container.m_name = InventoryTitle(); _container.m_width = 4; _container.m_height = 2; _container.m_checkGuardStone = false; _container.m_autoDestroyEmpty = false; ZNetView component = ((Component)this).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { Inventory val = new Inventory(_container.m_name, (Sprite)null, 4, 2); val.m_onChanged = _container.OnContainerChanged; _container.m_inventory = val; _container.Load(); } } } public string InventoryTitle() { return (((Object)(object)_companion != (Object)null) ? _companion.DisplayName : "Dvergr") + "'s pack"; } public void RefreshTitle() { if ((Object)(object)_container != (Object)null) { _container.m_name = InventoryTitle(); } } public float TotalWeight() { if (Inventory == null) { return 0f; } return Inventory.GetTotalWeight(); } public bool Holds(ItemData item) { if (item?.m_shared == null || Inventory == null) { return false; } foreach (ItemData allItem in Inventory.GetAllItems()) { if (allItem?.m_shared != null && allItem.m_shared.m_name == item.m_shared.m_name) { return true; } } return false; } public ItemData FindItem(Func match) { if (Inventory == null) { return null; } foreach (ItemData allItem in Inventory.GetAllItems()) { if (allItem != null && match(allItem)) { return allItem; } } return null; } public List Items() { if (Inventory == null) { return new List(); } return Inventory.GetAllItems(); } public bool ConsumeOne(ItemData item) { if (item == null || Inventory == null) { return false; } return Inventory.RemoveItem(item, 1); } } [RequireComponent(typeof(DvergrCompanion))] [RequireComponent(typeof(CompanionInventory))] public class CompanionInventoryAI : MonoBehaviour { private const float TickInterval = 1f; private const float PickupRange = 8f; private const float MeadHealBelowFraction = 0.35f; private const float MeadHealUntilFraction = 0.9f; private DvergrCompanion _companion; private CompanionInventory _inv; private Character _character; private MonsterAI _ai; private ZNetView _znv; private float _tick; private float _foodPeak; private float _foodApplied; private float _foodDuration; private float _foodElapsed; private bool _healing; private void Awake() { _companion = ((Component)this).GetComponent(); _inv = ((Component)this).GetComponent(); _character = ((Component)this).GetComponent(); _ai = ((Component)this).GetComponent(); _znv = ((Component)this).GetComponent(); } private void Update() { if ((Object)(object)_character == (Object)null || _character.IsDead()) { return; } if (!((Object)(object)_znv == (Object)null) && _znv.IsValid() && !_znv.IsOwner()) { if ((Object)(object)_companion != (Object)null && (Object)(object)_inv != (Object)null) { _companion.IsEncumbered = _inv.IsOverCapacity; } return; } TickFoodBuff(Time.deltaTime); bool isOverCapacity = _inv.IsOverCapacity; if (_companion.DuelMode) { _companion.IsEncumbered = isOverCapacity; } else { _companion.ApplyEncumbrance(isOverCapacity); } _tick += Time.deltaTime; if (!(_tick < 1f)) { _tick = 0f; TickConsumption(); if (!isOverCapacity && (!((Object)(object)_ai != (Object)null) || (!((BaseAI)_ai).IsAlerted() && !((BaseAI)_ai).HaveTarget())) && !_companion.ChoreActive && !_companion.DuelMode && !_inv.IsEmpty) { TickPickup(); } } } private void TickPickup() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) Inventory inventory = _inv.Inventory; if (inventory == null) { return; } Collider[] array = Physics.OverlapSphere(((Component)this).transform.position, 8f); foreach (Collider val in array) { ItemDrop val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponentInParent() : null); if ((Object)(object)val2 == (Object)null || !val2.CanPickup(false)) { continue; } ZNetView component = ((Component)val2).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid()) { continue; } ItemData itemData = val2.m_itemData; if (itemData?.m_shared != null && _inv.Holds(itemData)) { if (inventory.CanAddItem(itemData, itemData.m_stack)) { inventory.AddItem(itemData.Clone()); component.ClaimOwnership(); ZNetScene.instance.Destroy(((Component)val2).gameObject); } break; } } } private void TickConsumption() { if (_inv.Inventory == null) { return; } ItemData val = _inv.FindItem((ItemData it) => CompanionConsumables.Classify(it) == ConsumableKind.ResistMead && !HasEffect(CompanionConsumables.ConsumeEffect(it))); if (val != null) { DrinkStatusMead(val); return; } float healthPercentage = _character.GetHealthPercentage(); if (healthPercentage < 0.35f) { _healing = true; } else if (healthPercentage > 0.9f) { _healing = false; } if (_healing) { ItemData val2 = _inv.FindItem((ItemData it) => CompanionConsumables.Classify(it) == ConsumableKind.HealthMead); if (val2 != null) { float num = CompanionConsumables.HealAmount(val2); if (_inv.ConsumeOne(val2)) { _character.Heal(num, true); PlayConsumeEffects(val2); } return; } _healing = false; } if (_foodApplied <= 0f && _foodElapsed >= _foodDuration) { ItemData val3 = _inv.FindItem((ItemData it) => CompanionConsumables.Classify(it) == ConsumableKind.Food); if (val3 != null) { EatFood(val3); } } } private void DrinkStatusMead(ItemData mead) { StatusEffect val = CompanionConsumables.ConsumeEffect(mead); if (!((Object)(object)val == (Object)null) && _inv.ConsumeOne(mead)) { _character.GetSEMan().AddStatusEffect(val, true, 0, 0f); PlayConsumeEffects(mead); } } private bool HasEffect(StatusEffect se) { if ((Object)(object)se == (Object)null || (Object)(object)_character == (Object)null) { return false; } return _character.GetSEMan().HaveStatusEffect(se.NameHash()); } private void PlayConsumeEffects(ItemData item) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) StatusEffect val = CompanionConsumables.ConsumeEffect(item); EffectList val2 = (((Object)(object)val != (Object)null) ? val.m_startEffects : null); if (val2 != null) { val2.Create(((Component)this).transform.position, ((Component)this).transform.rotation, ((Component)this).transform, 1f, -1); } } private void EatFood(ItemData food) { if (_inv.ConsumeOne(food)) { _foodPeak = food.m_shared.m_food; _foodDuration = Mathf.Max(1f, food.m_shared.m_foodBurnTime); _foodElapsed = 0f; ApplyFoodBonus(_foodPeak); _companion.IsFed = true; _companion.FedIcon = ((food.m_shared.m_icons != null && food.m_shared.m_icons.Length != 0) ? food.m_shared.m_icons[0] : null); PlayConsumeEffects(food); } } private void TickFoodBuff(float dt) { if (!(_foodApplied <= 0f) || !(_foodElapsed >= _foodDuration)) { _foodElapsed += dt; if (_foodElapsed >= _foodDuration) { ExpireFood(); return; } float num = _foodElapsed / _foodDuration; float num2 = ((num <= 0.5f) ? 1f : Mathf.Clamp01(1f - (num - 0.5f) / 0.5f)); ApplyFoodBonus(_foodPeak * num2); } } private void ApplyFoodBonus(float target) { if ((Object)(object)_character == (Object)null) { return; } float num = target - _foodApplied; if (!(Mathf.Abs(num) < 0.01f)) { float num2 = Mathf.Max(1f, _character.GetMaxHealth() + num); _character.SetMaxHealth(num2); if (num > 0f) { _character.Heal(num, false); } else if (_character.GetHealth() > num2) { _character.SetHealth(num2); } _foodApplied = target; } } private void ExpireFood() { if (_foodApplied > 0f && (Object)(object)_character != (Object)null) { float num = Mathf.Max(1f, _character.GetMaxHealth() - _foodApplied); _character.SetMaxHealth(num); if (_character.GetHealth() > num) { _character.SetHealth(num); } } _foodApplied = 0f; _foodPeak = 0f; _foodElapsed = 0f; _foodDuration = 0f; if ((Object)(object)_companion != (Object)null) { _companion.IsFed = false; _companion.FedIcon = null; } } } public class CompanionInventoryGui : MonoBehaviour { private static CompanionInventoryGui _instance; private DvergrCompanion _openCompanion; private CompanionInventory _openInventory; private Character _openCharacter; private TMP_InputField _nameField; private TMP_Text _hpText; private bool _cloneFailed; public static bool IsTyping { get { if ((Object)(object)_instance != (Object)null && (Object)(object)_instance._nameField != (Object)null) { return _instance._nameField.isFocused; } return false; } } public static void Open(DvergrCompanion companion, CompanionInventory inventory) { if (!((Object)(object)_instance == (Object)null) && !((Object)(object)companion == (Object)null) && !((Object)(object)inventory == (Object)null) && !((Object)(object)InventoryGui.instance == (Object)null)) { inventory.EnsureContainer(); inventory.RefreshTitle(); if (!((Object)(object)inventory.Container == (Object)null)) { InventoryGui.instance.Show(inventory.Container, 0); _instance._openCompanion = companion; _instance._openInventory = inventory; _instance._openCharacter = ((Component)companion).GetComponent(); _instance.BuildWidgets(); _instance.ShowWidgets(show: true); _instance.Refresh(); } } } private void Awake() { _instance = this; } private void Update() { if (!((Object)(object)_openCompanion == (Object)null)) { InventoryGui instance = InventoryGui.instance; if (!((Object)(object)instance != (Object)null) || !InventoryGui.IsVisible() || !((Object)(object)instance.m_currentContainer != (Object)null) || !((Object)(object)_openInventory != (Object)null) || !((Object)(object)instance.m_currentContainer == (Object)(object)_openInventory.Container) || !((Object)(object)_openCharacter != (Object)null) || _openCharacter.IsDead()) { ShowWidgets(show: false); _openCompanion = null; _openInventory = null; _openCharacter = null; } else { Refresh(); } } } private void Refresh() { if ((Object)(object)_nameField != (Object)null && !_nameField.isFocused) { _nameField.SetTextWithoutNotify(_openCompanion.DisplayName); } if ((Object)(object)_hpText != (Object)null && (Object)(object)_openCharacter != (Object)null) { float num = Mathf.Ceil(_openCharacter.GetHealth()); float num2 = Mathf.Ceil(_openCharacter.GetMaxHealth()); string arg = (_openCompanion.IsFed ? "#FFD24A" : "#FFFFFF"); _hpText.text = $"HP {num:0} / {num2:0}"; } } private void BuildWidgets() { if (_cloneFailed) { return; } RectTransform val = (((Object)(object)InventoryGui.instance != (Object)null) ? InventoryGui.instance.m_container : null); if ((Object)(object)val == (Object)null) { return; } try { EnsureNameField(val); EnsureHpText(val); } catch (Exception ex) { _cloneFailed = true; Plugin.Log.LogWarning((object)("[inventory] Could not build companion panel widgets: " + ex.Message)); } } private void EnsureNameField(RectTransform panel) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_nameField != (Object)null) { return; } GuiInputField val = (((Object)(object)TextInput.instance != (Object)null) ? TextInput.instance.m_inputField : null); if (!((Object)(object)val == (Object)null)) { GameObject val2 = Object.Instantiate(((Component)val).gameObject, (Transform)(object)panel); ((Object)val2).name = "CompanionNameField"; RectTransform component = val2.GetComponent(); if ((Object)(object)component != (Object)null) { Park(component, new Vector2(18f, -44f), new Vector2(210f, 28f)); } _nameField = val2.GetComponent(); if ((Object)(object)_nameField == (Object)null) { Object.Destroy((Object)(object)val2); return; } ((UnityEventBase)_nameField.onEndEdit).RemoveAllListeners(); ((UnityEvent)(object)_nameField.onEndEdit).AddListener((UnityAction)OnNameSubmitted); _nameField.characterLimit = 24; } } private void EnsureHpText(RectTransform panel) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_hpText != (Object)null) { return; } TMP_Text val = (((Object)(object)InventoryGui.instance != (Object)null) ? InventoryGui.instance.m_containerWeight : null); if (!((Object)(object)val == (Object)null)) { GameObject val2 = Object.Instantiate(((Component)val).gameObject, (Transform)(object)panel); ((Object)val2).name = "CompanionHpText"; RectTransform component = val2.GetComponent(); if ((Object)(object)component != (Object)null) { Park(component, new Vector2(236f, -44f), new Vector2(210f, 28f)); } _hpText = val2.GetComponent(); if ((Object)(object)_hpText != (Object)null) { _hpText.alignment = (TextAlignmentOptions)513; _hpText.enableAutoSizing = false; _hpText.fontSize = 18f; _hpText.richText = true; } } } private static void Park(RectTransform rt, Vector2 anchoredPos, Vector2 size) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) rt.anchorMin = new Vector2(0f, 1f); rt.anchorMax = new Vector2(0f, 1f); rt.pivot = new Vector2(0f, 1f); rt.anchoredPosition = anchoredPos; rt.sizeDelta = size; } private void ShowWidgets(bool show) { if ((Object)(object)_nameField != (Object)null) { ((Component)_nameField).gameObject.SetActive(show); } if ((Object)(object)_hpText != (Object)null) { ((Component)_hpText).gameObject.SetActive(show); } } private void OnNameSubmitted(string text) { if (!((Object)(object)_openCompanion == (Object)null) && !string.IsNullOrWhiteSpace(text) && !(text == _openCompanion.DisplayName)) { _openCompanion.SetName(text); if ((Object)(object)_openInventory != (Object)null) { _openInventory.RefreshTitle(); } } } } public class CompanionMapPins : MonoBehaviour { private const float UpdateInterval = 0.25f; private readonly Dictionary _pins = new Dictionary(); private readonly HashSet _seen = new HashSet(); private readonly List _stale = new List(); private Minimap _boundMap; private float _timer; private void Update() { //IL_00a8: 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_0176: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) Minimap instance = Minimap.instance; Player localPlayer = Player.m_localPlayer; if ((Object)(object)instance == (Object)null || (Object)(object)localPlayer == (Object)null || !Plugin.ShowMapPins.Value) { if (_pins.Count > 0) { if ((Object)(object)instance == (Object)null) { _pins.Clear(); } else { RemoveAll(instance); } } _boundMap = instance; return; } if ((Object)(object)_boundMap != (Object)(object)instance) { _pins.Clear(); _boundMap = instance; } _timer += Time.deltaTime; if (_timer < 0.25f) { return; } _timer = 0f; Color color = PinColor(); float scale = Mathf.Clamp(Plugin.CompanionPinScale.Value, 0.2f, 2f); _seen.Clear(); foreach (DvergrCompanion item in DvergrCompanion.All) { if ((Object)(object)item == (Object)null || item.OwnerId == 0L || !item.IsOwner(localPlayer)) { continue; } _seen.Add(item); if (_pins.TryGetValue(item, out var value) && value != null) { value.m_pos = ((Component)item).transform.position; if (value.m_name != item.DisplayName) { value.m_name = item.DisplayName; } } else { value = instance.AddPin(((Component)item).transform.position, (PinType)10, item.DisplayName, false, false, 0L, default(PlatformUserID)); _pins[item] = value; } StylePin(value, color, scale); } _stale.Clear(); foreach (KeyValuePair pin in _pins) { if (!_seen.Contains(pin.Key)) { _stale.Add(pin.Key); } } foreach (DvergrCompanion item2 in _stale) { if (_pins.TryGetValue(item2, out var value2) && value2 != null) { instance.RemovePin(value2); } _pins.Remove(item2); } } private void RemoveAll(Minimap map) { foreach (KeyValuePair pin in _pins) { if (pin.Value != null) { map.RemovePin(pin.Value); } } _pins.Clear(); } private static void StylePin(PinData pin, Color color, float scale) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) Image val = pin?.m_iconElement; if (!((Object)(object)val == (Object)null)) { ((Graphic)val).color = color; ((Transform)((Graphic)val).rectTransform).localScale = new Vector3(scale, scale, 1f); } } private static Color PinColor() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) string text = Plugin.CompanionPinColor.Value ?? string.Empty; if (!text.StartsWith("#")) { text = "#" + text; } Color result = default(Color); if (!ColorUtility.TryParseHtmlString(text, ref result)) { return new Color(1f, 0.72f, 0.3f); } return result; } public static void AddDeathMarker(string companionName, Vector3 pos) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (Plugin.ShowDeathMarker != null && Plugin.ShowDeathMarker.Value) { Minimap instance = Minimap.instance; if (!((Object)(object)instance == (Object)null)) { string text = (string.IsNullOrEmpty(companionName) ? "Fallen companion" : companionName); instance.AddPin(pos, (PinType)4, text, true, false, 0L, default(PlatformUserID)); Plugin.Log.LogInfo((object)("[map] death marker placed for '" + text + "'.")); } } } } [HarmonyPatch(typeof(TeleportWorld), "Teleport")] public static class CompanionPortalPatch { public static void Postfix(Player player, bool __runOriginal) { if (__runOriginal && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && ((Character)player).IsTeleporting()) { CompanionTeleport.FollowOwnerThroughPortal(player); } } } [HarmonyPatch(typeof(TeleportWorld), "Teleport")] public static class CompanionPortalBlockPatch { private static float _lastMsg = -999f; public static bool Prefix(TeleportWorld __instance, Player player) { if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return true; } if ((Object)(object)__instance == (Object)null || ((Object)__instance).name.IndexOf("portal_wood", StringComparison.OrdinalIgnoreCase) < 0) { return true; } if (CompanionTeleport.TryFindPortalBlocker(player, out var companionName, out var itemName)) { if (Time.time - _lastMsg > 2.5f) { _lastMsg = Time.time; ((Character)player).Message((MessageType)2, companionName + " is carrying " + itemName + " — you can't take it through the portal.", 0, (Sprite)null); } return false; } return true; } } internal static class CompanionTeleport { private const float GatherRange = 60f; public static bool TryFindPortalBlocker(Player owner, out string companionName, out string itemName) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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_00b7: Unknown result type (might be due to invalid IL or missing references) companionName = null; itemName = null; if ((Object)(object)owner == (Object)null) { return false; } long playerID = owner.GetPlayerID(); Vector3 position = ((Component)owner).transform.position; foreach (DvergrCompanion item in DvergrCompanion.All) { if ((Object)(object)item == (Object)null || item.Stance != CompanionStance.Follow || item.OwnerId == 0L || item.OwnerId != playerID || item.ChoreActive || item.DuelMode || item.IsFeral) { continue; } Character component = ((Component)item).GetComponent(); if ((Object)(object)component == (Object)null || component.IsDead() || Vector3.Distance(((Component)component).transform.position, position) > 60f) { continue; } CompanionInventory component2 = ((Component)item).GetComponent(); Inventory val = (((Object)(object)component2 != (Object)null) ? component2.Inventory : null); if (val == null) { continue; } foreach (ItemData allItem in val.GetAllItems()) { if (allItem?.m_shared != null && !allItem.m_shared.m_teleportable) { companionName = item.DisplayName; itemName = ((Localization.instance != null) ? Localization.instance.Localize(allItem.m_shared.m_name) : allItem.m_shared.m_name); return true; } } } return false; } public static void FollowOwnerThroughPortal(Player owner) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) Vector3 teleportTargetPos = owner.m_teleportTargetPos; Quaternion teleportTargetRot = owner.m_teleportTargetRot; Vector3 teleportFromPos = owner.m_teleportFromPos; long playerID = owner.GetPlayerID(); int num = 0; foreach (DvergrCompanion item in DvergrCompanion.All) { if (!((Object)(object)item == (Object)null) && item.Stance == CompanionStance.Follow && item.OwnerId != 0L && item.OwnerId == playerID && !item.ChoreActive && !item.DuelMode && !item.IsFeral) { Character component = ((Component)item).GetComponent(); if (!((Object)(object)component == (Object)null) && !component.IsDead() && !(Vector3.Distance(((Component)component).transform.position, teleportFromPos) > 60f)) { TeleportCompanion(component, teleportTargetPos + SpreadOffset(num), teleportTargetRot); num++; } } } if (num > 0) { Plugin.Log.LogInfo((object)$"[portal] Brought {num} companion(s) through the portal for {owner.GetPlayerName()}."); } } private static void TeleportCompanion(Character ch, Vector3 pos, Quaternion rot) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_0024: Unknown result type (might be due to invalid IL or missing references) ZNetView component = ((Component)ch).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { component.ClaimOwnership(); component.GetZDO().SetPosition(pos); } ((Component)ch).transform.SetPositionAndRotation(pos, rot); if ((Object)(object)ch.m_body != (Object)null) { ch.m_body.position = pos; ch.m_body.rotation = rot; } } private static Vector3 SpreadOffset(int index) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) float num = 2.399963f * (float)index; float num2 = 1.2f + 0.5f * (float)index; return new Vector3(Mathf.Cos(num) * num2, 0f, Mathf.Sin(num) * num2); } } [HarmonyPatch(typeof(MonsterAI), "Start")] public static class CompanionRestorePatch { public static void Postfix(MonsterAI __instance) { CommunionService.RestoreCompanion(((Component)__instance).GetComponent()); } } public class ContainerPanelPositioner : MonoBehaviour { private const float DefaultRowsBelow = 2f; private const float FallbackRowHeight = 68f; private const float ScreenMargin = 48f; private const string AutoValue = "auto"; private RectTransform _panel; private Vector2 _basePos; private Vector2 _offset; private bool _applied; private bool _dragging; private string _lastConfigValue; private bool _parseWarned; private ContainerDragSurface _surface; private static int _biomeLords = -1; private static bool _decisionLogged; public static bool Enabled { get { bool flag = Plugin.MoveContainerPanel == null || Plugin.MoveContainerPanel.Value; bool flag2 = BiomeLordsLoaded(); bool flag3 = flag && !flag2; if (!_decisionLogged) { _decisionLogged = true; Plugin.Log.LogInfo((object)("[inventory] container-panel positioning: " + (flag3 ? "ON" : "OFF") + " " + $"(config={flag}, BiomeLords={flag2}). BiomeLords ⇒ we leave the chest UI position to it.")); } return flag3; } } public static bool BiomeLordsLoaded() { if (_biomeLords < 0) { _biomeLords = 0; foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { string obj = pluginInfo.Key ?? string.Empty; PluginInfo value = pluginInfo.Value; object obj2; if (value == null) { obj2 = null; } else { BepInPlugin metadata = value.Metadata; obj2 = ((metadata != null) ? metadata.Name : null); } if (obj2 == null) { obj2 = string.Empty; } string text = (string)obj2; if (obj.IndexOf("biomelord", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("biomelord", StringComparison.OrdinalIgnoreCase) >= 0) { _biomeLords = 1; break; } } } return _biomeLords == 1; } private void Update() { if (!Enabled) { Release(); return; } InventoryGui instance = InventoryGui.instance; RectTransform val = (((Object)(object)instance != (Object)null) ? instance.m_container : null); if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy) { Track(val); EnsureSurface(); SyncFromConfig(instance); if (!_dragging) { Apply(); } } } private void Track(RectTransform container) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_panel == (Object)(object)container)) { _panel = container; _basePos = container.anchoredPosition; _applied = false; _dragging = false; _surface = null; _lastConfigValue = null; } } private void SyncFromConfig(InventoryGui gui) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) string text = ((Plugin.ContainerPanelOffset != null) ? Plugin.ContainerPanelOffset.Value : "auto"); if (!(text == _lastConfigValue)) { _lastConfigValue = text; _offset = ParseOffset(text, gui); } } private Vector2 ParseOffset(string raw, InventoryGui gui) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) raw = (raw ?? string.Empty).Trim(); if (raw.Length == 0 || raw.Equals("auto", StringComparison.OrdinalIgnoreCase)) { return DefaultOffset(gui); } string[] array = raw.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 2 && float.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { return new Vector2(result, result2); } if (!_parseWarned) { _parseWarned = true; Plugin.Log.LogWarning((object)("[inventory] ContainerPanelOffset '" + raw + "' isn't 'auto' or 'x,y' — using the default position.")); } return DefaultOffset(gui); } private static Vector2 DefaultOffset(InventoryGui gui) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) float num = (((Object)(object)gui != (Object)null && (Object)(object)gui.m_playerGrid != (Object)null && gui.m_playerGrid.m_elementSpace > 0f) ? gui.m_playerGrid.m_elementSpace : 68f); return new Vector2(0f, -2f * num); } private void Apply() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_panel == (Object)null)) { _panel.anchoredPosition = _basePos + _offset; _applied = true; ClampToScreen(); } } private void ClampToScreen() { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_panel == (Object)null)) { Vector3[] array = (Vector3[])(object)new Vector3[4]; _panel.GetWorldCorners(array); float x = array[0].x; float x2 = array[2].x; float y = array[0].y; float y2 = array[1].y; float num = 0f; float num2 = 0f; if (x2 < 48f) { num = 48f - x2; } else if (x > (float)Screen.width - 48f) { num = (float)Screen.width - 48f - x; } if (y2 < 48f) { num2 = 48f - y2; } else if (y > (float)Screen.height - 48f) { num2 = (float)Screen.height - 48f - y; } if (num != 0f || num2 != 0f) { Vector2 val = new Vector2(num, num2) / CanvasScale(); _offset += val; _panel.anchoredPosition = _basePos + _offset; } } } private float CanvasScale() { Canvas val = (((Object)(object)_panel != (Object)null) ? ((Component)_panel).GetComponentInParent() : null); float num = (((Object)(object)val != (Object)null) ? val.scaleFactor : 1f); if (!(num > 0.0001f)) { return 1f; } return num; } private void Release() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_panel != (Object)null && _applied) { _panel.anchoredPosition = _basePos; _applied = false; } if ((Object)(object)_surface != (Object)null) { Object.Destroy((Object)(object)((Component)_surface).gameObject); } _surface = null; _dragging = false; } private void EnsureSurface() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_surface != (Object)null) && !((Object)(object)_panel == (Object)null)) { GameObject val = new GameObject("LSII_ContainerDragSurface", new Type[1] { typeof(RectTransform) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent((Transform)(object)_panel, false); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; Image val3 = val.AddComponent(); ((Graphic)val3).color = Color.clear; ((Graphic)val3).raycastTarget = true; ((Transform)val2).SetAsFirstSibling(); _surface = val.AddComponent(); _surface.Bind(this, val3); } } internal void BeginDrag() { _dragging = true; } internal void DragBy(Vector2 screenDelta) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_panel == (Object)null)) { _offset += screenDelta / CanvasScale(); _panel.anchoredPosition = _basePos + _offset; _applied = true; ClampToScreen(); } } internal void EndDrag() { _dragging = false; if (Plugin.ContainerPanelOffset != null) { string value = (_lastConfigValue = string.Format(CultureInfo.InvariantCulture, "{0:0.##},{1:0.##}", _offset.x, _offset.y)); Plugin.ContainerPanelOffset.Value = value; } } } public class ContainerDragSurface : MonoBehaviour, IBeginDragHandler, IEventSystemHandler, IDragHandler, IEndDragHandler, IPointerEnterHandler, IPointerExitHandler { private static readonly Color Hover = new Color(1f, 1f, 1f, 0.05f); private ContainerPanelPositioner _owner; private Image _image; internal void Bind(ContainerPanelPositioner owner, Image image) { _owner = owner; _image = image; } public void OnBeginDrag(PointerEventData eventData) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if ((int)eventData.button == 0) { _owner?.BeginDrag(); } } public void OnDrag(PointerEventData eventData) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) if ((int)eventData.button == 0) { _owner?.DragBy(eventData.delta); } } public void OnEndDrag(PointerEventData eventData) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if ((int)eventData.button == 0) { _owner?.EndDrag(); } } public void OnPointerEnter(PointerEventData eventData) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_image != (Object)null) { ((Graphic)_image).color = Hover; } } public void OnPointerExit(PointerEventData eventData) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_image != (Object)null) { ((Graphic)_image).color = Color.clear; } } } public enum DvergrCaste { Rogue, FireMage, IceMage, SupportMage } public static class DvergrCasteExtensions { public static string Display(this DvergrCaste caste) { return caste switch { DvergrCaste.FireMage => "Fire Mage", DvergrCaste.IceMage => "Ice Mage", DvergrCaste.SupportMage => "Support Mage", _ => "Rogue", }; } } public class DvergrCompanion : MonoBehaviour, TextReceiver { public enum DuelExitReason { Defeated, NoOpponents, OwnerGone, OwnerStopped } public const string ZdoKeyRecruited = "DE_Recruited"; public const string ZdoKeyCaste = "DE_Caste"; public const string ZdoKeyLevel = "DE_Level"; public const string ZdoKeyXp = "DE_Xp"; public const string ZdoKeyOwner = "DE_Owner"; public const string ZdoKeyOwnerName = "DE_OwnerName"; public const string ZdoKeyName = "DE_Name"; public const string ZdoKeyCompanionId = "DE_CompanionId"; public const string ZdoKeyDuel = "DE_Duel"; public const string ZdoKeyPartyDuel = "DE_PartyDuel"; public const float SubdueFloorHealthFraction = 0.05f; private const float DuelWinXpBonus = 50f; private const float DuelDetectRange = 30f; private const float OwnerVisionRange = 40f; private const float DuelWaitTimeout = 60f; private const float DuelScanInterval = 1f; private const float StandbyAlertRange = 0f; private const float HostileDuration = 30f; public const int MaxLevel = 10; private static readonly int[] XpToNextByLevel = new int[9] { 100, 250, 450, 700, 1000, 1400, 1900, 2500, 3200 }; private const float GuardAlertRangeMultiplier = 2.5f; private const float SpeedBonusPerLevel = 0.03f; private const float HealthBonusPerLevel = 0.04f; private Character _character; private ZNetView _znv; private MonsterAI _ai; private float _baseAlertRange; private bool _baseAlertRangeCaptured; private float _baseRunSpeed; private float _baseSpeed; private float _baseWalkSpeed; private bool _baseSpeedsCaptured; private string _customName; private string _companionId; private long _ownerId; private string _ownerName; private readonly Dictionary _hostileUntil = new Dictionary(); private float _pruneTimer; private bool _duelEngaged; private float _duelWaitTimer; private float _duelScanTimer; private bool _duelResolved; public string DuelOpponentId; public long DuelOpponentOwner; private bool _partyEngaged; private float _partyWaitTimer; private float _partyScanTimer; private int _partyPeakEnemies; private readonly Dictionary _partyAllySnap = new Dictionary(); private readonly Dictionary _partyEnemySnap = new Dictionary(); private long _partyOpponentOwner; private string _partyOpponentOwnerName; private static readonly Dictionary s_partyReportLatch = new Dictionary(); private const float PartyReportWindow = 15f; private bool _feral; private bool _duelModeLocal; private bool _partyDuelLocal; private static readonly HashSet s_all = new HashSet(); private bool _encumbranceActive; public DvergrCaste Caste { get; private set; } public int Level { get; private set; } = 1; public float Xp { get; private set; } public CompanionStance Stance { get; private set; } public float XpPercentToNextLevel { get { if (!IsMaxLevel) { return Mathf.Clamp(Xp / XpRequiredForNextLevel() * 100f, 0f, 100f); } return 100f; } } public bool IsMaxLevel => Level >= 10; public bool IsDuelResolved => _duelResolved; public long OwnerId => _ownerId; public bool DuelMode { get { if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { return _znv.GetZDO().GetBool("DE_Duel", false); } return _duelModeLocal; } private set { _duelModeLocal = value; if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { _znv.ClaimOwnership(); _znv.GetZDO().Set("DE_Duel", value); } } } public bool PartyDuelMode { get { if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { return _znv.GetZDO().GetBool("DE_PartyDuel", false); } return _partyDuelLocal; } private set { _partyDuelLocal = value; if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { _znv.ClaimOwnership(); _znv.GetZDO().Set("DE_PartyDuel", value); } } } public bool InAnyDuelMode { get { if (!DuelMode) { return PartyDuelMode; } return true; } } public string OwnerName { get { if (!string.IsNullOrEmpty(_ownerName)) { return _ownerName; } if (_ownerId != 0L) { Player player = Player.GetPlayer(_ownerId); if ((Object)(object)player != (Object)null) { return player.GetPlayerName(); } } return null; } } public static IEnumerable All => s_all; public bool ChoreActive { get; set; } public bool IsEncumbered { get; set; } public bool IsFed { get; set; } public Sprite FedIcon { get; set; } public bool HasCustomName => !string.IsNullOrEmpty(_customName); public string DisplayName { get { if (!HasCustomName) { return DefaultName(); } return _customName; } } public string CompanionId { get { if (!string.IsNullOrEmpty(_companionId)) { return _companionId; } if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { string text = _znv.GetZDO().GetString("DE_CompanionId", (string)null); if (!string.IsNullOrEmpty(text)) { _companionId = text; } } return _companionId; } } public bool IsFeral => _feral; private float XpRequiredForNextLevel() { int num = Level - 1; if (num < 0 || num >= XpToNextByLevel.Length) { return float.MaxValue; } return XpToNextByLevel[num]; } public bool MatchesDuelAssignment(DvergrCompanion other) { if ((Object)(object)other == (Object)null) { return false; } if (!string.IsNullOrEmpty(DuelOpponentId)) { return other.CompanionId == DuelOpponentId; } if (DuelOpponentOwner != 0L) { return other.OwnerId == DuelOpponentOwner; } return true; } public void DespawnToTotem() { ZNetView component = ((Component)this).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { component.ClaimOwnership(); ZNetScene.instance.Destroy(((Component)this).gameObject); } else { Object.Destroy((Object)(object)((Component)this).gameObject); } } private void OnEnable() { s_all.Add(this); } private void OnDisable() { s_all.Remove(this); } public static bool PlayerHasCompanion(Player player) { if ((Object)(object)player == (Object)null) { return false; } foreach (DvergrCompanion item in s_all) { if ((Object)(object)item != (Object)null && item.IsOwner(player)) { return true; } } return false; } private void Awake() { _character = ((Component)this).GetComponent(); _znv = ((Component)this).GetComponent(); _ai = ((Component)this).GetComponent(); if ((Object)(object)_znv != (Object)null && _znv.IsValid() && _znv.GetZDO().GetBool("DE_Recruited", false)) { Caste = (DvergrCaste)_znv.GetZDO().GetInt("DE_Caste", 0); Level = _znv.GetZDO().GetInt("DE_Level", 1); Xp = _znv.GetZDO().GetFloat("DE_Xp", 0f); _ownerId = _znv.GetZDO().GetLong("DE_Owner", 0L); _ownerName = _znv.GetZDO().GetString("DE_OwnerName", (string)null); _customName = _znv.GetZDO().GetString("DE_Name", (string)null); _companionId = _znv.GetZDO().GetString("DE_CompanionId", (string)null); ApplyLevelToCharacter(); if (_znv.IsOwner()) { _znv.GetZDO().Set("DE_Duel", false); _znv.GetZDO().Set("DE_PartyDuel", false); } } if ((Object)(object)_ai != (Object)null) { _baseAlertRange = _ai.m_alertRange; _baseAlertRangeCaptured = true; } ApplyMovementTweaks(); } private void ApplyMovementTweaks() { if ((Object)(object)_ai != (Object)null && ((BaseAI)_ai).m_jumpInterval <= 0f) { ((BaseAI)_ai).m_jumpInterval = 8f; } if ((Object)(object)_character != (Object)null) { Character character = _character; character.m_jumpForce *= 0.5f; } } public void SetPassive(bool passive) { if (!((Object)(object)_ai == (Object)null)) { _ai.m_alertRange = (passive ? 0f : (_baseAlertRangeCaptured ? _baseAlertRange : _ai.m_alertRange)); } } public void ApplyEncumbrance(bool encumbered) { IsEncumbered = encumbered; if ((Object)(object)_ai == (Object)null) { return; } if (encumbered) { if (!_baseAlertRangeCaptured) { _baseAlertRange = _ai.m_alertRange; _baseAlertRangeCaptured = true; } _ai.SetTarget((Character)null); ((BaseAI)_ai).SetAlerted(false); _ai.m_alertRange = 0f; _encumbranceActive = true; } else if (_encumbranceActive) { _encumbranceActive = false; if (ChoreActive) { SetPassive(passive: true); } else { RestoreAlertRangeForStance(); } } } private string DefaultName() { if ((Object)(object)_character != (Object)null && !string.IsNullOrEmpty(_character.m_name) && Localization.instance != null) { return Localization.instance.Localize(_character.m_name); } return "Dvergr"; } public void SetName(string name) { _customName = (string.IsNullOrWhiteSpace(name) ? null : name.Trim()); if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { _znv.GetZDO().Set("DE_Name", _customName ?? string.Empty); } } public string GetText() { return DisplayName; } public void SetText(string text) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) SetName(text); if ((Object)(object)Chat.instance != (Object)null) { Chat.instance.SetNpcText(((Component)this).gameObject, Vector3.up * 2.2f, 20f, 4f, string.Empty, "I'll answer to " + DisplayName + " now.", false); } } public string EnsureCompanionId() { string companionId = CompanionId; if (!string.IsNullOrEmpty(companionId)) { return companionId; } _companionId = Guid.NewGuid().ToString("N"); if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { _znv.GetZDO().Set("DE_CompanionId", _companionId); } return _companionId; } public void SetCompanionId(string id) { if (!string.IsNullOrEmpty(id)) { _companionId = id; if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { _znv.GetZDO().Set("DE_CompanionId", id); } } } public void SetOwner(long ownerId) { _ownerId = ownerId; if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { _znv.GetZDO().Set("DE_Owner", ownerId); } } public void SetOwnerName(string ownerName) { _ownerName = (string.IsNullOrWhiteSpace(ownerName) ? null : ownerName); if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { _znv.GetZDO().Set("DE_OwnerName", _ownerName ?? string.Empty); } } public bool IsOwner(Player player) { if ((Object)(object)player == (Object)null) { return false; } if (_ownerId == 0L) { return true; } return player.GetPlayerID() == _ownerId; } public Player OwnerPlayer() { if (_ownerId == 0L) { return Player.m_localPlayer; } return Player.GetPlayer(_ownerId); } public bool IsOwnerNear(float range) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) Player val = OwnerPlayer(); if ((Object)(object)val != (Object)null) { return Vector3.Distance(((Component)val).transform.position, ((Component)this).transform.position) <= range; } return false; } public bool IsHostileTo(Character other) { if ((Object)(object)other == (Object)null) { return false; } Player val = (Player)(object)((other is Player) ? other : null); if (val != null) { if (_feral) { return true; } if (IsOwner(val)) { return false; } if (Stance == CompanionStance.Guard && _ownerId != 0L) { return true; } } if (_hostileUntil.TryGetValue(other, out var value)) { return Time.time < value; } return false; } public void GoFeral(Character attacker) { if (_feral) { return; } _feral = true; if (DuelMode) { ExitDuelMode(DuelExitReason.OwnerStopped); } if (PartyDuelMode) { ExitPartyDuelMode(DuelExitReason.OwnerStopped); } if ((Object)(object)_ai != (Object)null) { if (!_baseAlertRangeCaptured) { _baseAlertRange = _ai.m_alertRange; _baseAlertRangeCaptured = true; } _ai.m_alertRange = _baseAlertRange * 2.5f; if ((Object)(object)attacker != (Object)null) { _ai.SetTarget(attacker); } ((BaseAI)_ai).SetAlerted(true); } Announce(DisplayName + " turns on you, eyes wild with the old corruption!"); } public void MarkHostile(Character player) { if (!((Object)(object)player == (Object)null)) { _hostileUntil[player] = Time.time + 30f; } } public void Retaliate(Character attacker) { MarkHostile(attacker); if ((Object)(object)_ai != (Object)null) { _ai.SetTarget(attacker); ((BaseAI)_ai).SetAlerted(true); } } public void MarkOwnersCompanionsHostile(long ownerId) { if (ownerId == 0L) { return; } foreach (DvergrCompanion item in s_all) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)this) && item._ownerId == ownerId) { if ((Object)(object)item._character != (Object)null) { MarkHostile(item._character); } if ((Object)(object)_character != (Object)null) { item.MarkHostile(_character); } } } } public bool HasPotentialDuelRivalNearby() { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) if (_ownerId == 0L) { return false; } foreach (DvergrCompanion item in s_all) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)this) && item._ownerId != 0L && item._ownerId != _ownerId) { Character character = item._character; if (!((Object)(object)character == (Object)null) && !character.IsDead() && Vector3.Distance(((Component)this).transform.position, ((Component)item).transform.position) <= 30f) { return true; } } } return false; } private void Update() { if (!InAnyDuelMode && (Stance == CompanionStance.Standby || ChoreActive) && (Object)(object)_ai != (Object)null) { Character targetCreature = ((BaseAI)_ai).GetTargetCreature(); if ((Object)(object)targetCreature != (Object)null) { Player val = (Player)(object)((targetCreature is Player) ? targetCreature : null); if (val == null || !IsHostileTo((Character)(object)val)) { _ai.SetTarget((Character)null); } } } bool flag = (Object)(object)_znv == (Object)null || !_znv.IsValid() || _znv.IsOwner(); if (DuelMode && flag) { TickDuel(); } else if (PartyDuelMode && flag) { TickPartyDuel(); } _pruneTimer += Time.deltaTime; if (!(_pruneTimer >= 5f)) { return; } _pruneTimer = 0f; if (_hostileUntil.Count <= 0) { return; } float time = Time.time; List list = new List(); foreach (KeyValuePair item in _hostileUntil) { if (item.Value <= time) { list.Add(item.Key); } } foreach (Character item2 in list) { _hostileUntil.Remove(item2); } } public void SetCaste(DvergrCaste caste) { Caste = caste; if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { _znv.GetZDO().Set("DE_Caste", (int)caste); } } public void AddXp(float amount) { if (!(amount <= 0f) && Level < 10) { Xp += amount; while (Level < 10 && Xp >= XpRequiredForNextLevel()) { Xp -= XpRequiredForNextLevel(); Level++; ApplyLevelToCharacter(); Plugin.Log.LogInfo((object)$"Dvergr {Caste} reached level {Level}."); ServerGuideBridge.RaiseLevelUp(Caste, Level); } if (Level >= 10) { Xp = 0f; } Persist(); } } private void ApplyLevelToCharacter() { if (!((Object)(object)_character == (Object)null)) { _character.SetLevel(Level); ApplyCasteBonus(); } } private void ApplyCasteBonus() { int num = Level - 1; switch (Caste) { case DvergrCaste.Rogue: case DvergrCaste.FireMage: { if (!_baseSpeedsCaptured) { _baseRunSpeed = _character.m_runSpeed; _baseSpeed = _character.m_speed; _baseWalkSpeed = _character.m_walkSpeed; _baseSpeedsCaptured = true; } float num3 = 1f + 0.03f * (float)num; _character.m_runSpeed = _baseRunSpeed * num3; _character.m_speed = _baseSpeed * num3; _character.m_walkSpeed = _baseWalkSpeed * num3; break; } case DvergrCaste.IceMage: case DvergrCaste.SupportMage: { float maxHealth = _character.GetMaxHealth(); float num2 = maxHealth * (1f + 0.04f * (float)num); _character.SetMaxHealth(num2); _character.SetHealth(Mathf.Min(_character.GetHealth() + (num2 - maxHealth), num2)); break; } } } private void Persist() { if ((Object)(object)_znv != (Object)null && _znv.IsValid()) { _znv.GetZDO().Set("DE_Level", Level); _znv.GetZDO().Set("DE_Xp", Xp); } } public void SetStance(CompanionStance stance, GameObject followTarget) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) Stance = stance; AnnounceCapability(); if (!((Object)(object)_ai == (Object)null)) { if (!_baseAlertRangeCaptured) { _baseAlertRange = _ai.m_alertRange; _baseAlertRangeCaptured = true; } switch (stance) { case CompanionStance.Follow: _ai.m_alertRange = _baseAlertRange; _ai.SetFollowTarget(followTarget); break; case CompanionStance.Guard: _ai.m_alertRange = _baseAlertRange * 2.5f; _ai.SetFollowTarget((GameObject)null); ((BaseAI)_ai).SetPatrolPoint(((Component)this).transform.position); break; case CompanionStance.Standby: _ai.m_alertRange = 0f; _ai.SetFollowTarget((GameObject)null); ((BaseAI)_ai).SetPatrolPoint(((Component)this).transform.position); _ai.SetTarget((Character)null); break; } } } public void AnnounceCapability() { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Chat.instance == (Object)null) && !((Object)(object)_character == (Object)null)) { string text = Caste switch { DvergrCaste.FireMage => "tend smelters, kilns and forges", DvergrCaste.IceMage => "run the refineries", DvergrCaste.SupportMage => "farm, cook, brew and tend the beasts", _ => "haul loose drops to a chest", }; string text2 = Stance switch { CompanionStance.Guard => "I'll hold this ground. Or set me to " + text + ".", CompanionStance.Standby => "I'll wait here, quietly. Set me to " + text + " when you're ready.", _ => "I'll follow and fight at your side. Set me to " + text + ".", }; Chat.instance.SetNpcText(((Component)this).gameObject, Vector3.up * 2.2f, 20f, 6f, string.Empty, text2, false); } } public void AnnounceBoarded() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Chat.instance == (Object)null) && !((Object)(object)_character == (Object)null)) { Chat.instance.SetNpcText(((Component)this).gameObject, Vector3.up * 2.2f, 20f, 5f, string.Empty, "Aboard — I'll take a seat and hold fast.", false); } } public bool EnterDuelMode() { if (DuelMode) { return true; } if (ChoreActive || _feral || PartyDuelMode) { return false; } DuelMode = true; _duelEngaged = false; _duelResolved = false; _duelWaitTimer = 0f; _duelScanTimer = 0f; if ((Object)(object)_ai != (Object)null) { if (!_baseAlertRangeCaptured) { _baseAlertRange = _ai.m_alertRange; _baseAlertRangeCaptured = true; } _ai.m_alertRange = 30f; ((BaseAI)_ai).SetAlerted(true); } Announce(DisplayName + " squares up — challenging rival companions to a duel."); return true; } public void ExitDuelMode(DuelExitReason reason) { if (DuelMode) { DuelMode = false; _duelEngaged = false; DuelOpponentId = null; DuelOpponentOwner = 0L; if ((Object)(object)_ai != (Object)null) { _ai.SetTarget((Character)null); ((BaseAI)_ai).SetAlerted(false); RestoreAlertRangeForStance(); } switch (reason) { case DuelExitReason.Defeated: Announce(DisplayName + " is subdued and yields the duel."); break; case DuelExitReason.NoOpponents: Announce(DisplayName + " finds no more challengers and stands down."); break; case DuelExitReason.OwnerGone: Announce(DisplayName + " loses sight of its owner and breaks off the duel."); break; case DuelExitReason.OwnerStopped: Announce(DisplayName + " lowers its guard and leaves the sparring circle."); break; } } } public bool ResolveSubdue(DvergrCompanion winner) { if (_duelResolved) { return false; } _duelResolved = true; winner?.AwardDuelWin(this); ExitDuelMode(DuelExitReason.Defeated); return true; } public void AwardDuelWin(DvergrCompanion loser) { AddXp(50f); ServerGuideBridge.RaiseDuelWon(Caste, ((Object)(object)loser != (Object)null) ? loser.Caste : Caste, DisplayName, ((Object)(object)loser != (Object)null) ? loser.DisplayName : null); if ((Object)(object)loser != (Object)null && _ownerId != 0L && loser._ownerId != 0L && _ownerId != loser._ownerId) { string text = EnsureCompanionId(); string companionId = loser.CompanionId; if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(companionId)) { LeaderboardSync.ReportDuel(new DuelResult { WinnerId = text, WinnerOwnerId = _ownerId, WinnerOwnerName = (OwnerName ?? string.Empty), WinnerName = DisplayName, WinnerCaste = (int)Caste, LoserId = companionId, LoserOwnerId = loser._ownerId, LoserOwnerName = (loser.OwnerName ?? string.Empty), LoserName = loser.DisplayName, LoserCaste = (int)loser.Caste }); } } Announce($"{DisplayName} wins the bout! (+{50f:0} XP)"); if ((Object)(object)Chat.instance != (Object)null) { string ownerName = OwnerName; string text2 = (((Object)(object)loser != (Object)null) ? loser.DisplayName : "its rival"); string text3 = (string.IsNullOrEmpty(ownerName) ? DisplayName : (DisplayName + " (" + ownerName + ")")); Chat.instance.SendText((Type)2, text3 + " wins the duel against " + text2 + "!"); } } private void TickDuel() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) Player val = OwnerPlayer(); if ((Object)(object)val == (Object)null || Vector3.Distance(((Component)val).transform.position, ((Component)this).transform.position) > 40f) { ExitDuelMode(DuelExitReason.OwnerGone); return; } _duelScanTimer += Time.deltaTime; if (_duelScanTimer < 1f) { return; } _duelScanTimer = 0f; DvergrCompanion dvergrCompanion = FindNearestDuelRival(); if ((Object)(object)dvergrCompanion != (Object)null) { _duelEngaged = true; _duelWaitTimer = 0f; if ((Object)(object)_ai != (Object)null) { Character targetCreature = ((BaseAI)_ai).GetTargetCreature(); if ((Object)(object)targetCreature == (Object)null || targetCreature.IsDead() || (Object)(object)((Component)targetCreature).GetComponent() != (Object)(object)dvergrCompanion) { _ai.SetTarget(((Component)dvergrCompanion).GetComponent()); ((BaseAI)_ai).SetAlerted(true); } } } else if (_duelEngaged) { ExitDuelMode(DuelExitReason.NoOpponents); } else { _duelWaitTimer += 1f; if (_duelWaitTimer >= 60f) { ExitDuelMode(DuelExitReason.NoOpponents); } } } private DvergrCompanion FindNearestDuelRival() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) DvergrCompanion result = null; float num = float.MaxValue; foreach (DvergrCompanion item in s_all) { if ((Object)(object)item == (Object)null || (Object)(object)item == (Object)(object)this || !item.DuelMode || item._ownerId == _ownerId || _ownerId == 0L || item._ownerId == 0L) { continue; } Character character = item._character; if (!((Object)(object)character == (Object)null) && !character.IsDead()) { float num2 = Vector3.Distance(((Component)this).transform.position, ((Component)item).transform.position); if (num2 <= 30f && num2 < num) { num = num2; result = item; } } } return result; } public bool EnterPartyDuelMode() { if (PartyDuelMode) { return true; } if (ChoreActive || _feral || DuelMode) { return false; } PartyDuelMode = true; _duelResolved = false; _partyEngaged = false; _partyWaitTimer = 0f; _partyScanTimer = 0f; _partyPeakEnemies = 0; _partyAllySnap.Clear(); _partyEnemySnap.Clear(); _partyOpponentOwner = 0L; _partyOpponentOwnerName = null; if ((Object)(object)_ai != (Object)null) { if (!_baseAlertRangeCaptured) { _baseAlertRange = _ai.m_alertRange; _baseAlertRangeCaptured = true; } _ai.m_alertRange = 30f; ((BaseAI)_ai).SetAlerted(true); } Announce(DisplayName + " joins the melee — a party duel!"); return true; } public void ExitPartyDuelMode(DuelExitReason reason) { if (PartyDuelMode) { PartyDuelMode = false; _partyEngaged = false; DuelOpponentId = null; DuelOpponentOwner = 0L; if ((Object)(object)_ai != (Object)null) { _ai.SetTarget((Character)null); ((BaseAI)_ai).SetAlerted(false); RestoreAlertRangeForStance(); } switch (reason) { case DuelExitReason.Defeated: Announce(DisplayName + " is subdued and steps out of the melee."); break; case DuelExitReason.NoOpponents: Announce(DisplayName + "'s side stands victorious!"); break; case DuelExitReason.OwnerGone: Announce(DisplayName + " loses sight of its owner and withdraws."); break; case DuelExitReason.OwnerStopped: Announce(DisplayName + " lowers its guard and leaves the melee."); break; } } } public bool ResolvePartySubdue() { if (_duelResolved) { return false; } _duelResolved = true; ExitPartyDuelMode(DuelExitReason.Defeated); return true; } private void TickPartyDuel() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) Player val = OwnerPlayer(); if ((Object)(object)val == (Object)null || Vector3.Distance(((Component)val).transform.position, ((Component)this).transform.position) > 40f) { ExitPartyDuelMode(DuelExitReason.OwnerGone); return; } _partyScanTimer += Time.deltaTime; if (_partyScanTimer < 1f) { return; } _partyScanTimer = 0f; int num = ScanParty(); if (num > _partyPeakEnemies) { _partyPeakEnemies = num; } if (num > 0) { _partyEngaged = true; _partyWaitTimer = 0f; if (!((Object)(object)_ai != (Object)null)) { return; } Character targetCreature = ((BaseAI)_ai).GetTargetCreature(); if ((Object)(object)targetCreature == (Object)null || targetCreature.IsDead() || (Object)(object)((Component)targetCreature).GetComponent() == (Object)null || !((Component)targetCreature).GetComponent().PartyDuelMode) { DvergrCompanion dvergrCompanion = FindNearestPartyRival(); if ((Object)(object)dvergrCompanion != (Object)null) { _ai.SetTarget(((Component)dvergrCompanion).GetComponent()); ((BaseAI)_ai).SetAlerted(true); } } } else if (_partyEngaged) { AwardPartyWin(); ExitPartyDuelMode(DuelExitReason.NoOpponents); } else { _partyWaitTimer += 1f; if (_partyWaitTimer >= 60f) { ExitPartyDuelMode(DuelExitReason.NoOpponents); } } } private void AwardPartyWin() { int num = Mathf.Max(1, _partyAllySnap.Count); int num2 = Mathf.Max(1, (_partyEnemySnap.Count > 0) ? _partyEnemySnap.Count : _partyPeakEnemies); float num3 = 50f * (float)num2 / (float)num; AddXp(num3); Announce($"{DisplayName} wins the party duel! (+{num3:0} XP)"); if (_partyOpponentOwner != 0L && _ownerId != 0L) { string key = ((_ownerId <= _partyOpponentOwner) ? $"{_ownerId}|{_partyOpponentOwner}" : $"{_partyOpponentOwner}|{_ownerId}"); if (!s_partyReportLatch.TryGetValue(key, out var value) || !(Time.time - value < 15f)) { s_partyReportLatch[key] = Time.time; string text = LeaderboardStore.FindParty(_ownerId)?.partyName; LeaderboardSync.ReportPartyDuel(new PartyDuelResult { WinnerOwnerId = _ownerId, WinnerOwnerName = (OwnerName ?? string.Empty), LoserOwnerId = _partyOpponentOwner, LoserOwnerName = (_partyOpponentOwnerName ?? string.Empty), MvpCaste = (int)Caste, WinnerMembers = SnapToList(_partyAllySnap), LoserMembers = SnapToList(_partyEnemySnap), WinnerPartyName = (text ?? string.Empty) }); ServerGuideBridge.RaisePartyDuelWon(OwnerName ?? string.Empty, num, _partyOpponentOwnerName ?? string.Empty, (int)Caste, text); } } } private static List SnapToList(Dictionary snap) { List list = new List(snap.Count); foreach (KeyValuePair item in snap) { string[] array = item.Value.Split(new char[1] { ':' }); int result = 0; int result2 = 1; if (array.Length >= 2) { int.TryParse(array[0], out result); int.TryParse(array[1], out result2); } list.Add(new PartyMemberSnap { companionId = item.Key, caste = result, level = result2 }); } return list; } private int ScanParty() { string companionId = CompanionId; if (!string.IsNullOrEmpty(companionId)) { _partyAllySnap[companionId] = $"{(int)Caste}:{Level}"; } int num = 0; foreach (DvergrCompanion item in s_all) { if ((Object)(object)item == (Object)null || (Object)(object)item == (Object)(object)this || !item.PartyDuelMode || _ownerId == 0L || item._ownerId == 0L) { continue; } Character character = item._character; if ((Object)(object)character == (Object)null || character.IsDead()) { continue; } string companionId2 = item.CompanionId; if (item._ownerId == _ownerId) { if (!string.IsNullOrEmpty(companionId2)) { _partyAllySnap[companionId2] = $"{(int)item.Caste}:{item.Level}"; } continue; } num++; if (!string.IsNullOrEmpty(companionId2)) { _partyEnemySnap[companionId2] = $"{(int)item.Caste}:{item.Level}"; } if (_partyOpponentOwner == 0L) { _partyOpponentOwner = item._ownerId; _partyOpponentOwnerName = item.OwnerName; } } return num; } private DvergrCompanion FindNearestPartyRival() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) DvergrCompanion result = null; float num = float.MaxValue; foreach (DvergrCompanion item in s_all) { if ((Object)(object)item == (Object)null || (Object)(object)item == (Object)(object)this || !item.PartyDuelMode || item._ownerId == _ownerId || _ownerId == 0L || item._ownerId == 0L) { continue; } Character character = item._character; if (!((Object)(object)character == (Object)null) && !character.IsDead()) { float num2 = Vector3.Distance(((Component)this).transform.position, ((Component)item).transform.position); if (num2 < num) { num = num2; result = item; } } } return result; } public bool HasPotentialPartyRivalNearby() { return HasPotentialDuelRivalNearby(); } private void RestoreAlertRangeForStance() { if (!((Object)(object)_ai == (Object)null)) { float num = (_baseAlertRangeCaptured ? _baseAlertRange : _ai.m_alertRange); switch (Stance) { case CompanionStance.Guard: _ai.m_alertRange = num * 2.5f; break; case CompanionStance.Standby: _ai.m_alertRange = 0f; break; default: _ai.m_alertRange = num; break; } } } private void Announce(string line) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_character == (Object)null)) { if ((Object)(object)Chat.instance != (Object)null) { Chat.instance.SetNpcText(((Component)this).gameObject, Vector3.up * 2.2f, 20f, 5f, string.Empty, line, false); } if ((Object)(object)MessageHud.instance != (Object)null && (Object)(object)Player.m_localPlayer != (Object)null && IsOwner(Player.m_localPlayer)) { MessageHud.instance.ShowMessage((MessageType)2, line, 0, (Sprite)null, false); } } } } public enum CompanionStance { Follow, Guard, Standby } [HarmonyPatch(typeof(InventoryGui), "Show")] public static class InventoryMenuBar { private struct MenuEntry { public string Name; public string Label; public Action Open; } private const float TopEdgeInset = -18f; private const float ButtonWidth = 132f; private const float ButtonHeight = 32f; private const float ButtonGap = 6f; private static MenuEntry[] Entries => new MenuEntry[3] { new MenuEntry { Name = "LSII_RankingsButton", Label = "Rankings", Open = delegate { RankingBoard.Open(); } }, new MenuEntry { Name = "LSII_TournamentButton", Label = "Tournaments", Open = delegate { TournamentRegistration.Toggle(Player.m_localPlayer); } }, new MenuEntry { Name = "LSII_BountyBoardButton", Label = "Bounty Board", Open = delegate { BountyBoardPanel.Toggle(Player.m_localPlayer); } } }; public static void Postfix(InventoryGui __instance) { try { Ensure(__instance); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[ui] inventory menu bar failed: " + ex.Message)); } } private static void Ensure(InventoryGui gui) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: 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 ((Object)(object)gui == (Object)null || (Object)(object)gui.m_player == (Object)null) { return; } Button takeAllButton = gui.m_takeAllButton; if ((Object)(object)takeAllButton == (Object)null) { return; } Transform parent = ((Transform)gui.m_player).parent; RectTransform val = (RectTransform)(((object)((parent is RectTransform) ? parent : null)) ?? ((object)gui.m_player)); if ((Object)(object)val == (Object)null) { return; } MenuEntry[] entries = Entries; float num = (float)entries.Length * 132f + (float)(entries.Length - 1) * 6f; float num2 = (0f - num) / 2f + 66f; Vector2 val2 = ParseOffset(MenuBarOffset()); bool flag = false; for (int i = 0; i < entries.Length; i++) { MenuEntry entry = entries[i]; if (!((Object)(object)((Transform)val).Find(entry.Name) != (Object)null)) { float num3 = num2 + (float)i * 138f; Build(takeAllButton, val, entry, new Vector2(num3, -18f) + val2); flag = true; } } if (flag) { Plugin.Log.LogInfo((object)($"[ui] inventory menu bar: {entries.Length} button(s) under " + $"'{((Object)val).name}', row width {num}, offset {val2}.")); } } private static void Build(Button source, RectTransform parent, MenuEntry entry, Vector2 pos) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Expected O, but got Unknown GameObject val = Object.Instantiate(((Component)source).gameObject, (Transform)(object)parent); ((Object)val).name = entry.Name; (val.GetComponent() ?? val.AddComponent()).ignoreLayout = true; RectTransform component = val.GetComponent(); if ((Object)(object)component != (Object)null) { Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0.5f, 1f); component.anchorMax = val2; component.anchorMin = val2; component.pivot = new Vector2(0.5f, 1f); component.sizeDelta = new Vector2(132f, 32f); ((Transform)component).localScale = Vector3.one; component.anchoredPosition = pos; } TMP_Text componentInChildren = val.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = entry.Label; componentInChildren.enableAutoSizing = false; componentInChildren.fontSize = 14f; } Button component2 = val.GetComponent