Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Thors Mjolnir v2.6.4
plugins/ValheimMjolnir/ValheimMjolnir.Core.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.CodeAnalysis; using ValheimMjolnir.Core.Gameplay; using ValheimMjolnir.Core.Identity; using ValheimMjolnir.Core.Persistence; using ValheimMjolnir.Core.State; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("ValheimMjolnir.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.6.4.0")] [assembly: AssemblyInformationalVersion("2.6.4+b4c6ac47ce16619676e8ff87e062a9fca15afbd1")] [assembly: AssemblyProduct("ValheimMjolnir.Core")] [assembly: AssemblyTitle("ValheimMjolnir.Core")] [assembly: AssemblyVersion("2.6.4.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace ValheimMjolnir.Core.State { public enum MjolnirAbilityState { Idle, LightningCharging, LightningExecuting, Flight, GroundSlam, Cyclone, CrosshairControl, ThunderClap, StormLine, ChainLightning, OverheadSlam, MultiAngleBarrage, Assaulting, GroundedLightningRod, GodBlastCharging, GodBlastDischarging, MomentumSlam, OrbitingHammer, LightningStep, Guard } public readonly struct MjolnirAggregateState : IEquatable<MjolnirAggregateState> { public MjolnirHammerState Hammer { get; } public MjolnirAbilityState Ability { get; } public static MjolnirAggregateState Initial => new MjolnirAggregateState(MjolnirHammerState.Stored, MjolnirAbilityState.Idle); public MjolnirAggregateState(MjolnirHammerState hammer, MjolnirAbilityState ability) { Hammer = hammer; Ability = ability; } public bool Equals(MjolnirAggregateState other) { if (Hammer == other.Hammer) { return Ability == other.Ability; } return false; } public override bool Equals(object? obj) { if (obj is MjolnirAggregateState other) { return Equals(other); } return false; } public override int GetHashCode() { return ((int)Hammer * 397) ^ (int)Ability; } public override string ToString() { return $"{Hammer}/{Ability}"; } public static bool operator ==(MjolnirAggregateState left, MjolnirAggregateState right) { return left.Equals(right); } public static bool operator !=(MjolnirAggregateState left, MjolnirAggregateState right) { return !left.Equals(right); } } public static class MjolnirAggregateStateMachine { public static MjolnirCommandDecision Evaluate(MjolnirAggregateState source, MjolnirCommand command) { if (!IsValid(source) || !Enum.IsDefined(typeof(MjolnirCommand), command)) { return MjolnirCommandDecision.Reject(source, command); } MjolnirAggregateState? mjolnirAggregateState; switch (command) { case MjolnirCommand.Equip: if (Is(source, MjolnirHammerState.Stored, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Held); break; } goto default; case MjolnirCommand.Unequip: if (Is(source, MjolnirHammerState.Held, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Stored); break; } goto default; case MjolnirCommand.BeginThrowCharge: if (Is(source, MjolnirHammerState.Held, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.ThrowCharging); break; } goto default; case MjolnirCommand.CancelThrowCharge: if (Is(source, MjolnirHammerState.ThrowCharging, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Held); break; } goto default; case MjolnirCommand.RequestThrowRelease: if (Is(source, MjolnirHammerState.ThrowCharging, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.ReleasePending); break; } goto default; case MjolnirCommand.CancelThrowRelease: if (Is(source, MjolnirHammerState.ReleasePending, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Held); break; } goto default; case MjolnirCommand.CommitThrow: if (Is(source, MjolnirHammerState.ReleasePending, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Outbound); break; } goto default; case MjolnirCommand.Embed: if (Is(source, MjolnirHammerState.Outbound, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Embedded); break; } goto default; case MjolnirCommand.Plant: { bool flag = source.Ability == MjolnirAbilityState.Idle; if (flag) { MjolnirHammerState hammer = source.Hammer; bool flag2 = (((uint)(hammer - 4) <= 2u || hammer == MjolnirHammerState.Levitating) ? true : false); flag = flag2; } if (flag) { mjolnirAggregateState = State(MjolnirHammerState.Planted); break; } goto default; } case MjolnirCommand.EmbedInEnemy: if (Is(source, MjolnirHammerState.Outbound, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.EmbeddedInEnemy); break; } goto default; case MjolnirCommand.ReleaseEnemyEmbedToDropped: if (Is(source, MjolnirHammerState.EmbeddedInEnemy, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Dropped); break; } goto default; case MjolnirCommand.Drop: { bool flag = source.Ability == MjolnirAbilityState.Idle; if (flag) { MjolnirHammerState hammer = source.Hammer; bool flag2 = (uint)hammer <= 1u; flag = flag2; } if (flag) { mjolnirAggregateState = State(MjolnirHammerState.Dropped); break; } goto default; } case MjolnirCommand.Pickup: if (Is(source, MjolnirHammerState.Dropped, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Stored); break; } goto default; case MjolnirCommand.BeginRecall: { MjolnirAbilityState ability = source.Ability; bool flag = ((ability == MjolnirAbilityState.Idle || ability == MjolnirAbilityState.GroundedLightningRod) ? true : false); bool flag2 = flag; if (flag2) { bool flag3; switch (source.Hammer) { case MjolnirHammerState.Outbound: case MjolnirHammerState.Embedded: case MjolnirHammerState.CrosshairControlled: case MjolnirHammerState.Levitating: case MjolnirHammerState.Planted: case MjolnirHammerState.EmbeddedInEnemy: flag3 = true; break; default: flag3 = false; break; } flag2 = flag3; } if (flag2) { mjolnirAggregateState = State(MjolnirHammerState.Returning); break; } goto default; } case MjolnirCommand.CancelRecallToOutbound: if (Is(source, MjolnirHammerState.Returning, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Outbound); break; } goto default; case MjolnirCommand.CancelRecallToEmbedded: if (Is(source, MjolnirHammerState.Returning, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Embedded); break; } goto default; case MjolnirCommand.BeginCatch: if (Is(source, MjolnirHammerState.Returning, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Catching); break; } goto default; case MjolnirCommand.CompleteCatch: if (Is(source, MjolnirHammerState.Catching, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Held); break; } goto default; case MjolnirCommand.BeginOrbit: if (Is(source, MjolnirHammerState.Returning, MjolnirAbilityState.Idle)) { mjolnirAggregateState = new MjolnirAggregateState(MjolnirHammerState.Orbiting, MjolnirAbilityState.OrbitingHammer); break; } goto default; case MjolnirCommand.EndOrbitToRecall: if (Is(source, MjolnirHammerState.Orbiting, MjolnirAbilityState.OrbitingHammer)) { mjolnirAggregateState = State(MjolnirHammerState.Returning); break; } goto default; case MjolnirCommand.ReleaseOrbit: if (Is(source, MjolnirHammerState.Orbiting, MjolnirAbilityState.OrbitingHammer)) { mjolnirAggregateState = State(MjolnirHammerState.Outbound); break; } goto default; case MjolnirCommand.BeginCrosshairControl: { MjolnirHammerState hammer = source.Hammer; bool flag2 = (uint)(hammer - 4) <= 2u; bool flag = flag2 && source.Ability == MjolnirAbilityState.Idle; if (!flag) { bool flag3 = source.Hammer == MjolnirHammerState.Levitating; if (flag3) { MjolnirAbilityState ability = source.Ability; bool flag4 = (uint)ability <= 2u; flag3 = flag4; } flag = flag3; } if (flag) { mjolnirAggregateState = new MjolnirAggregateState(MjolnirHammerState.CrosshairControlled, source.Ability); break; } goto default; } case MjolnirCommand.UpdateCrosshairTarget: { bool flag = source.Hammer == MjolnirHammerState.CrosshairControlled; if (flag) { MjolnirAbilityState ability = source.Ability; bool flag2 = (uint)ability <= 2u; flag = flag2; } if (flag) { mjolnirAggregateState = new MjolnirAggregateState(MjolnirHammerState.CrosshairControlled, source.Ability); break; } goto default; } case MjolnirCommand.CompleteCrosshairControl: if (source.Hammer == MjolnirHammerState.CrosshairControlled) { mjolnirAggregateState = new MjolnirAggregateState(MjolnirHammerState.Levitating, source.Ability); break; } goto default; case MjolnirCommand.ReleaseCrosshairControl: { MjolnirHammerState hammer = source.Hammer; if ((uint)(hammer - 9) <= 1u) { mjolnirAggregateState = new MjolnirAggregateState(MjolnirHammerState.Outbound, source.Ability); break; } goto default; } case MjolnirCommand.ExhaustCrosshairControl: if (source.Hammer == MjolnirHammerState.CrosshairControlled) { mjolnirAggregateState = new MjolnirAggregateState(MjolnirHammerState.Levitating, source.Ability); break; } goto default; case MjolnirCommand.BeginLightningCharge: { bool flag = source.Ability == MjolnirAbilityState.Idle; if (flag) { bool flag2; switch (source.Hammer) { case MjolnirHammerState.Held: case MjolnirHammerState.Embedded: case MjolnirHammerState.Dropped: case MjolnirHammerState.CrosshairControlled: case MjolnirHammerState.Levitating: case MjolnirHammerState.EmbeddedInEnemy: flag2 = true; break; default: flag2 = false; break; } flag = flag2; } if (flag) { mjolnirAggregateState = new MjolnirAggregateState(source.Hammer, MjolnirAbilityState.LightningCharging); break; } goto default; } case MjolnirCommand.CancelLightningCharge: if (source.Ability == MjolnirAbilityState.LightningCharging) { mjolnirAggregateState = new MjolnirAggregateState(source.Hammer, MjolnirAbilityState.Idle); break; } goto default; case MjolnirCommand.CommitLightningCast: if (source.Ability == MjolnirAbilityState.LightningCharging) { mjolnirAggregateState = new MjolnirAggregateState(source.Hammer, MjolnirAbilityState.LightningExecuting); break; } goto default; case MjolnirCommand.CancelLightningCast: if (source.Ability == MjolnirAbilityState.LightningExecuting) { mjolnirAggregateState = new MjolnirAggregateState(source.Hammer, MjolnirAbilityState.Idle); break; } goto default; case MjolnirCommand.CompleteLightning: if (source.Ability == MjolnirAbilityState.LightningCharging) { mjolnirAggregateState = new MjolnirAggregateState(source.Hammer, MjolnirAbilityState.Idle); break; } if (source.Ability == MjolnirAbilityState.LightningExecuting) { mjolnirAggregateState = new MjolnirAggregateState(source.Hammer, MjolnirAbilityState.Idle); break; } goto default; case MjolnirCommand.BeginFlight: if (Is(source, MjolnirHammerState.Held, MjolnirAbilityState.Idle)) { mjolnirAggregateState = new MjolnirAggregateState(MjolnirHammerState.Held, MjolnirAbilityState.Flight); break; } goto default; case MjolnirCommand.EndFlight: if (Is(source, MjolnirHammerState.Held, MjolnirAbilityState.Flight)) { mjolnirAggregateState = State(MjolnirHammerState.Held); break; } goto default; case MjolnirCommand.BeginGroundSlam: if (Is(source, MjolnirHammerState.Held, MjolnirAbilityState.Flight)) { mjolnirAggregateState = new MjolnirAggregateState(MjolnirHammerState.Held, MjolnirAbilityState.GroundSlam); break; } goto default; case MjolnirCommand.CompleteGroundSlam: if (Is(source, MjolnirHammerState.Held, MjolnirAbilityState.GroundSlam)) { mjolnirAggregateState = State(MjolnirHammerState.Held); break; } goto default; case MjolnirCommand.BeginMomentumSlam: { bool flag = source.Hammer == MjolnirHammerState.Held; if (flag) { MjolnirAbilityState ability = source.Ability; bool flag2 = ((ability == MjolnirAbilityState.Idle || ability == MjolnirAbilityState.Flight) ? true : false); flag = flag2; } if (flag) { mjolnirAggregateState = new MjolnirAggregateState(MjolnirHammerState.Held, MjolnirAbilityState.MomentumSlam); break; } goto default; } case MjolnirCommand.CompleteMomentumSlam: if (Is(source, MjolnirHammerState.Held, MjolnirAbilityState.MomentumSlam)) { mjolnirAggregateState = State(MjolnirHammerState.Held); break; } goto default; case MjolnirCommand.BeginLightningStep: { bool flag = source.Hammer == MjolnirHammerState.Held; if (flag) { MjolnirAbilityState ability = source.Ability; bool flag2 = ((ability == MjolnirAbilityState.Idle || ability == MjolnirAbilityState.Flight) ? true : false); flag = flag2; } if (flag) { mjolnirAggregateState = new MjolnirAggregateState(MjolnirHammerState.Held, MjolnirAbilityState.LightningStep); break; } goto default; } case MjolnirCommand.CompleteLightningStep: if (Is(source, MjolnirHammerState.Held, MjolnirAbilityState.LightningStep)) { mjolnirAggregateState = State(MjolnirHammerState.Held); break; } goto default; case MjolnirCommand.BeginGuard: if (Is(source, MjolnirHammerState.Held, MjolnirAbilityState.Idle)) { mjolnirAggregateState = new MjolnirAggregateState(MjolnirHammerState.Held, MjolnirAbilityState.Guard); break; } goto default; case MjolnirCommand.CompleteGuard: if (Is(source, MjolnirHammerState.Held, MjolnirAbilityState.Guard)) { mjolnirAggregateState = State(MjolnirHammerState.Held); break; } goto default; case MjolnirCommand.MarkUnavailable: if (source.Hammer != MjolnirHammerState.Unavailable) { mjolnirAggregateState = State(MjolnirHammerState.Unavailable); break; } goto default; case MjolnirCommand.RecoverStored: if (Is(source, MjolnirHammerState.Unavailable, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Stored); break; } goto default; case MjolnirCommand.RecoverDropped: if (Is(source, MjolnirHammerState.Unavailable, MjolnirAbilityState.Idle)) { mjolnirAggregateState = State(MjolnirHammerState.Dropped); break; } goto default; default: mjolnirAggregateState = null; break; } MjolnirAggregateState? mjolnirAggregateState2 = mjolnirAggregateState; if (!mjolnirAggregateState2.HasValue || !IsValid(mjolnirAggregateState2.Value)) { return MjolnirCommandDecision.Reject(source, command); } return MjolnirCommandDecision.Accept(source, mjolnirAggregateState2.Value, command); } public static bool IsValid(MjolnirAggregateState state) { if (!Enum.IsDefined(typeof(MjolnirHammerState), state.Hammer) || !Enum.IsDefined(typeof(MjolnirAbilityState), state.Ability)) { return false; } switch (state.Ability) { case MjolnirAbilityState.Idle: return true; case MjolnirAbilityState.LightningCharging: { bool result; switch (state.Hammer) { case MjolnirHammerState.Held: case MjolnirHammerState.Outbound: case MjolnirHammerState.Embedded: case MjolnirHammerState.Dropped: case MjolnirHammerState.CrosshairControlled: case MjolnirHammerState.Levitating: case MjolnirHammerState.EmbeddedInEnemy: result = true; break; default: result = false; break; } return result; } case MjolnirAbilityState.LightningExecuting: { bool result; switch (state.Hammer) { case MjolnirHammerState.Held: case MjolnirHammerState.Outbound: case MjolnirHammerState.Embedded: case MjolnirHammerState.Dropped: case MjolnirHammerState.CrosshairControlled: case MjolnirHammerState.Levitating: case MjolnirHammerState.EmbeddedInEnemy: result = true; break; default: result = false; break; } return result; } case MjolnirAbilityState.Flight: case MjolnirAbilityState.GroundSlam: case MjolnirAbilityState.MomentumSlam: return state.Hammer == MjolnirHammerState.Held; case MjolnirAbilityState.LightningStep: return state.Hammer == MjolnirHammerState.Held; case MjolnirAbilityState.Guard: return state.Hammer == MjolnirHammerState.Held; case MjolnirAbilityState.ThunderClap: case MjolnirAbilityState.StormLine: case MjolnirAbilityState.OverheadSlam: return state.Hammer == MjolnirHammerState.Held; case MjolnirAbilityState.Cyclone: case MjolnirAbilityState.CrosshairControl: case MjolnirAbilityState.ChainLightning: case MjolnirAbilityState.MultiAngleBarrage: { MjolnirHammerState hammer = state.Hammer; return ((uint)(hammer - 4) <= 3u || (uint)(hammer - 9) <= 1u) ? true : false; } case MjolnirAbilityState.Assaulting: return state.Hammer == MjolnirHammerState.CrosshairControlled; case MjolnirAbilityState.GroundedLightningRod: return state.Hammer == MjolnirHammerState.Planted; case MjolnirAbilityState.OrbitingHammer: return state.Hammer == MjolnirHammerState.Orbiting; case MjolnirAbilityState.GodBlastCharging: case MjolnirAbilityState.GodBlastDischarging: return state.Hammer == MjolnirHammerState.Held; default: return false; } } private static bool Is(MjolnirAggregateState state, MjolnirHammerState hammer, MjolnirAbilityState ability) { if (state.Hammer == hammer) { return state.Ability == ability; } return false; } private static MjolnirAggregateState State(MjolnirHammerState hammer) { return new MjolnirAggregateState(hammer, MjolnirAbilityState.Idle); } } public enum MjolnirCommand { Equip, Unequip, BeginThrowCharge, CancelThrowCharge, RequestThrowRelease, CancelThrowRelease, CommitThrow, Embed, Drop, Pickup, BeginRecall, CancelRecallToOutbound, CancelRecallToEmbedded, BeginCatch, CompleteCatch, BeginCrosshairControl, UpdateCrosshairTarget, CompleteCrosshairControl, ExhaustCrosshairControl, BeginLightningCharge, CancelLightningCharge, CommitLightningCast, CancelLightningCast, CompleteLightning, BeginFlight, EndFlight, BeginGroundSlam, CompleteGroundSlam, BeginMomentumSlam, CompleteMomentumSlam, MarkUnavailable, RecoverStored, RecoverDropped, ReleaseCrosshairControl, Plant, BeginOrbit, EndOrbitToRecall, ReleaseOrbit, BeginLightningStep, CompleteLightningStep, BeginGuard, CompleteGuard, EmbedInEnemy, ReleaseEnemyEmbedToDropped } public sealed class MjolnirCommandDecision { public bool Accepted { get; } public MjolnirAggregateState Source { get; } public MjolnirAggregateState Target { get; } public MjolnirCommand Command { get; } public TransitionFailure Failure { get; } private MjolnirCommandDecision(bool accepted, MjolnirAggregateState source, MjolnirAggregateState target, MjolnirCommand command, TransitionFailure failure) { Accepted = accepted; Source = source; Target = target; Command = command; Failure = failure; } internal static MjolnirCommandDecision Accept(MjolnirAggregateState source, MjolnirAggregateState target, MjolnirCommand command) { return new MjolnirCommandDecision(accepted: true, source, target, command, TransitionFailure.None); } internal static MjolnirCommandDecision Reject(MjolnirAggregateState source, MjolnirCommand command) { return new MjolnirCommandDecision(accepted: false, source, source, command, TransitionFailure.InvalidTransition); } } public enum MjolnirHammerState { Stored, Held, ThrowCharging, ReleasePending, Outbound, Embedded, Dropped, Returning, Catching, CrosshairControlled, Levitating, Unavailable, Planted, Orbiting, EmbeddedInEnemy } public enum MjolnirState { Stored, Equipped, Held, Charging, MeleeAttack, ThrownOutbound, Embedded, Dropped, Orbiting, Recalling, Returning, Caught, LightningSummoned, FlightActive, GroundSlam, CrosshairControlled, Levitating, Unavailable, Planted, EmbeddedInEnemy } public static class MjolnirStateMachine { private static readonly IReadOnlyDictionary<MjolnirState, MjolnirState[]> Transitions = new Dictionary<MjolnirState, MjolnirState[]> { [MjolnirState.Stored] = States(MjolnirState.Equipped, MjolnirState.LightningSummoned, MjolnirState.Unavailable), [MjolnirState.Equipped] = States(MjolnirState.Held, MjolnirState.Stored, MjolnirState.Unavailable), [MjolnirState.Held] = States(MjolnirState.Charging, MjolnirState.MeleeAttack, MjolnirState.ThrownOutbound, MjolnirState.FlightActive, MjolnirState.Equipped, MjolnirState.Stored, MjolnirState.Unavailable), [MjolnirState.Charging] = States(MjolnirState.Held, MjolnirState.MeleeAttack, MjolnirState.ThrownOutbound, MjolnirState.Unavailable), [MjolnirState.MeleeAttack] = States(MjolnirState.Held, MjolnirState.Unavailable), [MjolnirState.ThrownOutbound] = States(MjolnirState.Embedded, MjolnirState.EmbeddedInEnemy, MjolnirState.Planted, MjolnirState.Dropped, MjolnirState.Orbiting, MjolnirState.Recalling, MjolnirState.Returning, MjolnirState.CrosshairControlled, MjolnirState.Unavailable), [MjolnirState.Embedded] = States(MjolnirState.Planted, MjolnirState.Recalling, MjolnirState.Returning, MjolnirState.Dropped, MjolnirState.LightningSummoned, MjolnirState.CrosshairControlled, MjolnirState.Unavailable), [MjolnirState.EmbeddedInEnemy] = States(MjolnirState.Recalling, MjolnirState.Returning, MjolnirState.Dropped, MjolnirState.Unavailable), [MjolnirState.Dropped] = States(MjolnirState.Stored, MjolnirState.Equipped, MjolnirState.Planted, MjolnirState.Recalling, MjolnirState.Returning, MjolnirState.LightningSummoned, MjolnirState.CrosshairControlled, MjolnirState.Unavailable), [MjolnirState.Orbiting] = States(MjolnirState.ThrownOutbound, MjolnirState.Recalling, MjolnirState.Returning, MjolnirState.Dropped, MjolnirState.Unavailable), [MjolnirState.Planted] = States(MjolnirState.Recalling, MjolnirState.Returning, MjolnirState.Dropped, MjolnirState.Unavailable), [MjolnirState.Recalling] = States(MjolnirState.Returning, MjolnirState.Unavailable), [MjolnirState.Returning] = States(MjolnirState.Caught, MjolnirState.Orbiting, MjolnirState.ThrownOutbound, MjolnirState.Embedded, MjolnirState.Dropped, MjolnirState.Unavailable), [MjolnirState.Caught] = States(MjolnirState.Held, MjolnirState.Unavailable), [MjolnirState.LightningSummoned] = States(MjolnirState.Returning, MjolnirState.Caught, MjolnirState.Unavailable), [MjolnirState.FlightActive] = States(MjolnirState.GroundSlam, MjolnirState.Held, MjolnirState.Unavailable), [MjolnirState.GroundSlam] = States(MjolnirState.Held, MjolnirState.Unavailable), [MjolnirState.CrosshairControlled] = States(MjolnirState.Levitating, MjolnirState.ThrownOutbound, MjolnirState.Recalling, MjolnirState.Dropped, MjolnirState.Unavailable), [MjolnirState.Levitating] = States(MjolnirState.Planted, MjolnirState.CrosshairControlled, MjolnirState.ThrownOutbound, MjolnirState.Recalling, MjolnirState.Returning, MjolnirState.Dropped, MjolnirState.Unavailable), [MjolnirState.Unavailable] = States(MjolnirState.Stored, MjolnirState.Dropped, MjolnirState.Equipped) }; public static bool CanTransition(MjolnirState source, MjolnirState target) { if (!Transitions.TryGetValue(source, out MjolnirState[] value)) { return false; } return Array.IndexOf(value, target) >= 0; } public static IReadOnlyList<MjolnirState> AllowedFrom(MjolnirState source) { if (!Transitions.TryGetValue(source, out MjolnirState[] value)) { return Array.Empty<MjolnirState>(); } return value; } public static TransitionDecision Evaluate(MjolnirState source, MjolnirState target) { if (!CanTransition(source, target)) { return TransitionDecision.Reject(source, target); } return TransitionDecision.Accept(source, target); } private static MjolnirState[] States(params MjolnirState[] states) { return states; } } public sealed class TransitionDecision { public bool Accepted { get; } public MjolnirState Source { get; } public MjolnirState Target { get; } public TransitionFailure Failure { get; } private TransitionDecision(bool accepted, MjolnirState source, MjolnirState target, TransitionFailure failure) { Accepted = accepted; Source = source; Target = target; Failure = failure; } public static TransitionDecision Accept(MjolnirState source, MjolnirState target) { return new TransitionDecision(accepted: true, source, target, TransitionFailure.None); } public static TransitionDecision Reject(MjolnirState source, MjolnirState target) { return new TransitionDecision(accepted: false, source, target, TransitionFailure.InvalidTransition); } } public enum TransitionFailure { None, UnknownHammer, InvalidHammerId, InvalidTransactionId, InvalidTransition, EpochMismatch, SequenceMismatch, RevisionMismatch, DuplicateHammer, WorldMismatch, WorldAlreadyIssued, NoIssuance, TransactionConflict, InvalidSnapshot, UnsupportedSchema } } namespace ValheimMjolnir.Core.Persistence { public sealed class HammerLedger { private enum LedgerCommandKind { Issue, Transition, AdvanceEpochForRecovery } private readonly struct LedgerCommandFingerprint : IEquatable<LedgerCommandFingerprint> { private LedgerCommandKind Kind { get; } private HammerIdentity Identity { get; } private long ExpectedStateSequence { get; } private long ExpectedRevision { get; } private MjolnirCommand? Command { get; } internal LedgerCommandFingerprint(LedgerCommandKind kind, HammerIdentity identity, long expectedStateSequence, long expectedRevision, MjolnirCommand? command) { Kind = kind; Identity = identity; ExpectedStateSequence = expectedStateSequence; ExpectedRevision = expectedRevision; Command = command; } public bool Equals(LedgerCommandFingerprint other) { if (Kind == other.Kind && Identity.Equals(other.Identity) && ExpectedStateSequence == other.ExpectedStateSequence && ExpectedRevision == other.ExpectedRevision) { return Command == other.Command; } return false; } public override bool Equals(object? obj) { if (obj is LedgerCommandFingerprint other) { return Equals(other); } return false; } public override int GetHashCode() { return ((((((((int)Kind * 397) ^ Identity.GetHashCode()) * 397) ^ ExpectedStateSequence.GetHashCode()) * 397) ^ ExpectedRevision.GetHashCode()) * 397) ^ (Command?.GetHashCode() ?? 0); } } private sealed class TransactionCacheEntry { internal LedgerCommandFingerprint Fingerprint { get; } internal LedgerCommandResult Result { get; } internal TransactionCacheEntry(LedgerCommandFingerprint fingerprint, LedgerCommandResult result) { Fingerprint = fingerprint; Result = result; } } private sealed class Record { internal HammerIdentity Identity { get; set; } internal Guid HammerId => Identity.HammerId; internal MjolnirAggregateState RuntimeState { get; set; } internal long StateSequence { get; set; } internal long Revision { get; set; } internal Record(HammerIdentity identity, MjolnirAggregateState runtimeState, long stateSequence, long revision) { Identity = identity; RuntimeState = runtimeState; StateSequence = stateSequence; Revision = revision; } internal LedgerSnapshot ToSnapshot() { return new LedgerSnapshot(Identity, RuntimeState, HammerRepresentationPolicy.For(RuntimeState.Hammer), StateSequence, Revision); } } private sealed class TransactionResultCache { private readonly int capacity; private readonly Dictionary<Guid, TransactionCacheEntry> results = new Dictionary<Guid, TransactionCacheEntry>(); private readonly Queue<Guid> insertionOrder = new Queue<Guid>(); internal TransactionResultCache(int capacity) { if (capacity < 1) { throw new ArgumentOutOfRangeException("capacity"); } this.capacity = capacity; } internal bool TryGet(Guid transactionId, out TransactionCacheEntry? result) { return results.TryGetValue(transactionId, out result); } internal void Add(Guid transactionId, TransactionCacheEntry result) { if (!results.ContainsKey(transactionId)) { while (results.Count >= capacity) { Guid key = insertionOrder.Dequeue(); results.Remove(key); } results.Add(transactionId, result); insertionOrder.Enqueue(transactionId); } } } private readonly object gate = new object(); private readonly TransactionResultCache transactionResults; private Record? record; public long WorldUid { get; } public bool HasIssuance { get { lock (gate) { return record != null; } } } public HammerLedger(long worldUid, int transactionCacheCapacity = 512) { if (worldUid == 0L) { throw new ArgumentOutOfRangeException("worldUid", "A ledger must be bound to a nonzero world UID."); } WorldUid = worldUid; transactionResults = new TransactionResultCache(transactionCacheCapacity); } public LedgerCommandResult Issue(HammerIdentity identity, Guid transactionId) { lock (gate) { LedgerCommandFingerprint fingerprint = new LedgerCommandFingerprint(LedgerCommandKind.Issue, identity, 0L, 0L, null); if (TryReplay(transactionId, fingerprint, out LedgerCommandResult result)) { return result; } if (transactionId == Guid.Empty) { return LedgerCommandResult.Reject(TransitionFailure.InvalidTransactionId, null); } if (identity.LedgerWorldUid != WorldUid) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.WorldMismatch, record?.ToSnapshot()), fingerprint); } if (record != null) { TransitionFailure failure = ((record.HammerId == identity.HammerId) ? TransitionFailure.DuplicateHammer : TransitionFailure.WorldAlreadyIssued); return Cache(transactionId, LedgerCommandResult.Reject(failure, record.ToSnapshot()), fingerprint); } record = new Record(identity, MjolnirAggregateState.Initial, 0L, 1L); return Cache(transactionId, LedgerCommandResult.Accept(record.ToSnapshot()), fingerprint); } } public LedgerCommandResult Transition(HammerIdentity identity, long expectedStateSequence, long expectedRevision, MjolnirCommand command, Guid transactionId) { checked { lock (gate) { LedgerCommandFingerprint fingerprint = new LedgerCommandFingerprint(LedgerCommandKind.Transition, identity, expectedStateSequence, expectedRevision, command); if (TryReplay(transactionId, fingerprint, out LedgerCommandResult result)) { return result; } if (transactionId == Guid.Empty) { return LedgerCommandResult.Reject(TransitionFailure.InvalidTransactionId, null); } if (identity.LedgerWorldUid != WorldUid) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.WorldMismatch, record?.ToSnapshot()), fingerprint); } if (record == null) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.NoIssuance, null), fingerprint); } if (record.Identity.HammerId != identity.HammerId) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.UnknownHammer, record.ToSnapshot()), fingerprint); } if (record.Identity.Epoch != identity.Epoch) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.EpochMismatch, record.ToSnapshot()), fingerprint); } if (record.StateSequence != expectedStateSequence) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.SequenceMismatch, record.ToSnapshot()), fingerprint); } if (record.Revision != expectedRevision) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.RevisionMismatch, record.ToSnapshot()), fingerprint); } MjolnirCommandDecision mjolnirCommandDecision = MjolnirAggregateStateMachine.Evaluate(record.RuntimeState, command); if (!mjolnirCommandDecision.Accepted) { return Cache(transactionId, LedgerCommandResult.Reject(mjolnirCommandDecision.Failure, record.ToSnapshot()), fingerprint); } record.RuntimeState = mjolnirCommandDecision.Target; record.StateSequence += 1; record.Revision += 1; return Cache(transactionId, LedgerCommandResult.Accept(record.ToSnapshot()), fingerprint); } } } public static LedgerRestoreResult TryRestore(LedgerSnapshot? snapshot, int transactionCacheCapacity = 512) { if (snapshot == null) { return LedgerRestoreResult.Reject(TransitionFailure.InvalidSnapshot); } if (snapshot.Schema != 1) { return LedgerRestoreResult.Reject(TransitionFailure.UnsupportedSchema); } HammerIdentity identity = snapshot.Identity; if (identity.HammerId == Guid.Empty || identity.Epoch < 0 || identity.LedgerWorldUid == 0L || snapshot.StateSequence < 0 || snapshot.Revision <= 0 || !MjolnirAggregateStateMachine.IsValid(snapshot.RuntimeState)) { return LedgerRestoreResult.Reject(TransitionFailure.InvalidSnapshot); } HammerRepresentation hammerRepresentation; try { hammerRepresentation = HammerRepresentationPolicy.For(snapshot.RuntimeState.Hammer); } catch (ArgumentOutOfRangeException) { return LedgerRestoreResult.Reject(TransitionFailure.InvalidSnapshot); } if (snapshot.Representation != hammerRepresentation) { return LedgerRestoreResult.Reject(TransitionFailure.InvalidSnapshot); } return LedgerRestoreResult.Accept(new HammerLedger(identity.LedgerWorldUid, transactionCacheCapacity) { record = new Record(identity, snapshot.RuntimeState, snapshot.StateSequence, snapshot.Revision) }); } public LedgerCommandResult AdvanceEpochForRecovery(HammerIdentity expectedIdentity, long expectedRevision, Guid transactionId) { lock (gate) { LedgerCommandFingerprint fingerprint = new LedgerCommandFingerprint(LedgerCommandKind.AdvanceEpochForRecovery, expectedIdentity, 0L, expectedRevision, null); if (TryReplay(transactionId, fingerprint, out LedgerCommandResult result)) { return result; } if (transactionId == Guid.Empty) { return LedgerCommandResult.Reject(TransitionFailure.InvalidTransactionId, null); } if (expectedIdentity.LedgerWorldUid != WorldUid) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.WorldMismatch, record?.ToSnapshot()), fingerprint); } if (record == null) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.NoIssuance, null), fingerprint); } if (record.Identity.HammerId != expectedIdentity.HammerId) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.UnknownHammer, record.ToSnapshot()), fingerprint); } if (record.Identity.Epoch != expectedIdentity.Epoch) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.EpochMismatch, record.ToSnapshot()), fingerprint); } if (record.Revision != expectedRevision) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.RevisionMismatch, record.ToSnapshot()), fingerprint); } if (record.RuntimeState.Hammer != MjolnirHammerState.Unavailable) { return Cache(transactionId, LedgerCommandResult.Reject(TransitionFailure.InvalidTransition, record.ToSnapshot()), fingerprint); } record.Identity = record.Identity.NextEpoch(); record.RuntimeState = MjolnirAggregateState.Initial; record.StateSequence = 0L; record.Revision = checked(record.Revision + 1); return Cache(transactionId, LedgerCommandResult.Accept(record.ToSnapshot()), fingerprint); } } public bool TryGet(out LedgerSnapshot? snapshot) { lock (gate) { if (record != null) { snapshot = record.ToSnapshot(); return true; } snapshot = null; return false; } } private bool TryReplay(Guid transactionId, LedgerCommandFingerprint fingerprint, out LedgerCommandResult? result) { if (transactionId != Guid.Empty && transactionResults.TryGet(transactionId, out TransactionCacheEntry result2)) { result = (result2.Fingerprint.Equals(fingerprint) ? result2.Result.AsReplay() : LedgerCommandResult.Reject(TransitionFailure.TransactionConflict, record?.ToSnapshot())); return true; } result = null; return false; } private LedgerCommandResult Cache(Guid transactionId, LedgerCommandResult result, LedgerCommandFingerprint fingerprint) { transactionResults.Add(transactionId, new TransactionCacheEntry(fingerprint, result)); return result; } } public enum HammerRepresentation { InventoryToken = 0, WorldItemDrop = 1, InventoryTokenAndLocalActor = 2, Quarantined = 3, [Obsolete("Use WorldItemDrop.")] WorldActor = 1, [Obsolete("Use InventoryTokenAndLocalActor.")] InventoryTokenAndWorldActor = 2 } public static class HammerRepresentationPolicy { public static HammerRepresentation For(MjolnirHammerState state) { switch (state) { case MjolnirHammerState.Stored: case MjolnirHammerState.Held: case MjolnirHammerState.ThrowCharging: case MjolnirHammerState.ReleasePending: return HammerRepresentation.InventoryToken; case MjolnirHammerState.Outbound: case MjolnirHammerState.Embedded: case MjolnirHammerState.Returning: case MjolnirHammerState.Catching: case MjolnirHammerState.CrosshairControlled: case MjolnirHammerState.Levitating: case MjolnirHammerState.Planted: case MjolnirHammerState.Orbiting: case MjolnirHammerState.EmbeddedInEnemy: return HammerRepresentation.InventoryTokenAndLocalActor; case MjolnirHammerState.Dropped: return HammerRepresentation.WorldItemDrop; case MjolnirHammerState.Unavailable: return HammerRepresentation.Quarantined; default: throw new ArgumentOutOfRangeException("state", state, "Unknown hammer state."); } } } public sealed class LedgerCommandResult { public bool Accepted { get; } public bool IsReplay { get; } public TransitionFailure Failure { get; } public LedgerSnapshot? Snapshot { get; } private LedgerCommandResult(bool accepted, bool replay, TransitionFailure failure, LedgerSnapshot? snapshot) { Accepted = accepted; IsReplay = replay; Failure = failure; Snapshot = snapshot; } internal static LedgerCommandResult Accept(LedgerSnapshot snapshot, bool replay = false) { return new LedgerCommandResult(accepted: true, replay, TransitionFailure.None, snapshot); } internal static LedgerCommandResult Reject(TransitionFailure failure, LedgerSnapshot? snapshot, bool replay = false) { return new LedgerCommandResult(accepted: false, replay, failure, snapshot); } internal LedgerCommandResult AsReplay() { if (!Accepted) { return Reject(Failure, Snapshot, replay: true); } return Accept(Snapshot, replay: true); } } public sealed class LedgerRestoreResult { public bool Accepted { get; } public TransitionFailure Failure { get; } public HammerLedger? Ledger { get; } private LedgerRestoreResult(bool accepted, TransitionFailure failure, HammerLedger? ledger) { Accepted = accepted; Failure = failure; Ledger = ledger; } internal static LedgerRestoreResult Accept(HammerLedger ledger) { return new LedgerRestoreResult(accepted: true, TransitionFailure.None, ledger); } internal static LedgerRestoreResult Reject(TransitionFailure failure) { return new LedgerRestoreResult(accepted: false, failure, null); } } public sealed class LedgerSnapshot { public const int CurrentSchema = 1; public int Schema { get; } public HammerIdentity Identity { get; } public MjolnirAggregateState RuntimeState { get; } public HammerRepresentation Representation { get; } public long StateSequence { get; } public long Revision { get; } public LedgerSnapshot(HammerIdentity identity, MjolnirAggregateState runtimeState, HammerRepresentation representation, long stateSequence, long revision, int schema = 1) { Schema = schema; Identity = identity; RuntimeState = runtimeState; Representation = representation; StateSequence = stateSequence; Revision = revision; } } } namespace ValheimMjolnir.Core.Net { public enum MjolnirAbilityCode : byte { None, Throw, Lightning, Flight, GroundSlam, CrosshairControl, Summon, ThunderClap, StormLine, Cyclone, ChainLightning, LightningPillar, AssistedLeap, FlightCharge, Heavenfall, GodOfThunder, OverheadSlam, SummonStorm, MultiAngleBarrage, Assault, LightningStep, Guard, ThunderGodOrbit, CloneStorm } public static class MjolnirAbilityCodes { public static MjolnirAbilityCode FromId(string? abilityId) { return abilityId switch { "mjolnir.throw" => MjolnirAbilityCode.Throw, "mjolnir.lightning" => MjolnirAbilityCode.Lightning, "mjolnir.flight" => MjolnirAbilityCode.Flight, "mjolnir.groundslam" => MjolnirAbilityCode.GroundSlam, "mjolnir.crosshaircontrol" => MjolnirAbilityCode.CrosshairControl, "mjolnir.summon" => MjolnirAbilityCode.Summon, "mjolnir.thunderclap" => MjolnirAbilityCode.ThunderClap, "mjolnir.stormline" => MjolnirAbilityCode.StormLine, "mjolnir.cyclone" => MjolnirAbilityCode.Cyclone, "mjolnir.chainlightning" => MjolnirAbilityCode.ChainLightning, "mjolnir.lightningpillar" => MjolnirAbilityCode.LightningPillar, "mjolnir.assistedleap" => MjolnirAbilityCode.AssistedLeap, "mjolnir.flightcharge" => MjolnirAbilityCode.FlightCharge, "mjolnir.heavenfall" => MjolnirAbilityCode.Heavenfall, "mjolnir.godofthunder" => MjolnirAbilityCode.GodOfThunder, "mjolnir.overheadslam" => MjolnirAbilityCode.OverheadSlam, "mjolnir.summonstorm" => MjolnirAbilityCode.SummonStorm, "mjolnir.multianglebarrage" => MjolnirAbilityCode.MultiAngleBarrage, "mjolnir.assault" => MjolnirAbilityCode.Assault, "mjolnir.lightningstep" => MjolnirAbilityCode.LightningStep, "mjolnir.guard" => MjolnirAbilityCode.Guard, "mjolnir.thundergodorbit" => MjolnirAbilityCode.ThunderGodOrbit, "mjolnir.clonestorm" => MjolnirAbilityCode.CloneStorm, _ => MjolnirAbilityCode.None, }; } public static string? ToId(MjolnirAbilityCode code) { return code switch { MjolnirAbilityCode.Throw => "mjolnir.throw", MjolnirAbilityCode.Lightning => "mjolnir.lightning", MjolnirAbilityCode.Flight => "mjolnir.flight", MjolnirAbilityCode.GroundSlam => "mjolnir.groundslam", MjolnirAbilityCode.CrosshairControl => "mjolnir.crosshaircontrol", MjolnirAbilityCode.Summon => "mjolnir.summon", MjolnirAbilityCode.ThunderClap => "mjolnir.thunderclap", MjolnirAbilityCode.StormLine => "mjolnir.stormline", MjolnirAbilityCode.Cyclone => "mjolnir.cyclone", MjolnirAbilityCode.ChainLightning => "mjolnir.chainlightning", MjolnirAbilityCode.LightningPillar => "mjolnir.lightningpillar", MjolnirAbilityCode.AssistedLeap => "mjolnir.assistedleap", MjolnirAbilityCode.FlightCharge => "mjolnir.flightcharge", MjolnirAbilityCode.Heavenfall => "mjolnir.heavenfall", MjolnirAbilityCode.GodOfThunder => "mjolnir.godofthunder", MjolnirAbilityCode.OverheadSlam => "mjolnir.overheadslam", MjolnirAbilityCode.SummonStorm => "mjolnir.summonstorm", MjolnirAbilityCode.MultiAngleBarrage => "mjolnir.multianglebarrage", MjolnirAbilityCode.Assault => "mjolnir.assault", MjolnirAbilityCode.LightningStep => "mjolnir.lightningstep", MjolnirAbilityCode.Guard => "mjolnir.guard", MjolnirAbilityCode.ThunderGodOrbit => "mjolnir.thundergodorbit", MjolnirAbilityCode.CloneStorm => "mjolnir.clonestorm", _ => null, }; } public static bool IsKnown(MjolnirAbilityCode code) { return ToId(code) != null; } } public static class MjolnirNetAdmissionPolicy { public static NetAdmissionResult Evaluate(in MjolnirNetMessage message, in NetPeerContext context, bool ownsLocally) { if (!context.IsNetworked) { return NetAdmissionResult.Reject(NetRejectionReason.NotNetworked); } if (message.Kind == MjolnirNetMessageKind.None) { return NetAdmissionResult.Reject(NetRejectionReason.UnknownKind); } bool num = MjolnirNetProtocol.IsSessionScoped(message.Kind); bool flag = message.Hammer.HammerId == MjolnirNetProtocol.SessionHammerId; if (num != flag) { return NetAdmissionResult.Reject(NetRejectionReason.ScopeMismatch); } if (message.Hammer.LedgerWorldUid != context.WorldUid) { return NetAdmissionResult.Reject(NetRejectionReason.WrongWorld); } if (message.SenderPeerId == context.LocalPeerId) { return NetAdmissionResult.Reject(NetRejectionReason.SelfEcho); } if (MjolnirNetProtocol.IsDiscrete(message.Kind) && message.ActivationId == 0L) { return NetAdmissionResult.Reject(NetRejectionReason.MissingActivationId); } if (message.Kind == MjolnirNetMessageKind.AbilityTriggered && !MjolnirAbilityCodes.IsKnown(message.Ability)) { return NetAdmissionResult.Reject(NetRejectionReason.UnknownAbility); } if (message.Kind == MjolnirNetMessageKind.OwnershipTransferred) { if (message.SenderPeerId != context.ServerPeerId) { return NetAdmissionResult.Reject(NetRejectionReason.ServerOnly); } return NetAdmissionResult.Accept(); } if (message.Kind == MjolnirNetMessageKind.SnapshotRequest) { return NetAdmissionResult.Accept(); } if (message.SenderPeerId != message.OwnerPeerId) { return NetAdmissionResult.Reject(NetRejectionReason.NotOwner); } if (ownsLocally) { return NetAdmissionResult.Reject(NetRejectionReason.ForeignClaimOnLocalHammer); } if (!context.HasLocalPlayer) { return NetAdmissionResult.Reject(NetRejectionReason.NoLocalPlayer); } return NetAdmissionResult.Accept(); } } public enum NetDecodeFailure { None, MalformedLength, Truncated, VersionMismatch, FieldOutOfRange, NonFiniteGeometry } public readonly struct NetDecodeResult { public bool Success { get; } public MjolnirNetMessage Message { get; } public NetDecodeFailure Failure { get; } private NetDecodeResult(bool success, MjolnirNetMessage message, NetDecodeFailure failure) { Success = success; Message = message; Failure = failure; } public static NetDecodeResult Ok(MjolnirNetMessage message) { return new NetDecodeResult(success: true, message, NetDecodeFailure.None); } public static NetDecodeResult Fail(NetDecodeFailure failure) { return new NetDecodeResult(success: false, default(MjolnirNetMessage), failure); } } public static class MjolnirNetCodec { [StructLayout(LayoutKind.Explicit)] private readonly struct FloatBits { [FieldOffset(0)] private readonly int bits; [FieldOffset(0)] private readonly float value; internal int Bits => bits; internal float Value => value; internal FloatBits(float value) { bits = 0; this.value = value; } internal FloatBits(int bits) { value = 0f; this.bits = bits; } } public const int MessageBytes = 139; private const MjolnirNetFlags AllFlags = MjolnirNetFlags.Embedded | MjolnirNetFlags.Grounded | MjolnirNetFlags.PlayerFlight | MjolnirNetFlags.LateJoinReplay; public static int Encode(in MjolnirNetMessage message, byte[] buffer) { if (buffer == null) { throw new ArgumentNullException("buffer"); } if (buffer.Length < 139) { throw new ArgumentException($"Buffer must hold at least {139} bytes.", "buffer"); } int offset = 0; WriteUInt16(buffer, ref offset, message.Version); buffer[offset++] = (byte)message.Kind; WriteGuid(buffer, ref offset, message.Hammer.HammerId); WriteInt32(buffer, ref offset, message.Hammer.Epoch); WriteInt64(buffer, ref offset, message.Hammer.LedgerWorldUid); WriteInt64(buffer, ref offset, message.SenderPeerId); WriteInt64(buffer, ref offset, message.OwnerPeerId); WriteInt64(buffer, ref offset, message.Sequence); WriteDouble(buffer, ref offset, message.SentAtSeconds); buffer[offset++] = (byte)message.State; buffer[offset++] = (byte)message.Flags; WriteVector(buffer, ref offset, message.Position); WriteSingle(buffer, ref offset, message.Rotation.X); WriteSingle(buffer, ref offset, message.Rotation.Y); WriteSingle(buffer, ref offset, message.Rotation.Z); WriteSingle(buffer, ref offset, message.Rotation.W); WriteVector(buffer, ref offset, message.Velocity); WriteVector(buffer, ref offset, message.Target); WriteInt64(buffer, ref offset, message.ActivationId); buffer[offset++] = (byte)message.Ability; WriteSingle(buffer, ref offset, message.Magnitude); buffer[offset++] = message.Detail; WriteInt64(buffer, ref offset, message.Claimant); return offset; } public static NetDecodeResult Decode(byte[]? buffer, int length) { if (buffer == null || length <= 0 || length > 256 || length > buffer.Length) { return NetDecodeResult.Fail(NetDecodeFailure.MalformedLength); } if (length < 139) { return NetDecodeResult.Fail(NetDecodeFailure.Truncated); } int offset = 0; ushort num = ReadUInt16(buffer, ref offset); if (num != 2) { return NetDecodeResult.Fail(NetDecodeFailure.VersionMismatch); } byte b = buffer[offset++]; if (!IsDefinedKind(b)) { return NetDecodeResult.Fail(NetDecodeFailure.FieldOutOfRange); } Guid guid = ReadGuid(buffer, ref offset); int num2 = ReadInt32(buffer, ref offset); long ledgerWorldUid = ReadInt64(buffer, ref offset); if (guid == Guid.Empty || num2 < 0) { return NetDecodeResult.Fail(NetDecodeFailure.FieldOutOfRange); } long senderPeerId = ReadInt64(buffer, ref offset); long ownerPeerId = ReadInt64(buffer, ref offset); long sequence = ReadInt64(buffer, ref offset); double sentAtSeconds = ReadDouble(buffer, ref offset); byte b2 = buffer[offset++]; byte b3 = buffer[offset++]; if (!IsDefinedState(b2) || b3 > 15) { return NetDecodeResult.Fail(NetDecodeFailure.FieldOutOfRange); } FlightVector position = ReadVector(buffer, ref offset); NetRotation rotation = new NetRotation(ReadSingle(buffer, ref offset), ReadSingle(buffer, ref offset), ReadSingle(buffer, ref offset), ReadSingle(buffer, ref offset)); FlightVector velocity = ReadVector(buffer, ref offset); FlightVector target = ReadVector(buffer, ref offset); long activationId = ReadInt64(buffer, ref offset); byte ability = buffer[offset++]; float magnitude = ReadSingle(buffer, ref offset); byte detail = buffer[offset++]; long claimant = ReadInt64(buffer, ref offset); MjolnirNetMessage message = new MjolnirNetMessage(num, (MjolnirNetMessageKind)b, new HammerIdentity(guid, num2, ledgerWorldUid), senderPeerId, ownerPeerId, sequence, sentAtSeconds, (MjolnirState)b2, (MjolnirNetFlags)b3, position, rotation, velocity, target, activationId, (MjolnirAbilityCode)ability, magnitude, detail, claimant); if (!message.HasFiniteGeometry || !rotation.IsValid) { return NetDecodeResult.Fail(NetDecodeFailure.NonFiniteGeometry); } return NetDecodeResult.Ok(message); } private static bool IsDefinedKind(byte value) { if (value > 0) { return value <= 14; } return false; } private static bool IsDefinedState(byte value) { return value <= 19; } private static void WriteUInt16(byte[] buffer, ref int offset, ushort value) { buffer[offset++] = (byte)value; buffer[offset++] = (byte)(value >> 8); } private static ushort ReadUInt16(byte[] buffer, ref int offset) { ushort result = (ushort)(buffer[offset] | (buffer[offset + 1] << 8)); offset += 2; return result; } private static void WriteInt32(byte[] buffer, ref int offset, int value) { buffer[offset++] = (byte)value; buffer[offset++] = (byte)(value >> 8); buffer[offset++] = (byte)(value >> 16); buffer[offset++] = (byte)(value >> 24); } private static int ReadInt32(byte[] buffer, ref int offset) { int result = buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24); offset += 4; return result; } private static void WriteInt64(byte[] buffer, ref int offset, long value) { for (int i = 0; i < 64; i += 8) { buffer[offset++] = (byte)(value >> i); } } private static long ReadInt64(byte[] buffer, ref int offset) { long num = 0L; for (int i = 0; i < 8; i++) { num |= (long)((ulong)buffer[offset + i] << i * 8); } offset += 8; return num; } private static void WriteSingle(byte[] buffer, ref int offset, float value) { WriteInt32(buffer, ref offset, SingleToBits(value)); } private static float ReadSingle(byte[] buffer, ref int offset) { return BitsToSingle(ReadInt32(buffer, ref offset)); } private static void WriteDouble(byte[] buffer, ref int offset, double value) { WriteInt64(buffer, ref offset, BitConverter.DoubleToInt64Bits(value)); } private static double ReadDouble(byte[] buffer, ref int offset) { return BitConverter.Int64BitsToDouble(ReadInt64(buffer, ref offset)); } private static void WriteVector(byte[] buffer, ref int offset, FlightVector value) { WriteSingle(buffer, ref offset, value.X); WriteSingle(buffer, ref offset, value.Y); WriteSingle(buffer, ref offset, value.Z); } private static FlightVector ReadVector(byte[] buffer, ref int offset) { return new FlightVector(ReadSingle(buffer, ref offset), ReadSingle(buffer, ref offset), ReadSingle(buffer, ref offset)); } private static void WriteGuid(byte[] buffer, ref int offset, Guid value) { byte[] array = value.ToByteArray(); for (int i = 0; i < 16; i++) { buffer[offset + i] = array[i]; } offset += 16; } private static Guid ReadGuid(byte[] buffer, ref int offset) { int a = buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24); short b = (short)(buffer[offset + 4] | (buffer[offset + 5] << 8)); short c = (short)(buffer[offset + 6] | (buffer[offset + 7] << 8)); Guid result = new Guid(a, b, c, buffer[offset + 8], buffer[offset + 9], buffer[offset + 10], buffer[offset + 11], buffer[offset + 12], buffer[offset + 13], buffer[offset + 14], buffer[offset + 15]); offset += 16; return result; } private static int SingleToBits(float value) { return new FloatBits(value).Bits; } private static float BitsToSingle(int bits) { return new FloatBits(bits).Value; } } [Flags] public enum MjolnirNetFlags : byte { None = 0, Embedded = 1, Grounded = 2, PlayerFlight = 4, LateJoinReplay = 8 } public readonly struct MjolnirNetMessage : IEquatable<MjolnirNetMessage> { public long Claimant { get; } public ushort Version { get; } public MjolnirNetMessageKind Kind { get; } public HammerIdentity Hammer { get; } public long SenderPeerId { get; } public long OwnerPeerId { get; } public long Sequence { get; } public double SentAtSeconds { get; } public MjolnirState State { get; } public MjolnirNetFlags Flags { get; } public FlightVector Position { get; } public NetRotation Rotation { get; } public FlightVector Velocity { get; } public FlightVector Target { get; } public long ActivationId { get; } public MjolnirAbilityCode Ability { get; } public float Magnitude { get; } public byte Detail { get; } public bool HasFiniteGeometry { get { if (IsFinite(Position) && IsFinite(Velocity) && IsFinite(Target) && !float.IsNaN(Magnitude) && !float.IsInfinity(Magnitude) && !double.IsNaN(SentAtSeconds)) { return !double.IsInfinity(SentAtSeconds); } return false; } } public MjolnirNetMessage(ushort version, MjolnirNetMessageKind kind, HammerIdentity hammer, long senderPeerId, long ownerPeerId, long sequence, double sentAtSeconds, MjolnirState state, MjolnirNetFlags flags, FlightVector position, NetRotation rotation, FlightVector velocity, FlightVector target, long activationId, MjolnirAbilityCode ability, float magnitude, byte detail = 0, long claimant = 0L) { Version = version; Kind = kind; Hammer = hammer; SenderPeerId = senderPeerId; OwnerPeerId = ownerPeerId; Sequence = sequence; SentAtSeconds = sentAtSeconds; State = state; Flags = flags; Position = position; Rotation = rotation; Velocity = velocity; Target = target; ActivationId = activationId; Ability = ability; Magnitude = magnitude; Detail = detail; Claimant = claimant; } public MjolnirNetMessage WithFlags(MjolnirNetFlags flags) { return new MjolnirNetMessage(Version, Kind, Hammer, SenderPeerId, OwnerPeerId, Sequence, SentAtSeconds, State, flags, Position, Rotation, Velocity, Target, ActivationId, Ability, Magnitude, Detail, 0L); } public MjolnirNetMessage WithKind(MjolnirNetMessageKind kind, long sequence) { return new MjolnirNetMessage(Version, kind, Hammer, SenderPeerId, OwnerPeerId, sequence, SentAtSeconds, State, Flags, Position, Rotation, Velocity, Target, ActivationId, Ability, Magnitude, Detail, 0L); } public bool Equals(MjolnirNetMessage other) { if (Version == other.Version && Kind == other.Kind && Hammer.Equals(other.Hammer) && SenderPeerId == other.SenderPeerId && OwnerPeerId == other.OwnerPeerId && Sequence == other.Sequence && SentAtSeconds.Equals(other.SentAtSeconds) && State == other.State && Flags == other.Flags && Position.Equals(other.Position) && Rotation.Equals(other.Rotation) && Velocity.Equals(other.Velocity) && Target.Equals(other.Target) && ActivationId == other.ActivationId && Ability == other.Ability && Magnitude.Equals(other.Magnitude) && Detail == other.Detail) { return Claimant == other.Claimant; } return false; } public override bool Equals(object? obj) { if (obj is MjolnirNetMessage other) { return Equals(other); } return false; } public override int GetHashCode() { return ((((((((int)Kind * 397) ^ Hammer.GetHashCode()) * 397) ^ SenderPeerId.GetHashCode()) * 397) ^ Sequence.GetHashCode()) * 397) ^ ActivationId.GetHashCode(); } public override string ToString() { return $"{Kind} hammer={Hammer} owner={OwnerPeerId} sender={SenderPeerId} seq={Sequence} state={State}"; } private static bool IsFinite(FlightVector value) { if (!float.IsNaN(value.X) && !float.IsInfinity(value.X) && !float.IsNaN(value.Y) && !float.IsInfinity(value.Y) && !float.IsNaN(value.Z)) { return !float.IsInfinity(value.Z); } return false; } } public enum MjolnirNetMessageKind : byte { None, SnapshotRequest, Snapshot, HammerState, ThrowCommitted, RecallStarted, HammerCaught, ImpactConfirmed, AbilityTriggered, FlightEntered, FlightExited, HammerReleased, OwnershipTransferred, HammerPresence, ReclaimRequested } public static class MjolnirNetProtocol { public const ushort Version = 2; public const string RpcName = "ValheimMjolnir.Net.v1"; public const int MaximumMessageBytes = 256; public const float StateBroadcastInterval = 0.1f; public const float MirrorStaleSeconds = 1f; public const float PresenceBroadcastInterval = 2f; public const float PresenceStaleSeconds = 7f; public const long NoPeer = 0L; public const long EveryPeer = 0L; public static readonly Guid SessionHammerId = new Guid("6a1d0f4c-9b3e-4a27-8f51-0e1c7b6d24aa"); public static bool IsSessionScoped(MjolnirNetMessageKind kind) { if (kind == MjolnirNetMessageKind.SnapshotRequest || kind == MjolnirNetMessageKind.ReclaimRequested) { return true; } return false; } public static bool IsDiscrete(MjolnirNetMessageKind kind) { return kind switch { MjolnirNetMessageKind.ThrowCommitted => true, MjolnirNetMessageKind.RecallStarted => true, MjolnirNetMessageKind.HammerCaught => true, MjolnirNetMessageKind.ImpactConfirmed => true, MjolnirNetMessageKind.AbilityTriggered => true, MjolnirNetMessageKind.FlightEntered => true, MjolnirNetMessageKind.FlightExited => true, MjolnirNetMessageKind.OwnershipTransferred => true, MjolnirNetMessageKind.ReclaimRequested => true, _ => false, }; } } public sealed class MjolnirNetSession { private readonly struct PresenceRecord { internal long OwnerPeerId { get; } internal double UpdatedAtSeconds { get; } internal PresenceRecord(long ownerPeerId, double updatedAtSeconds) { OwnerPeerId = ownerPeerId; UpdatedAtSeconds = updatedAtSeconds; } } private readonly Dictionary<HammerIdentity, RemoteHammerSnapshot> mirrors = new Dictionary<HammerIdentity, RemoteHammerSnapshot>(); private readonly Dictionary<HammerIdentity, PresenceRecord> presence = new Dictionary<HammerIdentity, PresenceRecord>(); private readonly Dictionary<HammerIdentity, long> outboundSequences = new Dictionary<HammerIdentity, long>(); private readonly NetSequenceGate sequences = new NetSequenceGate(); private readonly NetActivationGate activations; private NetPeerContext context; private long nextActivationId; public NetPeerContext Context => context; public int MirrorCount => mirrors.Count; public int RemoteHammerCount => presence.Count; public IEnumerable<RemoteHammerSnapshot> Mirrors => mirrors.Values; public event Action<MjolnirNetMessage, NetRejectionReason>? Rejected; public MjolnirNetSession(NetPeerContext context, int activationMemory = 256) { this.context = context; activations = new NetActivationGate(activationMemory); } public void UpdateContext(NetPeerContext updated) { bool num = updated.WorldUid != context.WorldUid || updated.LocalPeerId != context.LocalPeerId || updated.Role != context.Role; context = updated; if (num) { Reset(); } } public void Reset() { mirrors.Clear(); presence.Clear(); outboundSequences.Clear(); sequences.Clear(); activations.Clear(); } public NetReceiveOutcome Receive(in MjolnirNetMessage message, double nowSeconds, bool ownsLocally) { NetAdmissionResult netAdmissionResult = MjolnirNetAdmissionPolicy.Evaluate(in message, in context, ownsLocally); if (!netAdmissionResult.Accepted) { return Refuse(in message, netAdmissionResult.Reason); } NetRejectionReason netRejectionReason = sequences.Admit(in message); if (netRejectionReason != NetRejectionReason.None) { return Refuse(in message, netRejectionReason); } if (MjolnirNetProtocol.IsDiscrete(message.Kind) && !activations.TryClaim(message.ActivationId)) { return Refuse(in message, NetRejectionReason.Duplicate); } if (!MjolnirNetProtocol.IsSessionScoped(message.Kind) && message.OwnerPeerId != context.LocalPeerId) { presence[message.Hammer] = new PresenceRecord(message.OwnerPeerId, nowSeconds); } switch (message.Kind) { case MjolnirNetMessageKind.SnapshotRequest: return NetReceiveOutcome.Act(NetReceiveAction.AnswerSnapshotRequest); case MjolnirNetMessageKind.ReclaimRequested: return NetReceiveOutcome.Act(NetReceiveAction.ReleaseReclaimedHammer); case MjolnirNetMessageKind.HammerPresence: return NetReceiveOutcome.Act(NetReceiveAction.None); case MjolnirNetMessageKind.Snapshot: case MjolnirNetMessageKind.HammerState: return ApplyState(in message, nowSeconds, NetReceiveAction.UpdateMirror); case MjolnirNetMessageKind.ThrowCommitted: return ApplyState(in message, nowSeconds, NetReceiveAction.PlayThrow); case MjolnirNetMessageKind.RecallStarted: return ApplyState(in message, nowSeconds, NetReceiveAction.PlayRecall); case MjolnirNetMessageKind.HammerCaught: Forget(message.Hammer); return NetReceiveOutcome.Act(NetReceiveAction.PlayCatch); case MjolnirNetMessageKind.HammerReleased: Forget(message.Hammer); return NetReceiveOutcome.Act(NetReceiveAction.RemoveMirror); case MjolnirNetMessageKind.ImpactConfirmed: ApplyState(in message, nowSeconds, NetReceiveAction.UpdateMirror); return NetReceiveOutcome.Act(NetReceiveAction.PlayImpact); case MjolnirNetMessageKind.AbilityTriggered: return NetReceiveOutcome.Act(NetReceiveAction.PlayAbility); case MjolnirNetMessageKind.FlightEntered: return ApplyState(in message, nowSeconds, NetReceiveAction.BeginRemoteFlight); case MjolnirNetMessageKind.FlightExited: return ApplyState(in message, nowSeconds, NetReceiveAction.EndRemoteFlight); case MjolnirNetMessageKind.OwnershipTransferred: TransferOwnership(in message, nowSeconds); return NetReceiveOutcome.Act(NetReceiveAction.ApplyOwnershipTransfer); default: return Refuse(in message, NetRejectionReason.UnknownKind); } } public bool TryGetMirror(HammerIdentity hammer, out RemoteHammerSnapshot snapshot) { return mirrors.TryGetValue(hammer, out snapshot); } public int SweepStaleMirrors(double nowSeconds, List<HammerIdentity> evicted) { if (evicted == null) { throw new ArgumentNullException("evicted"); } evicted.Clear(); foreach (KeyValuePair<HammerIdentity, RemoteHammerSnapshot> mirror in mirrors) { if (nowSeconds - mirror.Value.UpdatedAtSeconds >= 1.0) { evicted.Add(mirror.Key); } } for (int i = 0; i < evicted.Count; i++) { Forget(evicted[i]); } return evicted.Count; } public int ForgetPeer(long peerId, List<HammerIdentity> evicted) { if (evicted == null) { throw new ArgumentNullException("evicted"); } evicted.Clear(); foreach (KeyValuePair<HammerIdentity, RemoteHammerSnapshot> mirror in mirrors) { if (mirror.Value.OwnerPeerId == peerId) { evicted.Add(mirror.Key); } } for (int i = 0; i < evicted.Count; i++) { mirrors.Remove(evicted[i]); } ForgetPeerPresence(peerId); sequences.ForgetSender(peerId); return evicted.Count; } public int SweepStalePresence(double nowSeconds, List<HammerIdentity> evicted) { if (evicted == null) { throw new ArgumentNullException("evicted"); } evicted.Clear(); foreach (KeyValuePair<HammerIdentity, PresenceRecord> item in presence) { if (nowSeconds - item.Value.UpdatedAtSeconds >= 7.0) { evicted.Add(item.Key); } } for (int i = 0; i < evicted.Count; i++) { presence.Remove(evicted[i]); } return evicted.Count; } public bool ForgetPresence(HammerIdentity hammer) { return presence.Remove(hammer); } private void ForgetPeerPresence(long peerId) { List<HammerIdentity> list = null; foreach (KeyValuePair<HammerIdentity, PresenceRecord> item in presence) { if (item.Value.OwnerPeerId == peerId) { (list ?? (list = new List<HammerIdentity>())).Add(item.Key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { presence.Remove(list[i]); } } } public MjolnirNetMessage BuildSnapshotRequest(double nowSeconds) { HammerIdentity hammer = new HammerIdentity(MjolnirNetProtocol.SessionHammerId, 0, context.WorldUid); return new MjolnirNetMessage(2, MjolnirNetMessageKind.SnapshotRequest, hammer, context.LocalPeerId, 0L, NextSequence(hammer), nowSeconds, MjolnirState.Unavailable, MjolnirNetFlags.None, FlightVector.Zero, NetRotation.Identity, FlightVector.Zero, FlightVector.Zero, 0L, MjolnirAbilityCode.None, 0f, 0, 0L); } public MjolnirNetMessage BuildReclaimRequest(long claimantCharacterId, double nowSeconds) { HammerIdentity hammer = new HammerIdentity(MjolnirNetProtocol.SessionHammerId, 0, context.WorldUid); return new MjolnirNetMessage(2, MjolnirNetMessageKind.ReclaimRequested, hammer, context.LocalPeerId, context.LocalPeerId, NextSequence(hammer), nowSeconds, MjolnirState.Unavailable, MjolnirNetFlags.None, FlightVector.Zero, NetRotation.Identity, FlightVector.Zero, FlightVector.Zero, NextActivationId(), MjolnirAbilityCode.None, 0f, 0, claimantCharacterId); } public MjolnirNetMessage BuildStateMessage(MjolnirNetMessageKind kind, HammerIdentity hammer, MjolnirState state, MjolnirNetFlags flags, FlightVector position, NetRotation rotation, FlightVector velocity, double nowSeconds) { if (kind == MjolnirNetMessageKind.Snapshot) { flags |= MjolnirNetFlags.LateJoinReplay; } return new MjolnirNetMessage(2, kind, hammer, context.LocalPeerId, context.LocalPeerId, NextSequence(hammer), nowSeconds, state, flags, position, rotation, velocity, FlightVector.Zero, 0L, MjolnirAbilityCode.None, 0f, 0, 0L); } public MjolnirNetMessage BuildDiscreteMessage(MjolnirNetMessageKind kind, HammerIdentity hammer, MjolnirState state, MjolnirNetFlags flags, FlightVector position, NetRotation rotation, FlightVector velocity, FlightVector target, double nowSeconds, MjolnirAbilityCode ability = MjolnirAbilityCode.None, float magnitude = 0f, byte detail = 0) { return new MjolnirNetMessage(2, kind, hammer, context.LocalPeerId, context.LocalPeerId, NextSequence(hammer), nowSeconds, state, flags, position, rotation, velocity, target, NextActivationId(), ability, magnitude, detail, 0L); } public long NextActivationId() { nextActivationId++; return ((context.LocalPeerId & 0xFFFFFF) << 40) | (nextActivationId & 0xFFFFFFFFFFL); } private long NextSequence(HammerIdentity hammer) { outboundSequences.TryGetValue(hammer, out var value); value++; outboundSequences[hammer] = value; return value; } private NetReceiveOutcome ApplyState(in MjolnirNetMessage message, double nowSeconds, NetReceiveAction action) { RemoteHammerSnapshot snapshot = new RemoteHammerSnapshot(message.Hammer, message.OwnerPeerId, message.State, message.Flags, message.Position, message.Rotation.Normalized, message.Velocity, nowSeconds); if (!snapshot.IsInWorld) { Forget(message.Hammer); return NetReceiveOutcome.Act((action == NetReceiveAction.UpdateMirror) ? NetReceiveAction.RemoveMirror : action, in snapshot); } mirrors[message.Hammer] = snapshot; if ((message.Flags & MjolnirNetFlags.LateJoinReplay) != MjolnirNetFlags.None && action != NetReceiveAction.UpdateMirror) { return NetReceiveOutcome.Act(NetReceiveAction.UpdateMirror, in snapshot); } return NetReceiveOutcome.Act(action, in snapshot); } private void TransferOwnership(in MjolnirNetMessage message, double nowSeconds) { if (mirrors.TryGetValue(message.Hammer, out var value)) { if (message.OwnerPeerId == context.LocalPeerId) { Forget(message.Hammer); presence.Remove(message.Hammer); } else { mirrors[message.Hammer] = new RemoteHammerSnapshot(value.Hammer, message.OwnerPeerId, value.State, value.Flags, value.Position, value.Rotation, value.Velocity, nowSeconds); } } } private void Forget(HammerIdentity hammer) { mirrors.Remove(hammer); } private NetReceiveOutcome Refuse(in MjolnirNetMessage message, NetRejectionReason reason) { this.Rejected?.Invoke(message, reason); return NetReceiveOutcome.Reject(reason); } } public sealed class NetActivationGate { private readonly int capacity; private readonly HashSet<long> seen; private readonly Queue<long> order; public int Count => seen.Count; public NetActivationGate(int capacity = 256) { this.capacity = ((capacity < 1) ? 1 : capacity); seen = new HashSet<long>(); order = new Queue<long>(this.capacity); } public bool TryClaim(long activationId) { if (activationId == 0L || !seen.Add(activationId)) { return false; } order.Enqueue(activationId); while (order.Count > capacity) { seen.Remove(order.Dequeue()); } return true; } public bool HasSeen(long activationId) { return seen.Contains(activationId); } public void Clear() { seen.Clear(); order.Clear(); } } public enum NetRejectionReason { None, NotNetworked, UnknownKind, WrongWorld, SelfEcho, NotOwner, ForeignClaimOnLocalHammer, ServerOnly, MissingActivationId, UnknownAbility, Duplicate, Stale, ScopeMismatch, NoLocalPlayer } public readonly struct NetAdmissionResult { public bool Accepted { get; } public NetRejectionReason Reason { get; } private NetAdmissionResult(bool accepted, NetRejectionReason reason) { Accepted = accepted; Reason = reason; } public static NetAdmissionResult Accept() { return new NetAdmissionResult(accepted: true, NetRejectionReason.None); } public static NetAdmissionResult Reject(NetRejectionReason reason) { return new NetAdmissionResult(accepted: false, reason); } public override string ToString() { if (!Accepted) { return $"rejected:{Reason}"; } return "accepted"; } } public enum NetPeerRole { Standalone, Client, ListenHost, DedicatedServer } public readonly struct NetPeerContext { public NetPeerRole Role { get; } public long LocalPeerId { get; } public long ServerPeerId { get; } public long WorldUid { get; } public bool IsServer { get { NetPeerRole role = Role; if ((uint)(role - 2) <= 1u) { return true; } return false; } } public bool HasLocalPlayer { get { NetPeerRole role = Role; if ((uint)role <= 2u) { return true; } return false; } } public bool IsNetworked => Role != NetPeerRole.Standalone; public NetPeerContext(NetPeerRole role, long localPeerId, long serverPeerId, long worldUid) { Role = role; LocalPeerId = localPeerId; ServerPeerId = serverPeerId; WorldUid = worldUid; } } public enum NetReceiveAction { None, UpdateMirror, RemoveMirror, PlayThrow, PlayRecall, PlayCatch, PlayImpact, PlayAbility, BeginRemoteFlight, EndRemoteFlight, AnswerSnapshotRequest, ApplyOwnershipTransfer, ReleaseReclaimedHammer } public readonly struct NetReceiveOutcome { public bool Accepted { get; } public NetRejectionReason Reason { get; } public NetReceiveAction Action { get; } public RemoteHammerSnapshot Snapshot { get; } public bool HasSnapshot { get; } private NetReceiveOutcome(bool accepted, NetRejectionReason reason, NetReceiveAction action, RemoteHammerSnapshot snapshot, bool hasSnapshot) { Accepted = accepted; Reason = reason; Action = action; Snapshot = snapshot; HasSnapshot = hasSnapshot; } public static NetReceiveOutcome Act(NetReceiveAction action, in RemoteHammerSnapshot snapshot) { return new NetReceiveOutcome(accepted: true, NetRejectionReason.None, action, snapshot, hasSnapshot: true); } public static NetReceiveOutcome Act(NetReceiveAction action) { return new NetReceiveOutcome(accepted: true, NetRejectionReason.None, action, default(RemoteHammerSnapshot), hasSnapshot: false); } public static NetReceiveOutcome Reject(NetRejectionReason reason) { return new NetReceiveOutcome(accepted: false, reason, NetReceiveAction.None, default(RemoteHammerSnapshot), hasSnapshot: false); } public override string ToString() { if (!Accepted) { return $"rejected:{Reason}"; } return $"accepted:{Action}"; } } public readonly struct NetRotation : IEquatable<NetRotation> { public readonly float X; public readonly float Y; public readonly float Z; public readonly float W; public static NetRotation Identity => new NetRotation(0f, 0f, 0f, 1f); public bool IsValid { get { float num = X * X + Y * Y + Z * Z + W * W; if (!float.IsNaN(num) && !float.IsInfinity(num)) { return num > 1E-06f; } return false; } } public NetRotation Normalized { get { if (!IsValid) { return Identity; } float num = (float)Math.Sqrt(X * X + Y * Y + Z * Z + W * W); return new NetRotation(X / num, Y / num, Z / num, W / num); } } public NetRotation(float x, float y, float z, float w) { X = x; Y = y; Z = z; W = w; } public bool Equals(NetRotation other) { float x = X; if (x.Equals(other.X)) { x = Y; if (x.Equals(other.Y)) { x = Z; if (x.Equals(other.Z)) { x = W; return x.Equals(other.W); } } } return false; } public override bool Equals(object? obj) { if (obj is NetRotation other) { return Equals(other); } return false; } public override int GetHashCode() { float x = X; int num = x.GetHashCode() * 397; x = Y; int num2 = (num ^ x.GetHashCode()) * 397; x = Z; int num3 = (num2 ^ x.GetHashCode()) * 397; x = W; return num3 ^ x.GetHashCode(); } public override string ToString() { return $"({X:0.###}, {Y:0.###}, {Z:0.###}, {W:0.###})"; } } public sealed class NetSequenceGate { private readonly struct Key : IEquatable<Key> { internal long Sender { get; } internal HammerIdentity Hammer { get; } internal MjolnirNetMessageKind Kind { get; } internal Key(long sender, HammerIdentity hammer, MjolnirNetMessageKind kind) { Sender = sender; Hammer = hammer; Kind = kind; } public bool Equals(Key other) { if (Sender == other.Sender && Kind == other.Kind) { return Hammer.Equals(other.Hammer); } return false; } public override bool Equals(object? obj) { if (obj is Key other) { return Equals(other); } return false; } public override int GetHashCode() { return (((Sender.GetHashCode() * 397) ^ Hammer.GetHashCode()) * 397) ^ (int)Kind; } } private readonly Dictionary<Key, long> highest = new Dictionary<Key, long>(); public int TrackedStreams => highest.Count; public NetRejectionReason Admit(in MjolnirNetMessage message) { if (message.Sequence == 0L) { return NetRejectionReason.None; } Key key = new Key(message.SenderPeerId, message.Hammer, message.Kind); if (!highest.TryGetValue(key, out var value)) { highest[key] = message.Sequence; return NetRejectionReason.None; } if (message.Sequence == value) { return NetRejectionReason.Duplicate; } if (message.Sequence < value) { return NetRejectionReason.Stale; } highest[key] = message.Sequence; return NetRejectionReason.None; } public int ForgetSender(long senderPeerId) { List<Key> list = null; foreach (KeyValuePair<Key, long> item in highest) { if (item.Key.Sender == senderPeerId) { (list ?? (list = new List<Key>())).Add(item.Key); } } if (list == null) { return 0; } for (int i = 0; i < list.Count; i++) { highest.Remove(list[i]); } return list.Count; } public void Clear() { highest.Clear(); } } public readonly struct RemoteHammerSnapshot { public HammerIdentity Hammer { get; } public long OwnerPeerId { get; } public MjolnirState State { get; } public MjolnirNetFlags Flags { get; } public FlightVector Position { get; } public NetRotation Rotation { get; } public FlightVector Velocity { get; } public double UpdatedAtSeconds { get; } public bool IsInWorld => State switch { MjolnirState.ThrownOutbound => true, MjolnirState.Embedded => true, MjolnirState.EmbeddedInEnemy => true, MjolnirState.Returning => true, MjolnirState.Recalling => true, MjolnirState.Dropped => true, MjolnirState.Orbiting => true, MjolnirState.Planted => true, MjolnirState.CrosshairControlled => true, MjolnirState.Levitating => true, _ => false, }; public bool IsPlayerFlight => (Flags & MjolnirNetFlags.PlayerFlight) != 0; public RemoteHammerSnapshot(HammerIdentity hammer, long ownerPeerId, MjolnirState state, MjolnirNetFlags flags, FlightVector position, NetRotation rotation, FlightVector velocity, double updatedAtSeconds) { Hammer = hammer; OwnerPeerId = ownerPeerId; State = state; Flags = flags; Position = position; Rotation = rotation; Velocity = velocity; UpdatedAtSeconds = updatedAtSeconds; } public RemoteHammerSnapshot WithMotion(MjolnirState state, MjolnirNetFlags flags, FlightVector position, NetRotation rotation, FlightVector velocity, double updatedAtSeconds) { return new RemoteHammerSnapshot(Hammer, OwnerPeerId, state, flags, position, rotation, velocity, updatedAtSeconds); } public FlightVector Extrapolate(double elapsedSeconds, float maximumSeconds = 0.5f) { if (elapsedSeconds <= 0.0) { return Position; } float num = (float)((elapsedSeconds > (double)maximumSeconds) ? ((double)maximumSeconds) : elapsedSeconds); return Position + Velocity * num; } public override string ToString() { return $"hammer={Hammer} owner={OwnerPeerId} state={State} flags={Flags}"; } } } namespace ValheimMjolnir.Core.Identity { public enum HammerClaim { None, Unclaimed, Owner, Borrower } public static class HammerClaimPolicy { public static HammerClaim Classify(string? ownerId, string? borrowerId, string? characterId) { if (string.IsNullOrWhiteSpace(characterId)) { return HammerClaim.None; } if (string.IsNullOrWhiteSpace(ownerId)) { return HammerClaim.Unclaimed; } if (Same(ownerId, characterId)) { return HammerClaim.Owner; } if (!Same(borrowerId, characterId)) { return HammerClaim.None; } return HammerClaim.Borrower; } public static bool PermitsUse(HammerClaim claim) { if ((uint)(claim - 1) <= 2u) { return true; } return false; } private static bool Same(string? left, string? right) { if (!string.IsNullOrWhiteSpace(left) && !string.IsNullOrWhiteSpace(right)) { return string.Equals(left, right, StringComparison.Ordinal); } return false; } } public readonly struct HammerIdentity : IEquatable<HammerIdentity> { public Guid HammerId { get; } public int Epoch { get; } public long LedgerWorldUid { get; } public HammerIdentity(Guid hammerId, int epoch, long ledgerWorldUid) { if (hammerId == Guid.Empty) { throw new ArgumentException("Hammer ID cannot be empty.", "hammerId"); } if (epoch < 0) { throw new ArgumentOutOfRangeException("epoch"); } HammerId = hammerId; Epoch = epoch; LedgerWorldUid = ledgerWorldUid; } public HammerIdentity NextEpoch() { return new HammerIdentity(HammerId, checked(Epoch + 1), LedgerWorldUid); } public bool Equals(HammerIdentity other) { if (HammerId.Equals(other.HammerId) && Epoch == other.Epoch) { return LedgerWorldUid == other.LedgerWorldUid; } return false; } public override bool Equals(object? obj) { if (obj is HammerIdentity other) { return Equals(other); } return false; } public override int GetHashCode() { return (((HammerId.GetHashCode() * 397) ^ Epoch) * 397) ^ LedgerWorldUid.GetHashCode(); } public override string ToString() { return $"{HammerId:N}:{Epoch}:{LedgerWorldUid}"; } } public static class HammerIdentityCodec { private sealed class DictionaryView : IReadOnlyDictionary<string, string>, IReadOnlyCollection<KeyValuePair<string, string>>, IEnumerable<KeyValuePair<string, string>>, IEnumerable { private readonly IDictionary<string, string> source; public int Count => source.Count; public IEnumerable<string> Keys => source.Keys; public IEnumerable<string> Values => source.Values; public string this[string key] => source[key]; internal DictionaryView(IDictionary<string, string> source) { this.source = source; } public bool ContainsKey(string key) { return source.ContainsKey(key); } public bool TryGetValue(string key, out string value) { return source.TryGetValue(key, out value); } public IEnumerator<KeyValuePair<string, string>> GetEnumerator() { return source.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } private const int LegacySchema = 2; public static HammerIdentityParseResult Read(IReadOnlyDictionary<string, string> data) { if (!data.TryGetValue("com.kewb.valheimmjolnir.schema", out string value)) { return Reject(HammerIdentityParseStatus.MissingSchema); } if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return Reject(HammerIdentityParseStatus.MalformedSchema); } if (result != 2 && result != 3) { return Reject(HammerIdentityParseStatus.UnsupportedSchema); } if (!data.TryGetValue("com.kewb.valheimmjolnir.id", out string value2) || string.IsNullOrWhiteSpace(value2)) { return Reject(HammerIdentityParseStatus.MissingIdentity); } if (!Guid.TryParseExact(value2, "N", out var result2) || result2 == Guid.Empty) { return Reject(HammerIdentityParseStatus.MalformedIdentity); } if (!data.TryGetValue("com.kewb.valheimmjolnir.epoch", out string value3) || !int.TryParse(value3, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result3) || result3 < 0) { return Reject(HammerIdentityParseStatus.InvalidEpoch); } string value4; bool result4 = default(bool); bool isProvisional = data.TryGetValue("com.kewb.valheimmjolnir.provisional", out value4) && bool.TryParse(value4, out result4) && result4; long? worldUid = null; if (result == 3 && data.TryGetValue("com.kewb.valheimmjolnir.world_uid", out string value5) && long.TryParse(value5, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result5) && result5 != 0L) { worldUid = result5; } return new HammerIdentityParseResult((result == 2) ? HammerIdentityParseStatus.LegacyUnbound : ((!worldUid.HasValue) ? HammerIdentityParseStatus.CurrentUnbound : HammerIdentityParseStatus.CurrentBound), new HammerIdentityEnvelope(result, result2, result3, worldUid, isProvisional)); } public static HammerIdentityMigrationResult MigrateToCurrent(IDictionary<string, string> data, long? worldUid, Func<Guid>? idFactory = null) { HammerIdentityParseResult hammerIdentityParseResult = Read((data as IReadOnlyDictionary<string, string>) ?? new DictionaryView(data)); if (hammerIdentityParseResult.Status == HammerIdentityParseStatus.UnsupportedSchema) { return RejectMigration(HammerIdentityMigrationFailure.UnsupportedSchema, hammerIdentityParseResult); } HammerIdentityEnvelope envelope = hammerIdentityParseResult.Envelope; bool flag = false; Guid guid; int num; if (envelope != null) { guid = envelope.HammerId; num = envelope.Epoch; if (worldUid.HasValue && worldUid.Value != 0L && envelope.IsWorldBound && envelope.WorldUid != worldUid) { return RejectMigration(HammerIdentityMigrationFailure.ForeignWorld, hammerIdentityParseResult); } } else { if (idFactory == null) { return RejectMigration(HammerIdentityMigrationFailure.InvalidSource, hammerIdentityParseResult); } guid = idFactory(); if (guid == Guid.Empty) { return RejectMigration(HammerIdentityMigrationFailure.InvalidGeneratedIdentity, hammerIdentityParseResult); } num = 0; flag = true; } long? num2 = ((envelope != null && envelope.IsWorldBound) ? envelope.WorldUid : worldUid); string value = guid.ToString("N"); string value2 = num.ToString(CultureInfo.InvariantCulture); string value3 = 3.ToString(CultureInfo.InvariantCulture); bool flag2 = Set(data, "com.kewb.valheimmjolnir.id", value) | Set(data, "com.kewb.valheimmjolnir.epoch", value2) | Set(data, "com.kewb.valheimmjolnir.schema", value3); flag2 = ((!num2.HasValue || num2.Value == 0L) ? (flag2 | data.Remove("com.kewb.valheimmjolnir.world_uid")) : (flag2 | Set(data, "com.kewb.valheimmjolnir.world_uid", num2.Value.ToString(CultureInfo.InvariantCulture)))); if (flag) { flag2 |= Set(data, "com.kewb.valheimmjolnir.provisional", bool.TrueString.ToLowerInvariant()); } HammerIdentityParseResult parseResult = Read((data as IReadOnlyDictionary<string, string>) ?? new DictionaryView(data)); return new HammerIdentityMigrationResult(accepted: true, flag2, HammerIdentityMigrationFailure.None, parseResult); } public static void WriteBoundIdentity(IDictionary<string, string> data, HammerIdentity identity) { if (identity.LedgerWorldUid == 0L) { throw new ArgumentOutOfRangeException("identity", "A bound identity requires a nonzero world UID."); } data["com.kewb.valheimmjolnir.id"] = identity.HammerId.ToString("N"); data["com.kewb.valheimmjolnir.epoch"] = identity.Epoch.ToString(CultureInfo.InvariantCulture); data["com.kewb.valheimmjolnir.world_uid"] = identity.LedgerWorldUid.ToString(CultureInfo.InvariantCulture); data["com.kewb.valheimmjolnir.schema"] = 3.ToString(CultureInfo.InvariantCulture); data.Remove("com.kewb.valheimmjolnir.provisional"); } private static HammerIdentityParseResult Reject(HammerIdentityParseStatus status) { return new HammerIdentityParseResult(status, null); } private static HammerIdentityMigrationResult RejectMigration(HammerIdentityMigrationFailure failure, HammerIdentityParseResult parsed) { return new HammerIdentityMigrationResult(accepted: false, changed: false, failure, parsed); } private static bool Set(IDictionary<string, string> data, string key, string value) { if (data.TryGetValue(key, out string value2) && string.Equals(value2, value, StringComparison.Ordinal)) { return false; } data[key] = value; return true; } } public sealed class HammerIdentityEnvelope { public int Schema { get; } public Guid HammerId { get; } public int Epoch { get; } public long? WorldUid { get; } public bool IsProvisional { get; } public bool IsWorldBound { get { if (WorldUid.HasValue) { return WorldUid.Value != 0; } return false; } } internal HammerIdentityEnvelope(int schema, Guid hammerId, int epoch, long? worldUid, bool isProvisional) { Schema = schema; HammerId = hammerId; Epoch = epoch; WorldUid = worldUid; IsProvisional = isProvisional; } public bool TryGetBoundIdentity(out HammerIdentity identity) { if (IsWorldBound) { identity = new HammerIdentity(HammerId, Epoch, WorldUid.Value); return true; } identity = default(HammerIdentity); return false; } } public enum HammerIdentityMigrationFailure { None, InvalidSource, UnsupportedSchema, ForeignWorld, InvalidGeneratedIdentity } public sealed class HammerIdentityMigrationResult { public bool Accepted { get; } public bool Changed { get; } public HammerIdentityMigrationFailure Failure { get; } public HammerIdentityParseResult ParseResult { get; } internal HammerIdentityMigrationResult(bool accepted, bool changed, HammerIdentityMigrationFailure failure, HammerIdentityParseResult parseResult) { Accepted = accepted; Changed = changed; Failure = failure; ParseResult = parseResult; } } public enum HammerIdentityParseStatus { CurrentBound, CurrentUnbound, LegacyUnbound, MissingSchema, MalformedSchema, UnsupportedSchema, MissingIdentity, MalformedIdentity, InvalidEpoch } public sealed class HammerIdentityParseResult { public HammerIdentityParseStatus Status { get; } public HammerIdentityEnvelope? Envelope { get; } public bool Accepted => Envelope != null; internal HammerIdentityParseResult(HammerIdentityParseStatus status, HammerIdentityEnvelope? envelope) { Status = status; Envelope = envelope; } public bool IsForeignTo(long worldUid) { if (worldUid != 0L) { HammerIdentityEnvelope? envelope = Envelope; if (envelope != null && envelope.IsWorldBound) { return Envelope.WorldUid != worldUid; } } return false; } } public static class HammerIdentitySchema { public const int Current = 3; public const string IdKey = "com.kewb.valheimmjolnir.id"; public const string EpochKey = "com.kewb.valheimmjolnir.epoch"; public const string WorldUidKey = "com.kewb.valheimmjolnir.world_uid"; public const string SchemaKey = "com.kewb.valheimmjolnir.schema"; public const string ProvisionalKey = "com.kewb.valheimmjolnir.provisional"; public const string OwnerKey = "com.kewb.valheimmjolnir.owner"; public const string QuarantineKey = "com.kewb.valheimmjolnir.quarantine"; public const string UndiscoveredKey = "com.kewb.valheimmjolnir.undiscovered"; public const string HammerOwnerKey = "com.kewb.valheimmjolnir.hammer_owner"; public const string BorrowerKey = "com.kewb.valheimmjolnir.borrower"; public const string LegacyStateKey = "com.kewb.valheimmjolnir.state"; } public enum InventoryTokenTransferAction { UseVanilla, PreserveReference } public static class InventoryTokenTransferPolicy { public static InventoryTokenTransferAction PlanUnpositioned(bool sameInventory, bool sourceStored, int sourceStack, bool hasFreeSlot) { if (!(sameInventory && sourceStored && sourceStack == 1 && hasFreeSlot)) { return InventoryTokenTransferAction.UseVanilla; } return InventoryTokenTransferAction.PreserveReference; } public static InventoryTokenTransferAction PlanPositioned(bool sameInventory, int sourceStack, int requestedAmount, int x, int y, int width, int height, bool destinationEmpty) { if (!(sameInventory && sourceStack == 1 && requestedAmount == sourceStack && x >= 0 && y >= 0 && x < width && y < height && destinationEmpty)) { return InventoryTokenTransferAction.UseVanilla; } return InventoryTokenTransferAction.PreserveReference; } public static bool RetainsAllCustomData(IReadOnlyDictionary<string, string> source, IReadOnlyDictionary<string, string> destination) { foreach (KeyValuePair<string, string> item in source) { if (!destination.TryGetValue(item.Key, out string value) || value != item.Value) { return false; } } return true; } } } namespace ValheimMjolnir.Core.Gameplay { public static class AbilityConflictRules { public static bool CanStart(string? requestedAbility, MjolnirAbilityState activeState, MjolnirHammerState hammerState) { switch (requestedAbility) { case "mjolnir.throw": return activeState == MjolnirAbilityState.Idle && hammerState == MjolnirHammerState.Held; case "mjolnir.guard": return activeState == MjolnirAbilityState.Idle && hammerState == MjolnirHammerState.Held; case "mjolnir.lightning": return activeState == MjolnirAbilityState.Idle && IsLightningReachable(hammerState); case "mjolnir.flight": return activeState == MjolnirAbilityState.Idle && hammerState == MjolnirHammerState.Held; case "mjolnir.lightningstep": { bool flag = ((activeState == MjolnirAbilityState.Idle || activeState == MjolnirAbilityState.Flight) ? true : false); return flag && hammerState == MjolnirHammerState.Held; } case "mjolnir.groundslam": return activeState == MjolnirAbilityState.Flight && hammerState == MjolnirHammerState.Held; case "mjolnir.momentumslam": { bool flag = ((activeState == MjolnirAbilityState.Idle || activeState == MjolnirAbilityState.Flight) ? true : false); return flag && hammerState == MjolnirHammerState.Held; } case "mjolnir.heavenfall": case "mjolnir.thunderspear": case "mjolnir.flightcharge": return activeState == MjolnirAbilityState.Flight && hammerState == MjolnirHammerState.Held; case "mjolnir.crosshaircontrol": return activeState == MjolnirAbilityState.Idle && IsHammerInWorld(hammerState); case "mjolnir.summon": { bool flag = activeState == MjolnirAbilityState.Idle; if (flag) { bool flag2 = ((hammerState == MjolnirHammerState.Stored || hammerState == MjolnirHammerState.Dropped || hammerState == MjolnirHammerState.Unavailable) ? true : false); flag = flag2; } return flag; } case "mjolnir.stormline": case "mjolnir.assistedleap": case "mjolnir.thunderclap": case "mjolnir.lightningpillar": return activeState == MjolnirAbilityState.Idle && hammerState == MjolnirHammerState.Held; case "mjolnir.overheadslam": return activeState == MjolnirAbilityState.Idle && hammerState == MjolnirHammerState.Held; case "mjolnir.assault": case "mjolnir.cyclone": case "mjolnir.multianglebarrage": { bool flag = activeState == MjolnirAbilityState.Idle; if (flag) { bool flag2 = (((uint)(hammerState - 4) <= 2u || (uint)(hammerState - 9) <= 1u) ? true : false); flag = flag2; } return flag; } case "mjolnir.orbitinghammer": return activeState == MjolnirAbilityState.Idle && hammerState == MjolnirHammerState.Returning; case "mjolnir.chainlightning": return activeState == MjolnirAbilityState.Idle && IsHammerInWorld(hammerState); case "mjolnir.groundedlightningrod": { bool flag = activeState == MjolnirAbilityState.Idle; if (flag) { bool flag2 = (((uint)(hammerState - 4) <= 2u || hammerState == MjolnirHammerState.Levitating) ? true : false); flag = flag2; } return flag; } case "mjolnir.godofthunder": case "mjolnir.summonstorm": return activeState == MjolnirAbilityState.Idle && hammerState == MjolnirHammerState.Held; case "mjolnir.godblast": return activeState == MjolnirAbilityState.Idle && hammerState == MjolnirHammerState.Held; default: return false; } } public static bool HasRule(string? abilityId) { if (abilityId != null) { if (!CanStart(abilityId, MjolnirAbilityState.Idle, MjolnirHammerState.Held) && !CanStart(abilityId, MjolnirAbilityState.Idle, MjolnirHammerState.Outbound) && !CanStart(abilityId, MjolnirAbilityState.Idle, MjolnirHammerState.Returning) && !CanStart(abilityId, MjolnirAbilityState.Idle, MjolnirHammerState.Stored) && !CanStart(abilityId, MjolnirAbilityState.Idle, MjolnirHammerState.Unavailable)) { return CanStart(abilityId, MjolnirAbilityState.Flight, MjolnirHammerState.Held); } return true; } return false; } public static bool IsHammerInWorld(MjolnirHammerState hammerState) { switch (hammerState) { case MjolnirHammerState.Outbound: case MjolnirHammerState.Embedded: case MjolnirHammerState.Dropped: case MjolnirHammerState.Returning: case MjolnirHammerState.CrosshairControlled: case MjolnirHammerState.Levitating: case MjolnirHammerState.Planted: case MjolnirHammerState.Orbiting: case MjolnirHammerState.EmbeddedInEnemy: return true; default: return false; } } public static bool IsLightningReachable(MjolnirHammerState hammerState) { if (hammerState != MjolnirHammerState.Held) { return IsHammerInWorld(hammerState); } return true; } } public static class AbilityMath { public static float CrosshairControlSpeed(float distance, float minimumSpeed, float maximumSpeed, float range) { float num = Clamp01(distance / Math.Max(0.01f, range)); return minimumSpeed + (maximumSpeed - minimumSpeed) * (num * num * (3f - 2f * num)); } public
plugins/ValheimMjolnir/ValheimMjolnir.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GUIFramework; using HarmonyLib; using Jotunn; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Splatform; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; using ValheimMjolnir.Compatibility; using ValheimMjolnir.Configuration; using ValheimMjolnir.Core.Authority; using ValheimMjolnir.Core.Gameplay; using ValheimMjolnir.Core.Identity; using ValheimMjolnir.Core.Net; using ValheimMjolnir.Core.Persistence; using ValheimMjolnir.Core.State; using ValheimMjolnir.Gameplay; using ValheimMjolnir.Gameplay.Abilities; using ValheimMjolnir.Gameplay.Hammer; using ValheimMjolnir.Gameplay.Passives; using ValheimMjolnir.Gameplay.Sentry; using ValheimMjolnir.Gameplay.Services; using ValheimMjolnir.Input; using ValheimMjolnir.Items; using ValheimMjolnir.Networking; using ValheimMjolnir.Plugin; using ValheimMjolnir.Presentation; using ValheimMjolnir.UI; using ValheimMjolnir.Worthiness; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("ValheimMjolnir")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.6.4.0")] [assembly: AssemblyInformationalVersion("2.6.4+b4c6ac47ce16619676e8ff87e062a9fca15afbd1")] [assembly: AssemblyProduct("ValheimMjolnir")] [assembly: AssemblyTitle("ValheimMjolnir")] [assembly: AssemblyVersion("2.6.4.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace ValheimMjolnir.Worthiness { [HarmonyPatch] internal static class DeedPatches { [HarmonyPatch(typeof(Character), "Damage", new Type[] { typeof(HitData) })] [HarmonyPostfix] private static void NoteDamage(Character __instance, HitData hit) { MjolnirDeedObserver.Current?.ObserveDamage(__instance, hit); } [HarmonyPatch(typeof(Character), "OnDeath")] [HarmonyPostfix] private static void NoteCreatureDeath(Character __instance) { MjolnirDeedObserver.Current?.ObserveDeath(__instance); } [HarmonyPatch(typeof(Player), "OnDeath")] [HarmonyPostfix] private static void NotePlayerDeath(Player __instance) { MjolnirDeedObserver.Current?.ObservePlayerDeath(__instance); } } public interface IMjolnirWorthinessLevelProvider : IMjolnirWorthinessProvider { float GetWorthinessLevel(Player player, ItemData hammer); } public interface IMjolnirWorthinessProvider { WorthinessDecision Evaluate(Player player, ItemData hammer); } public static class MjolnirApi { private static readonly List<IMjolnirWorthinessProvider> Providers = new List<IMjolnirWorthinessProvider>(); internal static IReadOnlyList<IMjolnirWorthinessProvider> RegisteredProviders => Providers; public static void RegisterWorthinessProvider(IMjolnirWorthinessProvider provider) { if (provider == null) { throw new ArgumentNullException("provider"); } if (!Providers.Contains(provider)) { Providers.Add(provider); } } public static void UnregisterWorthinessProvider(IMjolnirWorthinessProvider provider) { Providers.Remove(provider); } public static void RegisterWorthinessLevelProvider(IMjolnirWorthinessLevelProvider provider) { RegisterWorthinessProvider(provider); } public static void UnregisterWorthinessLevelProvider(IMjolnirWorthinessLevelProvider provider) { UnregisterWorthinessProvider(provider); } } internal sealed class MjolnirDeedObserver : IDisposable { private sealed class Wound { private readonly Character victim; internal float LastStruck { get; private set; } internal bool DroveToFlight { get; private set; } internal float FledAt { get; private set; } internal bool FoughtBack { get; private set; } internal bool Rewarded { get; set; } internal bool IsGone { get { if (!((Object)(object)victim == (Object)null)) { return !Object.op_Implicit((Object)(object)victim); } return true; } } internal Wound(Character victim) { this.victim = victim; } internal void Struck(float now, bool fleeing, bool fightingUs) { LastStruck = now; if (fleeing && FledAt <= 0f) { FledAt = now; } DroveToFlight |= fleeing; FoughtBack |= fightingUs; Rewarded = false; } internal bool IsRecent(float now, float within) { return now - LastStruck <= Math.Max(within, 5f); } } private const float WoundMemorySeconds = 180f; private const float SweepIntervalSeconds = 2f; private const string FelledGuardiansKey = "mjolnir_felled_guardians"; private readonly MjolnirConfig config; private readonly MjolnirWorthinessService worthiness; private readonly ManualLogSource logger; private readonly Dictionary<int, Wound> wounds = new Dictionary<int, Wound>(); private readonly List<int> expired = new List<int>(); private float nextSweep; private bool warnedOnce; internal static MjolnirDeedObserver? Current { get; private set; } internal MjolnirDeedObserver(MjolnirConfig config, MjolnirWorthinessService worthiness, ManualLogSource logger) { this.config = config; this.worthiness = worthiness; this.logger = logger; Current = this; } internal void ObserveDamage(Character? victim, HitData? hit) { if (!ShouldObserve() || (Object)(object)victim == (Object)null || !Object.op_Implicit((Object)(object)victim) || hit == null) { return; } try { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && Object.op_Implicit((Object)(object)localPlayer) && (object)hit.GetAttacker() == localPlayer && (object)victim != localPlayer) { int instanceID = ((Object)victim).GetInstanceID(); if (!wounds.TryGetValue(instanceID, out Wound value)) { value = new Wound(victim); wounds[instanceID] = value; } value.Struck(Time.time, IsFleeing(victim), IsFightingUs(localPlayer, victim)); } } catch (Exception exception) { WarnOnce("recording a wound", exception); } } internal void ObserveDeath(Character? victim) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) if (!ShouldObserve() || (Object)(object)victim == (Object)null || !Object.op_Implicit((Object)(object)victim)) { return; } try { int instanceID = ((Object)victim).GetInstanceID(); Wound value; bool flag = wounds.TryGetValue(instanceID, out value); wounds.Remove(instanceID); Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && Object.op_Implicit((Object)(object)localPlayer) && flag && value.IsRecent(Time.time, config.SparedFleeingSeconds.Value)) { worthiness.RecordDeed(localPlayer, ClassifyKill(localPlayer, victim, value)); if (HeldTheLine(localPlayer, victim)) { worthiness.RecordDeed(localPlayer, (WorthinessDeed)6); } } } catch (Exception exception) { WarnOnce("judging a kill", exception); } } internal void ObservePlayerDeath(Player? player) { //IL_003a: 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_0047: Unknown result type (might be due to invalid IL or missing references) if (!ShouldObserve() || (Object)(object)player == (Object)null || !Object.op_Implicit((Object)(object)player) || player != Player.m_localPlayer) { return; } try { wounds.Clear(); MjolnirAbilityController? current = MjolnirAbilityController.Current; MjolnirAbilityState val = (MjolnirAbilityState)((current != null) ? ((int)current.ActiveAbilityState) : 0); worthiness.RecordDeed(player, (WorthinessDeed)(((int)val == 0) ? 9 : 8)); } catch (Exception exception) { WarnOnce("judging a death", exception); } } internal void Tick() { if (!ShouldObserve() || Time.time < nextSweep) { return; } nextSweep = Time.time + 2f; try { Player localPlayer = Player.m_localPlayer; float time = Time.time; float value = config.SparedFleeingSeconds.Value; expired.Clear(); foreach (KeyValuePair<int, Wound> wound in wounds) { Wound value2 = wound.Value; if (value2.IsGone || time - value2.LastStruck > 180f) { expired.Add(wound.Key); } else if (value2.DroveToFlight && !value2.Rewarded && !(time - value2.LastStruck < value)) { value2.Rewarded = true; expired.Add(wound.Key); worthiness.RecordDeed(localPlayer, (WorthinessDeed)1); } } foreach (int item in expired) { wounds.Remove(item); } expired.Clear(); } catch (Exception exception) { WarnOnce("sweeping wounds", exception); } } internal void Reset() { wounds.Clear(); expired.Clear(); nextSweep = 0f; } public void Dispose() { Reset(); if (Current == this) { Current = null; } } private WorthinessDeed ClassifyKill(Player local, Character victim, Wound wound) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (IsHelpless(victim)) { return (WorthinessDeed)4; } if (victim.IsBoss()) { return CreditGuardian(local, victim); } if (!WasDefendingAnother(local, victim)) { if (!WasChasedDown(wound, victim)) { float maxHealth = ((Character)local).GetMaxHealth(); float maxHealth2 = victim.GetMaxHealth(); if (!(maxHealth <= 0f) && !(maxHealth2 <= 0f)) { if (!(maxHealth2 >= maxHealth * config.StrongerFoeHealthRatio.Value)) { if (maxHealth2 <= maxHealth * config.WeakerFoeHealthRatio.Value && !wound.FoughtBack) { return (WorthinessDeed)5; } return (WorthinessDeed)0; } return (WorthinessDeed)3; } return (WorthinessDeed)0; } return (WorthinessDeed)2; } return (WorthinessDeed)7; } private bool WasChasedDown(Wound wound, Character victim) { if (!wound.DroveToFlight && !IsFleeing(victim)) { return false; } if (wound.FledAt > 0f) { return Time.time - wound.FledAt >= Mathf.Max(0f, config.FleeGraceSeconds.Value); } return false; } private static bool IsFightingUs(Player local, Character victim) { BaseAI baseAI = victim.GetBaseAI(); MonsterAI val = (MonsterAI)(object)((baseAI is MonsterAI) ? baseAI : null); if (val == null) { return false; } Character targetCreature = ((BaseAI)val).GetTargetCreature(); if ((Object)(object)targetCreature != (Object)null && Object.op_Implicit((Object)(object)targetCreature)) { return (object)targetCreature == local; } return false; } private WorthinessDeed CreditGuardian(Player local, Character victim) { //IL_00b4: 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_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) try { string name = victim.m_name; if (string.IsNullOrEmpty(name)) { return (WorthinessDeed)10; } if (local.m_customData == null) { local.m_customData = new Dictionary<string, string>(); } local.m_customData.TryGetValue("mjolnir_felled_guardians", out var value); string text = value ?? string.Empty; string[] array = text.Split(new char[1] { ';' }); for (int i = 0; i < array.Length; i++) { if (string.Equals(array[i], name, StringComparison.Ordinal)) { return (WorthinessDeed)3; } } local.m_customData["mjolnir_felled_guardians"] = ((text.Length == 0) ? name : (text + ";" + name)); return (WorthinessDeed)10; } catch (Exception) { return (WorthinessDeed)10; } } private bool HeldTheLine(Player local, Character victim) { if (!IsHelpless(victim) && !IsFleeing(victim) && ((Character)local).GetMaxHealth() > 0f) { return ((Character)local).GetHealth() / ((Character)local).GetMaxHealth() <= config.HeldTheLineHealthRatio.Value; } return false; } private static bool IsHelpless(Character victim) { if (!victim.IsTamed()) { return victim.IsPlayer(); } return true; } private static bool IsFleeing(Character victim) { BaseAI baseAI = victim.GetBaseAI(); MonsterAI val = (MonsterAI)(object)((baseAI is MonsterAI) ? baseAI : null); if (val == null) { return false; } float fleeIfLowHealth = val.m_fleeIfLowHealth; if (fleeIfLowHealth > 0f) { return victim.GetHealthPercentage() < fleeIfLowHealth; } return false; } private static bool WasDefendingAnother(Player local, Character victim) { BaseAI baseAI = victim.GetBaseAI(); MonsterAI val = (MonsterAI)(object)((baseAI is MonsterAI) ? baseAI : null); if (val == null) { return false; } Character targetCreature = ((BaseAI)val).GetTargetCreature(); if ((Object)(object)targetCreature != (Object)null && Object.op_Implicit((Object)(object)targetCreature) && (object)targetCreature != local) { if (!targetCreature.IsTamed()) { return targetCreature.IsPlayer(); } return true; } return false; } private bool ShouldObserve() { if (config.Enabled.Value) { return WorthinessProgressionPolicy.ShouldRecordDeeds(worthiness.IsLiving, worthiness.IsBypassed); } return false; } private void WarnOnce(string what, Exception exception) { if (!warnedOnce) { warnedOnce = true; logger.LogWarning((object)("Worthiness deed observation failed while " + what + ": " + exception.Message + ". Further failures of this kind are not logged.")); } } } internal sealed class MjolnirWorthinessService : IDisposable { private sealed class PersistedWorthiness { private int playerId; private string? raw; private float value; internal bool TryRead(Player? player, string key, out float result) { result = 0f; if ((Object)(object)player == (Object)null || !Object.op_Implicit((Object)(object)player) || player.m_customData == null || !player.m_customData.TryGetValue(key, out var text)) { return false; } int instanceID = ((Object)player).GetInstanceID(); if (instanceID == playerId && string.Equals(raw, text, StringComparison.Ordinal)) { result = value; return true; } if (!float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out var result2) || float.IsNaN(result2) || float.IsInfinity(result2)) { return false; } result = ClampWorthiness(result2); playerId = instanceID; raw = text; value = result; return true; } internal void Invalidate() { raw = null; } } internal const string LedgerKey = "mjolnir_worthiness_ledger"; internal const string OverrideKey = "mjolnir_worthiness_override"; private readonly MjolnirConfig config; private readonly ManualLogSource logger; private readonly ConditionalWeakTable<HitData, object> scaledHits = new ConditionalWeakTable<HitData, object>(); private readonly HashSet<Type> warnedDecisionProviders = new HashSet<Type>(); private readonly HashSet<Type> warnedLevelProviders = new HashSet<Type>(); private static readonly object ScaledMarker = new object(); private int heldPowersFrame = -1; private int heldPowersPlayerId; private bool heldPowersResult; private readonly PersistedWorthiness savedOverride = new PersistedWorthiness(); private readonly PersistedWorthiness savedLedger = new PersistedWorthiness(); private readonly RepeatedLossEscalator lossEscalator = new RepeatedLossEscalator(); private int gatingTierPlayerId = -1; private WorthinessTier gatingTier; internal static MjolnirWorthinessService? Current { get; private set; } internal bool PreventHeldStagger => config.HeldStaggerImmunity.Value; internal bool PreventHeldKnockback => config.HeldKnockbackImmunity.Value; internal bool PreventHeldScreenShake => config.HeldScreenShakeImmunity.Value; internal bool IsBypassed => config.BypassWorthinessProgression.Value; internal bool IsLiving => config.Worthiness.Value == WorthinessMode.Living; internal event Action<Player, WorthinessDeed, float>? DeedRecorded; internal MjolnirWorthinessService(MjolnirConfig config, ManualLogSource logger) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown this.config = config; this.logger = logger; Current = this; } internal bool CanUse(Player player, ItemData hammer, bool bindIfAllowed, bool showMessage = true) { MjolnirItemIdentity.Ensure(hammer); bool num = !hammer.m_customData.ContainsKey("com.kewb.valheimmjolnir.quarantine") && (config.BypassWorthinessProgression.Value || Evaluate(player, hammer, bindIfAllowed)); if (!num && showMessage) { ShowUnworthyMessage(player); } return num; } internal void ShowUnworthyMessage(Player player) { if (config.Messages.Value != MessageMode.Off && config.ShowFailureMessages.Value) { MjolnirAbilityController.Current?.ShowFailureAlert(player, "$msg_mjolnir_unworthy"); } } internal void NormalizeInventory(Player player) { MjolnirItemIdentity.EnsureInventory(((Humanoid)player).GetInventory()); MjolnirActiveRegistry.Current?.AuditInventory(((Humanoid)player).GetInventory(), player); } internal bool HasHeldPowers(Player player) { if ((Object)(object)player == (Object)null || !Object.op_Implicit((Object)(object)player)) { return false; } int frameCount = Time.frameCount; int instanceID = ((Object)player).GetInstanceID(); if (heldPowersFrame == frameCount && heldPowersPlayerId == instanceID) { return heldPowersResult; } heldPowersFrame = frameCount; heldPowersPlayerId = instanceID; heldPowersResult = EvaluateHeldPowers(player); return heldPowersResult; } private bool EvaluateHeldPowers(Player player) { ItemData rightItem = ((Humanoid)player).RightItem; if (rightItem == null || !MjolnirItemIdentity.IsMjolnir(rightItem) || rightItem.m_customData.ContainsKey("com.kewb.valheimmjolnir.quarantine")) { return false; } MjolnirActiveRegistry current = MjolnirActiveRegistry.Current; if (current != null && current.TryGetLegitimatelyOwnedToken(player, out ItemData token) && token == rightItem) { return CanUse(player, rightItem, bindIfAllowed: false, showMessage: false); } return false; } internal float GetHeldStaminaCostMultiplier(Player player) { float num = GetWorthinessLevel(player) / 100f; return AbilityMath.StaminaCostMultiplier(config.HeldStaminaCostReduction.Value * num); } internal void ApplyDamageBonus(Player player, HitData hit, bool mjolnirDamage) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Invalid comparison between Unknown and I4 if ((Object)(object)player == (Object)null || !Object.op_Implicit((Object)(object)player) || hit == null || scaledHits.TryGetValue(hit, out object _)) { return; } MjolnirActiveRegistry current = MjolnirActiveRegistry.Current; if (current == null || !current.TryGetLegitimatelyOwnedToken(player, out ItemData token)) { return; } float? providedLevel = null; if (config.Worthiness.Value == WorthinessMode.CustomProviders && !config.BypassWorthinessProgression.Value) { if (!TryEvaluateCustomWorthiness(player, token, out var level)) { return; } providedLevel = level; } else if (!CanUse(player, token, bindIfAllowed: false, showMessage: false)) { return; } scaledHits.Add(hit, ScaledMarker); float num = CalculateDamageBonus(player, token, providedLevel); if (num <= 0f) { return; } if (mjolnirDamage) { if (config.ScaleMjolnirDamage.Value) { ((DamageTypes)(ref hit.m_damage)).Modify(1f + num * config.MjolnirDamageScale.Value); } return; } if ((int)hit.m_skill == 11) { if (config.ScaleUnarmedDamage.Value) { ((DamageTypes)(ref hit.m_damage)).Modify(1f + num * config.UnarmedDamageScale.Value); } return; } if (config.ScalePhysicalDamage.Value) { float num2 = 1f + num * config.PhysicalDamageScale.Value; hit.m_damage.m_damage *= num2; hit.m_damage.m_blunt *= num2; hit.m_damage.m_slash *= num2; hit.m_damage.m_pierce *= num2; } if (config.ScaleElementalDamage.Value) { float num3 = 1f + num * config.ElementalDamageScale.Value; hit.m_damage.m_fire *= num3; hit.m_damage.m_frost *= num3; hit.m_damage.m_poison *= num3; hit.m_damage.m_spirit *= num3; } if (config.ScaleLightningDamage.Value) { hit.m_damage.m_lightning *= 1f + num * config.LightningDamageScale.Value; } } internal void ApplyMjolnirStructureDamageBonus(Player player, HitData hit) { if (config.ScaleMjolnirStructureDamage.Value) { ApplyDamageBonus(player, hit, mjolnirDamage: true); } } public void Dispose() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) lossEscalator.Reset(); gatingTierPlayerId = -1; gatingTier = (WorthinessTier)0; if (Current == this) { Current = null; } } private bool Evaluate(Player player, ItemData hammer, bool bindIfAllowed) { if (config.Worthiness.Value == WorthinessMode.Disabled) { return true; } if (GetWorthinessLevel(player) <= config.UnworthyThreshold.Value) { return false; } switch (config.Worthiness.Value) { case WorthinessMode.EveryoneWorthy: return true; case WorthinessMode.Living: return true; case WorthinessMode.ProgressionBased: { if (TryGetSavedWorthiness(player, out var value)) { return value > config.UnworthyThreshold.Value; } if ((Object)(object)ZoneSystem.instance != (Object)null) { if (!ZoneSystem.instance.GetGlobalKey("defeated_dragon")) { return ZoneSystem.instance.GetGlobalKey("defeated_moder"); } return true; } return false; } case WorthinessMode.CustomProviders: return EvaluateProviders(player, hammer); default: if (MjolnirItemIdentity.IsBoundTo(hammer, player)) { return true; } if (!MjolnirItemIdentity.IsUnbound(hammer)) { return false; } if (bindIfAllowed && config.BindOnFirstEquip.Value) { MjolnirItemIdentity.Bind(hammer, player); logger.LogInfo((object)string.Format("Mjolnir {0} bound to local character {1}.", hammer.m_customData["com.kewb.valheimmjolnir.id"], player.GetPlayerID())); } return true; } } private bool EvaluateProviders(Player player, ItemData hammer) { bool flag = false; foreach (IMjolnirWorthinessProvider registeredProvider in MjolnirApi.RegisteredProviders) { WorthinessDecision worthinessDecision; try { worthinessDecision = registeredProvider.Evaluate(player, hammer); } catch (Exception exception) { WarnDecisionProvider(registeredProvider, exception); return false; } if (worthinessDecision == WorthinessDecision.Deny) { return false; } flag = flag || worthinessDecision == WorthinessDecision.Allow; } return flag; } private float CalculateDamageBonus(Player player, ItemData hammer, float? providedLevel) { return WorthinessCurve.CalculateBonus((providedLevel ?? GetWorthinessLevel(player)) / 100f, config.UnworthyThreshold.Value / 100f, config.BarelyWorthyThreshold.Value / 100f, config.WorthyThreshold.Value / 100f, config.HighlyWorthyThreshold.Value / 100f, config.ExceptionalWorthinessThreshold.Value / 100f, config.MinimumWorthinessDamageBonus.Value / 100f, config.BarelyWorthyDamageBonus.Value / 100f, config.WorthyDamageBonus.Value / 100f, config.HighlyWorthyDamageBonus.Value / 100f, config.MaximumWorthinessDamageBonus.Value / 100f, config.WorthinessCurveExponent.Value); } internal float GetWorthinessLevel(Player player) { if (config.Worthiness.Value == WorthinessMode.Living) { return LivingWorthiness(player); } if (TryGetSavedWorthiness(player, out var value)) { return value; } return config.Worthiness.Value switch { WorthinessMode.Disabled => 0f, WorthinessMode.ProgressionBased => ProgressionWorthiness(), WorthinessMode.CustomProviders => BuiltInWorthiness(player), _ => BuiltInWorthiness(player), }; } internal WorthinessTier GatingTier(Player? player) { //IL_002e: 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_0076: 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) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !Object.op_Implicit((Object)(object)player)) { return (WorthinessTier)0; } float worthinessLevel = GetWorthinessLevel(player); if (!config.EnableWorthinessTiers.Value) { return WorthinessTierPolicy.FromLevel(worthinessLevel); } int instanceID = ((Object)player).GetInstanceID(); if (instanceID != gatingTierPlayerId) { gatingTierPlayerId = instanceID; gatingTier = WorthinessTierPolicy.FromLevel(worthinessLevel); return gatingTier; } gatingTier = WorthinessTierGate.Resolve(gatingTier, worthinessLevel, config.TierDropMargin.Value); return gatingTier; } internal void RefreshGatingTier(Player? player) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) GatingTier(player); } internal bool RecordDeed(Player? player, WorthinessDeed deed) { //IL_008d: 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_0031: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_0074: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !Object.op_Implicit((Object)(object)player) || (int)deed == 0 || !WorthinessProgressionPolicy.ShouldRecordDeeds(IsLiving, IsBypassed)) { return false; } try { WorthinessLedgerSettings val = Escalated(config.LedgerSettings(), deed); float num = default(float); float value = WorthinessLedger.Apply(LivingWorthiness(player), deed, ref val, ref num); if (num == 0f) { return false; } WriteLedger(player, value); this.DeedRecorded?.Invoke(player, deed, num); return true; } catch (Exception ex) { logger.LogWarning((object)$"Worthiness deed {deed} could not be recorded: {ex.Message}"); return false; } } private WorthinessLedgerSettings Escalated(in WorthinessLedgerSettings settings, WorthinessDeed deed) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //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) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Invalid comparison between Unknown and I4 //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_0062: Invalid comparison between Unknown and I4 //IL_00b3: Unknown result type (might be due to invalid IL or missing references) if ((int)deed != 8 && (int)deed != 9) { return settings; } RepeatedLossEscalator obj = lossEscalator; RepeatedLossSettings val = config.LossEscalation(); obj.Retune(ref val); float num = lossEscalator.Record(deed, Time.time); if (num >= 1f) { return settings; } float num2 = (((int)deed == 8) ? (((WorthinessLedgerSettings)(ref settings)).DiedInHubris * num) : ((WorthinessLedgerSettings)(ref settings)).DiedInHubris); float num3 = (((int)deed == 9) ? (((WorthinessLedgerSettings)(ref settings)).DiedFighting * num) : ((WorthinessLedgerSettings)(ref settings)).DiedFighting); return new WorthinessLedgerSettings(((WorthinessLedgerSettings)(ref settings)).SparedFleeingFoe, ((WorthinessLedgerSettings)(ref settings)).StruckFleeingFoe, ((WorthinessLedgerSettings)(ref settings)).DefeatedStrongerFoe, ((WorthinessLedgerSettings)(ref settings)).StruckHelplessCreature, ((WorthinessLedgerSettings)(ref settings)).PunchedDown, ((WorthinessLedgerSettings)(ref settings)).HeldTheLine, ((WorthinessLedgerSettings)(ref settings)).ShieldedAnother, num2, num3, ((WorthinessLedgerSettings)(ref settings)).FelledAGuardian, ((WorthinessLedgerSettings)(ref settings)).HighWorthinessResistance, ((WorthinessLedgerSettings)(ref settings)).Floor); } internal LightningPotency LightningPotencyFor(Player? player) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_003a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !Object.op_Implicit((Object)(object)player) || IsBypassed) { return LightningPotency.Full; } try { float worthinessLevel = GetWorthinessLevel(player); LightningPotencySettings val = config.LightningPotencyRamp(); return LightningPotencyPolicy.For(worthinessLevel, ref val); } catch (Exception ex) { logger.LogWarning((object)("Lightning potency could not be resolved: " + ex.Message)); return LightningPotency.Full; } } private float LivingWorthiness(Player player) { if (savedLedger.TryRead(player, "mjolnir_worthiness_ledger", out var result)) { return result; } float num = SeedFor(player); WriteLedger(player, num); return num; } private float SeedFor(Player player) { //IL_0050: 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) float num = config.LivingWorthinessStart.Value; if (TryGetSavedWorthiness(player, out var value)) { num = Math.Max(num, value); } if (config.UseClubsSkillForWorthiness.Value) { num = Math.Max(num, ClampWorthiness(((Character)player).GetSkillLevel((SkillType)3))); } float num2 = num; WorthinessLedgerSettings val = config.LedgerSettings(); return WorthinessLedger.Seed(num2, ref val); } private void WriteLedger(Player player, float value) { //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) WorthinessLedgerSettings val = config.LedgerSettings(); float num = WorthinessLedger.Seed(value, ref val); if (player.m_customData == null) { player.m_customData = new Dictionary<string, string>(); } player.m_customData["mjolnir_worthiness_ledger"] = num.ToString("0.###", CultureInfo.InvariantCulture); savedLedger.Invalidate(); } private float BuiltInWorthiness(Player player) { if (!config.UseClubsSkillForWorthiness.Value) { return config.DefaultWorthinessLevel.Value; } return Math.Max(0f, Math.Min(100f, ((Character)player).GetSkillLevel((SkillType)3))); } private bool TryGetSavedWorthiness(Player player, out float value) { return savedOverride.TryRead(player, "mjolnir_worthiness_override", out value); } internal bool TrySetWorthiness(Player player, float value, out float appliedValue) { appliedValue = ClampWorthiness(value); if ((Object)(object)player == (Object)null || !Object.op_Implicit((Object)(object)player) || float.IsNaN(value) || float.IsInfinity(value)) { return false; } if (player.m_customData == null) { player.m_customData = new Dictionary<string, string>(); } if (IsLiving) { player.m_customData.Remove("mjolnir_worthiness_override"); savedOverride.Invalidate(); WriteLedger(player, appliedValue); return true; } player.m_customData["mjolnir_worthiness_override"] = appliedValue.ToString("0.##", CultureInfo.InvariantCulture); savedOverride.Invalidate(); return true; } internal bool ResetWorthiness(Player player, out float appliedValue) { return TrySetWorthiness(player, IsLiving ? config.LivingWorthinessStart.Value : config.DefaultWorthinessLevel.Value, out appliedValue); } private static float ClampWorthiness(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return Math.Max(0f, Math.Min(100f, value)); } return 0f; } private bool TryEvaluateCustomWorthiness(Player player, ItemData hammer, out float level) { bool result = false; bool flag = false; float num = 0f; foreach (IMjolnirWorthinessProvider registeredProvider in MjolnirApi.RegisteredProviders) { WorthinessDecision worthinessDecision; try { worthinessDecision = registeredProvider.Evaluate(player, hammer); } catch (Exception exception) { WarnDecisionProvider(registeredProvider, exception); level = 0f; return false; } switch (worthinessDecision) { case WorthinessDecision.Deny: level = 0f; return false; case WorthinessDecision.Allow: result = true; if (!(registeredProvider is IMjolnirWorthinessLevelProvider mjolnirWorthinessLevelProvider)) { break; } try { float worthinessLevel = mjolnirWorthinessLevelProvider.GetWorthinessLevel(player, hammer); worthinessLevel = ((float.IsNaN(worthinessLevel) || float.IsInfinity(worthinessLevel)) ? 0f : Math.Max(0f, Math.Min(100f, worthinessLevel))); num = Math.Max(num, worthinessLevel); flag = true; } catch (Exception ex) { Type type = registeredProvider.GetType(); if (warnedLevelProviders.Add(type)) { logger.LogWarning((object)("Worthiness level provider " + type.FullName + " failed: " + ex.Message)); } } break; } } level = (flag ? num : BuiltInWorthiness(player)); return result; } private static float ProgressionWorthiness() { if ((Object)(object)ZoneSystem.instance == (Object)null) { return 0f; } return (float)(0 + (HasGlobalKey("defeated_eikthyr") ? 1 : 0) + (HasGlobalKey("defeated_gdking") ? 1 : 0) + (HasGlobalKey("defeated_bonemass") ? 1 : 0) + ((HasGlobalKey("defeated_dragon") || HasGlobalKey("defeated_moder")) ? 1 : 0) + (HasGlobalKey("defeated_goblinking") ? 1 : 0) + (HasGlobalKey("defeated_queen") ? 1 : 0) + (HasGlobalKey("defeated_fader") ? 1 : 0)) * 14.285714f; } private static bool HasGlobalKey(string key) { return ZoneSystem.instance.GetGlobalKey(key); } private void WarnDecisionProvider(IMjolnirWorthinessProvider provider, Exception exception) { Type type = provider.GetType(); if (warnedDecisionProviders.Add(type)) { logger.LogWarning((object)("Worthiness provider " + type.FullName + " failed closed: " + exception.Message)); } } } public enum WorthinessDecision { Abstain, Allow, Deny } [HarmonyPatch] internal static class WorthinessPatches { [HarmonyPatch(typeof(Humanoid), "EquipItem", new Type[] { typeof(ItemData), typeof(bool) })] [HarmonyPrefix] [HarmonyPriority(400)] private static bool RequireWorthyEquip(Humanoid __instance, ItemData item, ref bool __result) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Invalid comparison between Unknown and I4 //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null || !MjolnirItemIdentity.IsMjolnir(item)) { return true; } MjolnirWorthinessService? current = MjolnirWorthinessService.Current; if (current != null && !current.CanUse(val, item, bindIfAllowed: true)) { __result = false; return false; } HammerClaim val2 = MjolnirItemIdentity.ClaimFor(item, val); if ((int)val2 == 1) { MjolnirItemIdentity.RecordPossession(item, val); return true; } if (!HammerClaimPolicy.PermitsUse(val2)) { if ((Object)(object)val == (Object)(object)Player.m_localPlayer) { MjolnirAbilityController.Current?.ShowFailureAlert(val, "$msg_mjolnir_not_yours"); } __result = false; return false; } return true; } [HarmonyPatch(typeof(Humanoid), "StartAttack", new Type[] { typeof(Character), typeof(bool) })] [HarmonyPrefix] private static bool RequireWorthyAttack(Humanoid __instance, ref bool __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null || ((Humanoid)val).RightItem == null || !MjolnirItemIdentity.IsMjolnir(((Humanoid)val).RightItem)) { return true; } MjolnirWorthinessService? current = MjolnirWorthinessService.Current; if (current != null && !current.CanUse(val, ((Humanoid)val).RightItem, bindIfAllowed: false)) { __result = false; return false; } if (!MjolnirItemIdentity.PermitsUse(((Humanoid)val).RightItem, val)) { __result = false; return false; } return true; } [HarmonyPatch(typeof(ItemDrop), "Interact", new Type[] { typeof(Humanoid), typeof(bool), typeof(bool) })] [HarmonyPrefix] private static bool RequireWorthyPickup(ItemDrop __instance, Humanoid character, ref bool __result) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Invalid comparison between Unknown and I4 Player val = (Player)(object)((character is Player) ? character : null); if (val == null || !MjolnirItemIdentity.IsMjolnir(__instance.m_itemData)) { return true; } MjolnirWorthinessService? current = MjolnirWorthinessService.Current; if (current == null || current.CanUse(val, __instance.m_itemData, bindIfAllowed: false, showMessage: false)) { if ((int)MjolnirItemIdentity.RecordPossession(__instance.m_itemData, val) == 3 && (Object)(object)val == (Object)(object)Player.m_localPlayer) { MjolnirAbilityController.Current?.ShowFailureAlert(val, "$msg_mjolnir_borrowed"); } if ((Object)(object)val == (Object)(object)Player.m_localPlayer) { MjolnirRestingSite.Current?.NotifyLifted(); } return true; } if ((Object)(object)val == (Object)(object)Player.m_localPlayer) { MjolnirRestingSite? current2 = MjolnirRestingSite.Current; if (current2 != null && current2.NotifyRefused()) { goto IL_00bb; } } MjolnirWorthinessService.Current?.ShowUnworthyMessage(val); goto IL_00bb; IL_00bb: __result = false; return false; } [HarmonyPatch(typeof(ItemDrop), "Pickup", new Type[] { typeof(Humanoid) })] [HarmonyPrefix] private static bool RequireWorthyAutoPickup(ItemDrop __instance, Humanoid character) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) Player val = (Player)(object)((character is Player) ? character : null); if (val == null || !MjolnirItemIdentity.IsMjolnir(__instance.m_itemData)) { return true; } MjolnirWorthinessService? current = MjolnirWorthinessService.Current; if (current != null && !current.CanUse(val, __instance.m_itemData, bindIfAllowed: false, showMessage: false)) { return false; } MjolnirItemIdentity.RecordPossession(__instance.m_itemData, val); return true; } } } namespace ValheimMjolnir.UI { internal sealed class MjolnirSettingsMenu : IDisposable { private sealed class BindingRow { internal string Name { get; } internal ConfigEntry<KeyboardShortcut> Entry { get; } internal KeyboardShortcut DefaultValue { get; } internal Button? ChangeButton { get; set; } internal BindingRow(string name, ConfigEntry<KeyboardShortcut> entry, KeyboardShortcut defaultValue) { //IL_0015: 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) Name = name; Entry = entry; DefaultValue = defaultValue; } } private readonly MjolnirConfig config; private readonly InputBindingService input; private readonly MjolnirAbilityController abilities; private readonly MjolnirWorthinessService worthiness; private readonly ManualLogSource logger; private readonly List<BindingRow> bindingRows; private GameObject? panel; private Transform? entries; private GameObject? buttonTemplate; private GameObject? labelTemplate; private GameObject? inputTemplate; private GameObject? sliderTemplate; private Button? bypassButton; private Button? noStaminaButton; private Button? oneShotKillButton; private Button? modelSelectionButton; private Button? storageAttachmentButton; private Button? auxiliaryHudButton; private Button? lightningBoltStyleButton; private Button? lightningDistortionButton; private Button? lightingModeButton; private Button? mapIconButton; private Slider? damageScaleSlider; private Slider? lightningDistortionStrengthSlider; private Slider? lightningDistortionLifetimeSlider; private TMP_Text? distortionStrengthLabel; private TMP_Text? distortionLifetimeLabel; private GuiInputField? worthinessInput; private TMP_Text? currentWorthinessText; private GuiInputField? storedLightningInput; private TMP_Text? currentStoredLightningText; private TMP_Text? damageScaleText; private TMP_Text? statusText; private BindingRow? captureTarget; private CursorLockMode previousCursorLock; private bool previousCursorVisible; private bool visible; internal static bool IsVisible => Current?.visible ?? false; private static MjolnirSettingsMenu? Current { get; set; } internal MjolnirSettingsMenu(MjolnirConfig config, InputBindingService input, MjolnirAbilityController abilities, MjolnirWorthinessService worthiness, ManualLogSource logger) { //IL_0048: 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_008c: 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_00d9: 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_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) this.config = config; this.input = input; this.abilities = abilities; this.worthiness = worthiness; this.logger = logger; bindingRows = new List<BindingRow> { new BindingRow("$kewb_mjolnir_settings", config.OpenSettingsMenuKey, new KeyboardShortcut((KeyCode)285, Array.Empty<KeyCode>())), new BindingRow("$kewb_mjolnir_throw_recall", config.ThrowRecallKey, new KeyboardShortcut((KeyCode)104, Array.Empty<KeyCode>())), new BindingRow("$kewb_mjolnir_lightning", config.LightningKey, new KeyboardShortcut((KeyCode)103, Array.Empty<KeyCode>())), new BindingRow("$kewb_mjolnir_god_blast", config.GodBlastKey, new KeyboardShortcut((KeyCode)50, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 })), new BindingRow("$kewb_mjolnir_flight", config.FlightKey, new KeyboardShortcut((KeyCode)121, Array.Empty<KeyCode>())), new BindingRow("$kewb_mjolnir_crosshair_control", config.CrosshairControlKey, new KeyboardShortcut((KeyCode)106, Array.Empty<KeyCode>())), new BindingRow("$kewb_mjolnir_overhead_slam", config.OverheadSlamKey, new KeyboardShortcut((KeyCode)98, Array.Empty<KeyCode>())), new BindingRow("$kewb_mjolnir_grounded_lightning_rod", config.GroundedLightningRodKey, new KeyboardShortcut((KeyCode)51, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 })), new BindingRow("$kewb_mjolnir_lightning_step", config.LightningStepKey, new KeyboardShortcut((KeyCode)53, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 })), new BindingRow("$kewb_mjolnir_guard", config.GuardKey, new KeyboardShortcut((KeyCode)54, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 })), new BindingRow("$kewb_mjolnir_sentry", config.SentryKey, new KeyboardShortcut((KeyCode)108, Array.Empty<KeyCode>())), new BindingRow("$kewb_mjolnir_cancel", config.CancelAbilityKey, new KeyboardShortcut((KeyCode)120, Array.Empty<KeyCode>())), new BindingRow("$menu_mjolnir_modifier", config.AbilityModifierKey, new KeyboardShortcut((KeyCode)308, Array.Empty<KeyCode>())) }; } internal void Tick() { if (visible) { ForceCursorAvailable(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !Object.op_Implicit((Object)(object)localPlayer) || ((Character)localPlayer).IsDead()) { Close(); } else if (!UnifiedPopup.IsVisible() && input.IsDown(input.OpenSettings)) { Close(); } else if (captureTarget != null) { TickKeyCapture(); } else if (!UnifiedPopup.IsVisible() && Input.GetKeyDown((KeyCode)27)) { Close(); } } else if (CanOpen() && input.IsDown(input.OpenSettings)) { Open(); } } private bool CanOpen() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && Object.op_Implicit((Object)(object)localPlayer) && !((Character)localPlayer).IsDead() && !Menu.IsVisible() && !InventoryGui.IsVisible() && !TextInput.IsVisible() && !Console.IsVisible() && !UnifiedPopup.IsVisible()) { if (!((Object)(object)Chat.instance == (Object)null)) { return !Chat.instance.HasFocus(); } return true; } return false; } private void Open() { //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 (EnsurePanel()) { Current = this; visible = true; Refresh(); panel.SetActive(true); panel.transform.SetAsLastSibling(); previousCursorLock = Cursor.lockState; previousCursorVisible = Cursor.visible; ForceCursorAvailable(); abilities.CancelActiveAbilitiesForSettings(); if ((Object)(object)EventSystem.current != (Object)null && (Object)(object)bypassButton != (Object)null) { EventSystem.current.SetSelectedGameObject(((Component)bypassButton).gameObject); } } } private bool EnsurePanel() { if ((Object)(object)panel != (Object)null) { return true; } if ((Object)(object)Menu.instance == (Object)null || (Object)(object)Menu.instance.m_menuDialog == (Object)null) { logger.LogWarning((object)"Mjolnir settings could not open because Valheim's menu UI is not ready."); return false; } Transform menuDialog = Menu.instance.m_menuDialog; Transform parent = (((Object)(object)Menu.instance.m_root.parent != (Object)null) ? Menu.instance.m_root.parent : menuDialog.parent); Transform val = menuDialog.Find("MenuEntries"); Button val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponentInChildren<Button>(true) : null); TMP_Text val3 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponentInChildren<TMP_Text>(true) : null); GuiInputField val4 = (((Object)(object)TextInput.instance != (Object)null) ? TextInput.instance.m_inputField : null); Slider val5 = (((Object)(object)InventoryGui.instance != (Object)null && (Object)(object)InventoryGui.instance.m_splitDialog != (Object)null) ? InventoryGui.instance.m_splitDialog.m_splitSlider : null); Image val6 = FindNativePanelImage(); if ((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) { logger.LogError((object)"Mjolnir settings could not find Valheim's native panel, button, text, input-field, or slider assets."); return false; } buttonTemplate = ((Component)val2).gameObject; labelTemplate = ((Component)val3).gameObject; inputTemplate = ((Component)val4).gameObject; sliderTemplate = ((Component)val5).gameObject; panel = CreateNativePanel(parent, val6); entries = CreateContentRoot(panel.transform); BuildPanel(); return true; } private static Image? FindNativePanelImage() { RectTransform val = (((Object)(object)InventoryGui.instance != (Object)null) ? InventoryGui.instance.m_crafting : null); if ((Object)(object)val == (Object)null) { return null; } Image component = ((Component)val).GetComponent<Image>(); if ((Object)(object)component != (Object)null && (Object)(object)component.sprite != (Object)null) { return component; } return (from image in ((Component)val).GetComponentsInChildren<Image>(true) where (Object)(object)image.sprite != (Object)null select image).OrderByDescending(delegate(Image image) { //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_0019: 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) Rect rect = ((Graphic)image).rectTransform.rect; float width = ((Rect)(ref rect)).width; rect = ((Graphic)image).rectTransform.rect; return width * ((Rect)(ref rect)).height; }).FirstOrDefault(); } private static GameObject CreateNativePanel(Transform parent, Image template) { //IL_0032: 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: 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_00b3: Expected O, but got Unknown GameObject val = new GameObject("MjolnirSettingsMenu", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val.transform.SetParent(parent, false); Image component = val.GetComponent<Image>(); component.sprite = template.sprite; ((Graphic)component).material = ((Graphic)template).material; ((Graphic)component).color = ((Graphic)template).color; component.type = template.type; component.preserveAspect = template.preserveAspect; ((Graphic)component).raycastTarget = true; Position(val.GetComponent<RectTransform>(), Vector2.zero, new Vector2(960f, 840f)); val.SetActive(false); return val; } private static Transform CreateContentRoot(Transform parent) { //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) //IL_002a: 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_003c: 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_0051: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Content", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); RectTransform component = val.GetComponent<RectTransform>(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; return val.transform; } private void BuildPanel() { //IL_0010: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: 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_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0155: 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_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Unknown result type (might be due to invalid IL or missing references) //IL_0350: Unknown result type (might be due to invalid IL or missing references) //IL_035f: Unknown result type (might be due to invalid IL or missing references) //IL_0382: Unknown result type (might be due to invalid IL or missing references) //IL_0391: Unknown result type (might be due to invalid IL or missing references) //IL_03b2: Unknown result type (might be due to invalid IL or missing references) //IL_03cc: Unknown result type (might be due to invalid IL or missing references) //IL_03db: Unknown result type (might be due to invalid IL or missing references) //IL_0402: Unknown result type (might be due to invalid IL or missing references) //IL_0411: Unknown result type (might be due to invalid IL or missing references) //IL_043b: Unknown result type (might be due to invalid IL or missing references) //IL_044a: Unknown result type (might be due to invalid IL or missing references) //IL_0470: Unknown result type (might be due to invalid IL or missing references) //IL_047f: Unknown result type (might be due to invalid IL or missing references) //IL_051c: Unknown result type (might be due to invalid IL or missing references) //IL_052b: Unknown result type (might be due to invalid IL or missing references) //IL_0554: Unknown result type (might be due to invalid IL or missing references) //IL_0563: Unknown result type (might be due to invalid IL or missing references) //IL_058f: Unknown result type (might be due to invalid IL or missing references) //IL_059e: Unknown result type (might be due to invalid IL or missing references) //IL_05da: Unknown result type (might be due to invalid IL or missing references) //IL_05e9: Unknown result type (might be due to invalid IL or missing references) CreateLabel("$kewb_mjolnir_settings", new Vector2(-25f, 350f), new Vector2(500f, 36f), 24f, out GameObject labelObject); CreateButton("$menu_mjolnir_close", new Vector2(405f, 350f), new Vector2(100f, 30f), Close); CreateLabel("$menu_mjolnir_worthiness", new Vector2(0f, 315f), new Vector2(700f, 24f), 16f, out labelObject); bypassButton = CreateButton("", new Vector2(0f, 284f), new Vector2(620f, 30f), ToggleBypass); CreateLabel("$menu_mjolnir_bypass_hint", new Vector2(0f, 258f), new Vector2(760f, 20f), 12f, out labelObject); CreateLabel("$menu_mjolnir_manual_worthiness", new Vector2(-260f, 228f), new Vector2(220f, 24f), 14f, out labelObject); currentWorthinessText = CreateLabel("", new Vector2(120f, 228f), new Vector2(390f, 24f), 14f, out labelObject); CreateWorthinessInput(new Vector2(-250f, 197f)); CreateButton("$menu_mjolnir_apply", new Vector2(80f, 197f), new Vector2(120f, 28f), ApplyWorthiness); CreateLabel("$menu_mjolnir_valid_range", new Vector2(-250f, 164f), new Vector2(240f, 20f), 12f, out labelObject); CreateButton("$menu_mjolnir_reset_base", new Vector2(95f, 164f), new Vector2(210f, 28f), ConfirmResetWorthiness); CreateLabel("$menu_mjolnir_gameplay", new Vector2(0f, 130f), new Vector2(700f, 24f), 16f, out labelObject); noStaminaButton = CreateButton("", new Vector2(0f, 101f), new Vector2(500f, 26f), ToggleNoStaminaCost); oneShotKillButton = CreateButton("", new Vector2(0f, 72f), new Vector2(500f, 26f), ToggleOneShotKill); CreateLabel("$menu_mjolnir_base_damage", new Vector2(-290f, 42f), new Vector2(180f, 22f), 13f, out labelObject); CreateDamageScaleSlider(new Vector2(15f, 42f)); damageScaleText = CreateLabel("", new Vector2(230f, 42f), new Vector2(130f, 22f), 12f, out labelObject); CreateLabel("$hud_mjolnir_stored_lightning", new Vector2(-300f, 12f), new Vector2(170f, 22f), 13f, out labelObject); currentStoredLightningText = CreateLabel("", new Vector2(-145f, 12f), new Vector2(120f, 22f), 12f, out labelObject); CreateStoredLightningInput(new Vector2(20f, 12f)); CreateButton("$menu_mjolnir_set", new Vector2(160f, 12f), new Vector2(90f, 28f), ApplyStoredLightning); CreateLabel("$menu_mjolnir_presentation", new Vector2(0f, -25f), new Vector2(700f, 24f), 16f, out labelObject); float num = CreatePresentationButtons(-54f); CreateLabel("$menu_mjolnir_saved_immediately", new Vector2(0f, num), new Vector2(700f, 20f), 12f, out labelObject); float num2 = num - 29f; CreateLabel("$menu_mjolnir_key_bindings", new Vector2(-90f, num2), new Vector2(700f, 24f), 16f, out labelObject); List<BindingRow> list = bindingRows.Where(IsBindingVisible).ToList(); int num3 = (list.Count + 1) / 2; for (int i = 0; i < list.Count; i++) { BindingRow row = list[i]; int num4 = i / num3; int num5 = i % num3; float num6 = (float)num4 * 470f; float num7 = num2 - 29f - (float)num5 * 27f; CreateLabel(row.Name, new Vector2(-370f + num6, num7), new Vector2(170f, 22f), 12f, out labelObject); row.ChangeButton = CreateButton("", new Vector2(-215f + num6, num7), new Vector2(130f, 24f), delegate { BeginCapture(row); }); CreateButton("$menu_mjolnir_reset", new Vector2(-105f + num6, num7), new Vector2(80f, 24f), delegate { ResetBinding(row); }); } statusText = CreateLabel("", new Vector2(0f, -394f), new Vector2(860f, 22f), 12f, out labelObject); } private float CreatePresentationButtons(float firstRowY) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) List<Action<Vector2, Vector2>> list = new List<Action<Vector2, Vector2>>(); list.Add(delegate(Vector2 position, Vector2 size) { //IL_0007: 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) modelSelectionButton = CreateButton("", position, size, ToggleModelSelection); }); list.Add(delegate(Vector2 position, Vector2 size) { //IL_0007: 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) auxiliaryHudButton = CreateButton("", position, size, ToggleAuxiliaryHud); }); list.Add(delegate(Vector2 position, Vector2 size) { //IL_0007: 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) storageAttachmentButton = CreateButton("", position, size, ToggleStorageAttachment); }); list.Add(delegate(Vector2 position, Vector2 size) { //IL_0007: 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) mapIconButton = CreateButton("", position, size, ToggleMapIcon); }); for (int num = 0; num * 4 < list.Count; num++) { int num2 = num * 4; int num3 = Math.Min(4, list.Count - num2); float num4 = num3 switch { 1 => 400f, 2 => 400f, 3 => 260f, _ => 190f, }; float num5 = num4 + 16f; float num6 = (0f - (float)(num3 - 1) * num5) / 2f; for (int num7 = 0; num7 < num3; num7++) { list[num2 + num7](new Vector2(num6 + (float)num7 * num5, firstRowY - (float)num * 30f), new Vector2(num4, 26f)); } } int num8 = (list.Count + 4 - 1) / 4; return firstRowY - (float)num8 * 30f; } private void CreateDamageScaleSlider(Vector2 position) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown //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) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: 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) GameObject val = Object.Instantiate<GameObject>(sliderTemplate, entries, false); ((Object)val).name = "MjolnirDamageScaleSlider"; damageScaleSlider = val.GetComponent<Slider>(); damageScaleSlider.onValueChanged = new SliderEvent(); damageScaleSlider.minValue = 1f; damageScaleSlider.maxValue = 10f; damageScaleSlider.wholeNumbers = true; ((UnityEvent<float>)(object)damageScaleSlider.onValueChanged).AddListener((UnityAction<float>)SetDamageScale); Navigation navigation = ((Selectable)damageScaleSlider).navigation; ((Navigation)(ref navigation)).mode = (Mode)3; ((Selectable)damageScaleSlider).navigation = navigation; Position(val.GetComponent<RectTransform>(), position, new Vector2(250f, 24f)); val.SetActive(true); } private void CreateDistortionSlider(string name, Vector2 position, float maximum, out Slider? slider, UnityAction<float> changed) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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) //IL_007c: Unknown result type (might be due to invalid IL or missing references) GameObject obj = Object.Instantiate<GameObject>(sliderTemplate, entries, false); ((Object)obj).name = name; Slider val = (slider = obj.GetComponent<Slider>()); val.onValueChanged = new SliderEvent(); val.minValue = 0f; val.maxValue = maximum; val.wholeNumbers = false; ((UnityEvent<float>)(object)val.onValueChanged).AddListener(changed); Navigation navigation = ((Selectable)val).navigation; ((Navigation)(ref navigation)).mode = (Mode)3; ((Selectable)val).navigation = navigation; Position(obj.GetComponent<RectTransform>(), position, new Vector2(250f, 24f)); obj.SetActive(true); } private void CreateWorthinessInput(Vector2 position) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) GameObject val = Object.Instantiate<GameObject>(inputTemplate, entries, false); ((Object)val).name = "MjolnirWorthinessInput"; worthinessInput = val.GetComponent<GuiInputField>(); StripInheritedListeners(worthinessInput); ((TMP_InputField)worthinessInput).contentType = (ContentType)3; ((TMP_InputField)worthinessInput).characterLimit = 6; Position(val.GetComponent<RectTransform>(), position, new Vector2(155f, 38f)); val.SetActive(true); } private void CreateStoredLightningInput(Vector2 position) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) GameObject val = Object.Instantiate<GameObject>(inputTemplate, entries, false); ((Object)val).name = "MjolnirStoredLightningInput"; storedLightningInput = val.GetComponent<GuiInputField>(); StripInheritedListeners(storedLightningInput); ((TMP_InputField)storedLightningInput).contentType = (ContentType)3; ((TMP_InputField)storedLightningInput).characterLimit = 6; Position(val.GetComponent<RectTransform>(), position, new Vector2(110f, 36f)); val.SetActive(true); } private static void StripInheritedListeners(GuiInputField field) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown ((TMP_InputField)field).onValueChanged = new OnChangeEvent(); ((TMP_InputField)field).onEndEdit = new SubmitEvent(); ((TMP_InputField)field).onSubmit = new SubmitEvent(); } private Button CreateButton(string text, Vector2 position, Vector2 size, Action action) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: 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) GameObject val = Object.Instantiate<GameObject>(buttonTemplate, entries, false); ((Object)val).name = "MjolnirSettingsButton"; Button component = val.GetComponent<Button>(); component.onClick = new ButtonClickedEvent(); ((UnityEvent)component.onClick).AddListener((UnityAction)delegate { action(); }); Navigation navigation = ((Selectable)component).navigation; ((Navigation)(ref navigation)).mode = (Mode)3; ((Selectable)component).navigation = navigation; TMP_Text componentInChildren = ((Component)component).GetComponentInChildren<TMP_Text>(true); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = Localize(text); componentInChildren.enableAutoSizing = true; componentInChildren.fontSizeMin = 10f; componentInChildren.fontSizeMax = 18f; } Position(val.GetComponent<RectTransform>(), position, size); val.SetActive(true); return component; } private TMP_Text CreateLabel(string text, Vector2 position, Vector2 size, float fontSize, out GameObject labelObject) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) labelObject = Object.Instantiate<GameObject>(labelTemplate, entries, false); ((Object)labelObject).name = "MjolnirSettingsLabel"; TMP_Text component = labelObject.GetComponent<TMP_Text>(); component.text = Localize(text); component.fontSize = fontSize; component.alignment = (TextAlignmentOptions)4097; Position(labelObject.GetComponent<RectTransform>(), position, size); labelObject.SetActive(true); return component; } private static void Position(RectTransform rect, Vector2 position, 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) //IL_004e: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = new Vector2(0.5f, 0.5f); rect.anchorMax = new Vector2(0.5f, 0.5f); rect.pivot = new Vector2(0.5f, 0.5f); rect.anchoredPosition = position; rect.sizeDelta = size; ((Transform)rect).localScale = Vector3.one; } private void Refresh() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0415: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !Object.op_Implicit((Object)(object)localPlayer)) { return; } float worthinessLevel = worthiness.GetWorthinessLevel(localPlayer); if ((Object)(object)worthinessInput != (Object)null) { ((TMP_InputField)worthinessInput).text = worthinessLevel.ToString("0.##", CultureInfo.InvariantCulture); } if ((Object)(object)currentWorthinessText != (Object)null) { string text = MjolnirPowerNames.Localize(MjolnirPowerNames.TierToken(WorthinessTierPolicy.FromLevel(worthinessLevel))); currentWorthinessText.text = string.Format("{0}: {1:0.##} / {2:0} ({3})", Localize("$menu_mjolnir_current"), worthinessLevel, 100f, text); } float charge = abilities.Passives.Charge; if ((Object)(object)currentStoredLightningText != (Object)null) { currentStoredLightningText.text = $"{charge:0.##} / {config.AbsorptionChargeCap.Value:0}"; } if ((Object)(object)storedLightningInput != (Object)null && !((TMP_InputField)storedLightningInput).isFocused) { ((TMP_InputField)storedLightningInput).text = charge.ToString("0.##", CultureInfo.InvariantCulture); } if ((Object)(object)bypassButton != (Object)null) { SetButtonText(bypassButton, Localize("$menu_mjolnir_bypass") + ": " + OnOff(config.BypassWorthinessProgression.Value)); } if ((Object)(object)noStaminaButton != (Object)null) { SetButtonText(noStaminaButton, Localize("$menu_mjolnir_no_stamina") + ": " + OnOff(config.NoStaminaCost.Value)); } if ((Object)(object)oneShotKillButton != (Object)null) { SetButtonText(oneShotKillButton, Localize("$menu_mjolnir_one_shot") + ": " + OnOff(config.OneShotKill.Value)); } if ((Object)(object)auxiliaryHudButton != (Object)null) { SetButtonText(auxiliaryHudButton, Localize("$menu_mjolnir_aux_hud") + ": " + OnOff(config.ShowAbilityHud.Value)); } if ((Object)(object)modelSelectionButton != (Object)null) { SetButtonText(modelSelectionButton, Localize("$menu_mjolnir_model") + ": " + ModelDisplayName(config.Model.Value)); } if ((Object)(object)storageAttachmentButton != (Object)null) { SetButtonText(storageAttachmentButton, Localize("$menu_mjolnir_holster") + ": " + StorageAttachmentDisplayName(config.StorageAttachmentMode.Value)); } if ((Object)(object)lightningBoltStyleButton != (Object)null) { SetButtonText(lightningBoltStyleButton, Localize("$menu_mjolnir_bolts") + ": " + BoltStyleDisplayName(config.LightningBoltStyle.Value)); } if ((Object)(object)lightningDistortionButton != (Object)null) { SetButtonText(lightningDistortionButton, Localize("$menu_mjolnir_distortion") + ": " + OnOff(config.LightningDistortion.Value)); } if ((Object)(object)lightingModeButton != (Object)null) { SetButtonText(lightingModeButton, Localize("$menu_mjolnir_lighting") + ": " + LightingModeDisplayName(config.LightingMode.Value)); } if ((Object)(object)mapIconButton != (Object)null) { SetButtonText(mapIconButton, Localize("$menu_mjolnir_map_icon") + ": " + OnOff(config.ShowHammerMapMarker.Value)); } UpdateDamageScalePresentation(); UpdateDistortionPresentation(); foreach (BindingRow bindingRow in bindingRows) { if ((Object)(object)bindingRow.ChangeButton != (Object)null) { SetButtonText(bindingRow.ChangeButton, (captureTarget == bindingRow) ? "$menu_mjolnir_press_key" : FormatShortcut(bindingRow.Entry.Value)); } } } private static void SetButtonText(Button button, string text) { TMP_Text componentInChildren = ((Component)button).GetComponentInChildren<TMP_Text>(true); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = Localize(text); } } private static string Localize(string text) { if (!text.StartsWith("$", StringComparison.Ordinal) || Localization.instance == null) { return text; } return Localization.instance.Localize(text); } private static string OnOff(bool enabled) { return Localize(enabled ? "$menu_mjolnir_on" : "$menu_mjolnir_off"); } private static string FormatText(string token, params object[] args) { return string.Format(CultureInfo.InvariantCulture, Localize(token), args); } private void ToggleBypass() { config.BypassWorthinessProgression.Value = !config.BypassWorthinessProgression.Value; config.Save(); if (!config.BypassWorthinessProgression.Value) { SetStatus(worthiness.IsLiving ? "$menu_mjolnir_bypass_living_off" : "$menu_mjolnir_bypass_saved_off"); Refresh(); } else { SetStatus(worthiness.IsLiving ? "$menu_mjolnir_bypass_living_on" : "$menu_mjolnir_bypass_saved_on"); Refresh(); } } private void ToggleNoStaminaCost() { config.NoStaminaCost.Value = !config.NoStaminaCost.Value; config.Save(); SetStatus(config.NoStaminaCost.Value ? "$menu_mjolnir_stamina_off_status" : "$menu_mjolnir_stamina_on_status"); Refresh(); } private void ToggleOneShotKill() { config.OneShotKill.Value = !config.OneShotKill.Value; config.Save(); SetStatus(config.OneShotKill.Value ? "$menu_mjolnir_one_shot_on_status" : "$menu_mjolnir_one_shot_off_status"); Refresh(); } private void ToggleAuxiliaryHud() { config.ShowAbilityHud.Value = !config.ShowAbilityHud.Value; config.Save(); SetStatus(config.ShowAbilityHud.Value ? "$menu_mjolnir_hud_shown" : "$menu_mjolnir_hud_hidden"); Refresh(); } private void ToggleModelSelection() { config.Model.Value = MjolnirModelCatalog.Next(config.Model.Value); config.Save(); SetStatus(FormatText("$menu_mjolnir_model_status", ModelDisplayName(config.Model.Value))); Refresh(); } private static string ModelDisplayName(MjolnirModelSelection selection) { return Localize(selection switch { MjolnirModelSelection.Ornate => "$menu_mjolnir_model_ornate", MjolnirModelSelection.Legacy => "$menu_mjolnir_model_legacy", _ => "$menu_mjolnir_model_default", }); } private void ToggleStorageAttachment() { config.StorageAttachmentMode.Value = ((config.StorageAttachmentMode.Value == MjolnirStorageAttachmentMode.Hip) ? MjolnirStorageAttachmentMode.Back : MjolnirStorageAttachmentMode.Hip); config.Save(); SetStatus((config.StorageAttachmentMode.Value == MjolnirStorageAttachmentMode.Hip) ? "$menu_mjolnir_holster_waist_status" : "$menu_mjolnir_holster_back_status"); Refresh(); } private static string StorageAttachmentDisplayName(MjolnirStorageAttachmentMode mode) { return Localize((mode == MjolnirStorageAttachmentMode.Hip) ? "$menu_mjolnir_holster_waist" : "$menu_mjolnir_holster_normal"); } private void ToggleMapIcon() { config.ShowHammerMapMarker.Value = !config.ShowHammerMapMarker.Value; config.Save(); SetStatus(config.ShowHammerMapMarker.Value ? "$menu_mjolnir_map_shown" : "$menu_mjolnir_map_hidden"); Refresh(); } private void ToggleLightningBoltStyle() { config.LightningBoltStyle.Value = ((config.LightningBoltStyle.Value == LightningBoltStyle.Layered) ? LightningBoltStyle.Unified : LightningBoltStyle.Layered); config.Save(); SetStatus((config.LightningBoltStyle.Value == LightningBoltStyle.Unified) ? "$menu_mjolnir_bolts_unified_status" : "$menu_mjolnir_bolts_layered_status"); Refresh(); } private static string BoltStyleDisplayName(LightningBoltStyle style) { return Localize((style == LightningBoltStyle.Unified) ? "$menu_mjolnir_bolts_unified" : "$menu_mjolnir_bolts_layered"); } private void ToggleLightingMode() { config.LightingMode.Value = ((config.LightingMode.Value == LightingMode.Legacy) ? LightingMode.BoneFollowing : LightingMode.Legacy); config.Save(); SetStatus((config.LightingMode.Value == LightingMode.BoneFollowing) ? "$menu_mjolnir_lighting_bone_status" : "$menu_mjolnir_lighting_legacy_status"); Refresh(); } private static string LightingModeDisplayName(LightingMode mode) { return Localize((mode == LightingMode.BoneFollowing) ? "$menu_mjolnir_lighting_bone" : "$menu_mjolnir_lighting_legacy"); } private void ToggleLightningDistortion() { config.LightningDistortion.Value = !config.LightningDistortion.Value; config.Save(); SetStatus(config.LightningDistortion.Value ? "$menu_mjolnir_distortion_on_status" : "$menu_mjolnir_distortion_off_status"); Refresh(); } private void SetLightningDistortionStrength(float requested) { float value = Mathf.Clamp(Mathf.Round(requested * 20f) / 20f, 0f, 10f); config.LightningDistortionStrength.Value = value; config.Save(); UpdateDistortionPresentation(); SetStatus(FormatText("$menu_mjolnir_distortion_strength_status", value.ToString("0.00", CultureInfo.InvariantCulture))); } private void SetLightningDistortionLifetime(float requested) { float num = Mathf.Clamp(Mathf.Round(requested * 20f) / 20f, 0f, 5f); config.LightningDistortionLifetime.Value = num; config.Save(); UpdateDistortionPresentation(); SetStatus((num <= 0f) ? Localize("$menu_mjolnir_distortion_follow_status") : FormatText("$menu_mjolnir_distortion_lifetime_status", num.ToString("0.00", CultureInfo.InvariantCulture))); } private void SetDamageScale(float requested) { float value = Mathf.Clamp(Mathf.Round(requested), 1f, 10f); config.GameplayDamageScale.Value = value; config.Save(); UpdateDamageScalePresentation(); SetStatus(config.OneShotKill.Value ? FormatText("$menu_mjolnir_damage_override_status", value.ToString("0", CultureInfo.InvariantCulture)) : FormatText("$menu_mjolnir_damage_status", value.ToString("0", CultureInfo.InvariantCulture))); } private void UpdateDamageScalePresentation() { float multiplier = MjolnirDamageScaling.GetMultiplier(config); Slider? obj = damageScaleSlider; if (obj != null) { obj.SetValueWithoutNotify(multiplier); } if (!((Object)(object)damageScaleText == (Object)null)) { if (config.OneShotKill.Value) { damageScaleText.text = FormatText("$menu_mjolnir_damage_overridden", multiplier.ToString("0", CultureInfo.InvariantCulture)); return; } int worldHammerCount = MjolnirDamageScaling.GetWorldHammerCount(); damageScaleText.text = ((worldHammerCount > 1) ? FormatText("$menu_mjolnir_crowd_damage", multiplier.ToString("0", CultureInfo.InvariantCulture), MjolnirDamageScaling.GetCrowdMultiplier().ToString("0.00", CultureInfo.InvariantCulture), worldHammerCount) : $"{multiplier:0}x"); } } private void UpdateDistortionPresentation() { float num = Mathf.Clamp(config.LightningDistortionStrength.Value, 0f, 10f); float num2 = Mathf.Clamp(config.LightningDistortionLifetime.Value, 0f, 5f); Slider? obj = lightningDistortionStrengthSlider; if (obj != null) { obj.SetValueWithoutNotify(num); } Slider? obj2 = lightningDistortionLifetimeSlider; if (obj2 != null) { obj2.SetValueWithoutNotify(num2); } if ((Object)(object)distortionStrengthLabel != (Object)null) { distortionStrengthLabel.text = $"{num:0.00}x"; } if ((Object)(object)distortionLifetimeLabel != (Object)null) { distortionLifetimeLabel.text = ((num2 <= 0f) ? Localize("$menu_mjolnir_follow_bolt") : $"{num2:0.00}s"); } } private void ApplyWorthiness() { if ((Object)(object)worthinessInput == (Object)null || string.IsNullOrWhiteSpace(((TMP_InputField)worthinessInput).text) || !float.TryParse(((TMP_InputField)worthinessInput).text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || float.IsNaN(result) || float.IsInfinity(result)) { SetStatus("$menu_mjolnir_invalid_worthiness"); return; } if (result < 0f || result > 100f) { SetStatus("$menu_mjolnir_worthiness_range"); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !worthiness.TrySetWorthiness(localPlayer, result, out var appliedValue)) { SetStatus("$menu_mjolnir_no_player"); return; } SetStatus(worthiness.IsLiving ? FormatText("$menu_mjolnir_worthiness_set_living", appliedValue.ToString("0.##", CultureInfo.InvariantCulture)) : FormatText("$menu_mjolnir_worthiness_set", appliedValue.ToString("0.##", CultureInfo.InvariantCulture))); Refresh(); } private void ApplyStoredLightning() { float value = config.AbsorptionChargeCap.Value; if ((Object)(object)storedLightningInput == (Object)null || string.IsNullOrWhiteSpace(((TMP_InputField)storedLightningInput).text) || !float.TryParse(((TMP_InputField)storedLightningInput).text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || float.IsNaN(result) || float.IsInfinity(result)) { SetStatus(FormatText("$menu_mjolnir_invalid_charge", value.ToString("0", CultureInfo.InvariantCulture))); return; } if (result < 0f || result > value) { SetStatus(FormatText("$menu_mjolnir_charge_range", value.ToString("0", CultureInfo.InvariantCulture))); return; } float num = abilities.Passives.SetCharge(result); SetStatus(FormatText("$menu_mjolnir_charge_set", num.ToString("0.##", CultureInfo.InvariantCulture))); Refresh(); } private void ConfirmResetWorthiness() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_005f: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown if (!UnifiedPopup.IsAvailable()) { SetStatus("$menu_mjolnir_popup_unavailable"); return; } UnifiedPopup.Push((PopupBase)new YesNoPopup(Localize("$menu_mjolnir_reset_worthiness"), worthiness.IsLiving ? Localize("$menu_mjolnir_reset_living_prompt") : Localize("$menu_mjolnir_reset_saved_prompt"), (PopupButtonCallback)delegate { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && worthiness.ResetWorthiness(localPlayer, out var appliedValue)) { SetStatus(worthiness.IsLiving ? FormatText("$menu_mjolnir_reset_living_status", appliedValue.ToString("0.##", CultureInfo.InvariantCulture)) : FormatText("$menu_mjolnir_reset_saved_status", appliedValue.ToString("0.##", CultureInfo.InvariantCulture))); Refresh(); } UnifiedPopup.Pop(); }, new PopupButtonCallback(UnifiedPopup.Pop), false, false)); } private void BeginCapture(BindingRow row) { captureTarget = row; SetStatus("$menu_mjolnir_capture_prompt"); Refresh(); } private void TickKeyCapture() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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 (captureTarget == null) { return; } if (Input.GetKeyDown((KeyCode)27)) { captureTarget = null; SetStatus("$menu_mjolnir_binding_cancelled"); Refresh(); return; } KeyboardShortcut shortcut = default(KeyboardShortcut); foreach (KeyCode item in Enum.GetValues(typeof(KeyCode)).Cast<KeyCode>()) { if ((int)item != 0 && (int)item != 27 && !IsModifier(item) && Input.GetKeyDown(item)) { ((KeyboardShortcut)(ref shortcut))..ctor(item, ActiveModifiers().ToArray()); BindingRow target = captureTarget; captureTarget = null; AssignBinding(target, shortcut); break; } } } private bool IsBindingVisible(BindingRow row) { return true; } private void AssignBinding(BindingRow target, KeyboardShortcut shortcut) { //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) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_00de: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_005f: Unknown result type (might be due to invalid IL or missing references) BindingRow bindingRow = bindingRows.FirstOrDefault((BindingRow row) => row != target && SameShortcut(row.Entry.Value, shortcut)); if (bindingRow == null) { ApplyBinding(target, shortcut); return; } if (!UnifiedPopup.IsAvailable()) { SetStatus(FormatText("$menu_mjolnir_binding_taken", FormatShortcut(shortcut), Localize(bindingRow.Name))); Refresh(); return; } UnifiedPopup.Push((PopupBase)new YesNoPopup(Localize("$menu_mjolnir_binding_conflict"), FormatText("$menu_mjolnir_binding_conflict_prompt", FormatShortcut(shortcut), Localize(bindingRow.Name)), (PopupButtonCallback)delegate { //IL_000d: Unknown result type (might be due to invalid IL or missing references) ApplyBinding(target, shortcut); UnifiedPopup.Pop(); }, (PopupButtonCallback)delegate { SetStatus("$menu_mjolnir_binding_cancelled"); Refresh(); UnifiedPopup.Pop(); }, false, false)); } private void ApplyBinding(BindingRow row, KeyboardShortcut shortcut) { //IL_0006: 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) row.Entry.Value = shortcut; config.Save(); SetStatus(FormatText("$menu_mjolnir_binding_set", Localize(row.Name), FormatShortcut(shortcut))); Refresh(); } private void ResetBinding(BindingRow row) { //IL_0007: 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) row.Entry.Value = row.DefaultValue; config.Save(); SetStatus(FormatText("$menu_mjolnir_binding_reset", Localize(row.Name), FormatShortcut(row.DefaultValue))); Refresh(); } private static IEnumerable<KeyCode> ActiveModifiers() { if (Input.GetKey((KeyCode)304)) { yield return (KeyCode)304; } else if (Input.GetKey((KeyCode)303)) { yield return (KeyCode)303; } if (Input.GetKey((KeyCode)306)) { yield return (KeyCode)306; } else if (Input.GetKey((KeyCode)305)) { yield return (KeyCode)305; } if (Input.GetKey((KeyCode)308)) { yield return (KeyCode)308; } else if (Input.GetKey((KeyCode)307)) { yield return (KeyCode)307; } } private static bool IsModifier(KeyCode key) { //IL_0000: 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: Invalid comparison between Unknown and I4 if (key - 303 <= 5) { return true; } return false; } private static bool SameShortcut(KeyboardShortcut left, KeyboardShortcut right) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: 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_0016: Unknown result type (might be due to invalid IL or missing references) if (((KeyboardShortcut)(ref left)).MainKey == ((KeyboardShortcut)(ref right)).MainKey) { return CanonicalModifiers(left).SequenceEqual(CanonicalModifiers(right)); } return false; } private static string FormatShortcut(KeyboardShortcut shortcut) { //IL_0002: 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) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0) { return Localize("$menu_mjolnir_unbound"); } IEnumerable<string> first = ((KeyboardShortcut)(ref shortcut)).Modifiers.Select(FormatKey).Distinct(); return string.Join("+", first.Concat(new string[1] { FormatKey(((KeyboardShortcut)(ref shortcut)).MainKey) })); } private unsafe static string FormatKey(KeyCode key) { //IL_0000: 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: Invalid comparison between Unknown and I4 //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 //IL_0023: 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_0028: Expected I4, but got Unknown if (key - 307 <= 1) { return "Alt"; } if ((int)key >= 48 && (int)key <= 57) { return (key - 48).ToString(CultureInfo.InvariantCulture); } return ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(); } private static IEnumerable<int> CanonicalModifiers(KeyboardShortcut shortcut) { return from key in ((KeyboardShortcut)(ref shortcut)).Modifiers.Select((KeyCode key) => (key - 307 > 1) ? ((int)key) : 308).Distinct() orderby key select key; } private void SetStatus(string message) { if ((Object)(object)statusText != (Object)null) { statusText.text = Localize(message); } } private void Close() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) captureTarget = null; if (visible) { visible = false; if ((Object)(object)panel != (Object)null) { panel.SetActive(false); } Cursor.lockState = previousCursorLock; Cursor.visible = previousCursorVisible; if (Current == this) { Current = null; } } } internal static void ForceCursorAvailable() { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } public void Dispose() { Close(); if ((Object)(object)panel != (Object)null) { Object.Destroy((Object)(object)panel); panel = null; } } } [HarmonyPatch(typeof(GameCamera), "UpdateMouseCapture")] internal static class MjolnirSettingsCursorPatch { [HarmonyPostfix] private static void KeepCursorUnlockedWhileMenuIsOpen() { if (MjolnirSettingsMenu.IsVisible) { MjolnirSettingsMenu.ForceCursorAvailable(); } } } [HarmonyPatch(typeof(PlayerController), "TakeInput", new Type[] { typeof(bool) })] internal static class MjolnirSettingsPlayerInputPatch { [HarmonyPostfix] private static void BlockPlayerInputWhileMenuIsOpen(ref bool __result) { if (MjolnirSettingsMenu.IsVisible) { __result = false; } } } [HarmonyPatch] internal static class MjolnirSettingsHotbarInputPatch { private static readonly MethodInfo? PlayerTakeInput = AccessTools.Method(typeof(Player), "TakeInput", (Type[])null, (Type[])null); private static bool Prepare() { return PlayerTakeInput != null; } private static MethodInfo TargetMethod() { return PlayerTakeInput; } [HarmonyPostfix] private static void BlockHotbarWhileMenuIsOpen(ref bool __result) { if (MjolnirSettingsMenu.IsVisible) { __result = false; } } } } namespace ValheimMjolnir.Presentation { internal sealed class MjolnirAbilityHudPresenter : MonoBehaviour { private const float PanelWidth = 250f; private const float RowHeight = 18f; private const float BarHeight = 9f; private const float Padding = 9f; private const float MinimapGap = 8f; private const float FallbackRightMargin = 18f; private const float FallbackTopFraction = 0.26f; private const int MaximumCooldownRows = 5; private static readonly Color PanelText = new Color(0.78f, 0.9f, 1f, 1f); private static readonly Color BarBackground = new Color(0.05f, 0.07f, 0.1f, 0.75f); private static readonly Color StoredLightningFill = new Color(0.36f, 0.74f, 1f, 0.95f); private static readonly Color WorthinessFill = new Color(0.85f, 0.78f, 0.45f, 0.95f); private static readonly Color ChargeFill = new Color(1f, 0.86f, 0.45f, 0.95f); private static readonly Color StageMark = new Color(0.85f, 0.95f, 1f, 0.85f); private static readonly Color CooldownFill = new Color(0.5f, 0.56f, 0.66f, 0.9f); private static readonly FieldInfo? SmallMapRoot = AccessTools.Field(typeof(Minimap), "m_smallRoot"); private MjolnirConfig config; private GUIStyle? panelStyle; private GUIStyle? labelStyle; private GUIStyle? valueStyle; private GUIStyle? timeStyle; private Texture2D? fill; private string hammerStateText = string.Empty; private string? hammerStateFor; private string? hammerAbilityFor; private string storedLightningText = string.Empty; private float storedLightning01 = -1f; private int storedLightningShown = int.MinValue; private string worthinessText = string.Empty; private float worthiness01 = -1f; private WorthinessTier worthinessTierShown = (WorthinessTier)(-1); private int worthinessShown = int.MinValue; private string chargeText = string.Empty; private string chargeDetailText = string.Empty; private float charge01 = -1f; private float chargeMarkA = -1f; private float chargeMarkB = -1f; private string? chargeLabelFor; private string? chargeStageFor; private int chargePercentShown = int.MinValue; private ThrowChargeTier throwTierShown = (ThrowChargeTier)(-1); private int throwRangeShown = int.MinValue; private int throwDamageHundredthsShown = int.MinValue; private RectTransform? minimapRoot; private Canvas? minimapCanvas; private readonly Vector3[] minimapCorners = (Vector3[])(object)new Vector3[4]; private readonly string[] cooldownNames = new string[5]; private readonly string[] cooldownTimes = new string[5]; private readonly string?[] cooldownIdShown = new string[5]; private readonly int[] cooldownTenthsShown = new int[5]; private readonly float[] cooldownFractions = new float[5]; private int cooldownRows; internal stat