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 MeadMakeover v1.0.0
plugins/BeastheimMeadMakeover.dll
Decompiled a day 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.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Beastheim.MeadMakeover { public enum CustomizationMode { Creator, Service } public sealed class DraftRow { public BodySlot Slot; public string Label; public int Min; public int Max; public int Value; } public sealed class AppearanceDraft { private AppearanceState _current; public readonly CustomizationMode Mode; public readonly BodyExposure Exposure; public readonly bool EyesEnabled; public readonly bool TattooEnabled; public readonly bool ShapeEnabled; public readonly ServiceScope Scope; public readonly AppearanceState Original; public Action Changed; public AppearanceState Current => _current; public bool CanEditBody { get { if ((Scope & ServiceScope.Body) != ServiceScope.None) { return Exposure.AnyEnabled; } return false; } } public bool CanEditEyes { get { if ((Scope & ServiceScope.Eyes) != ServiceScope.None) { return EyesEnabled; } return false; } } public bool CanEditShape { get { if ((Scope & ServiceScope.Shape) != ServiceScope.None) { return ShapeEnabled; } return false; } } public bool CanEditTattoo { get { if ((Scope & ServiceScope.Tattoo) != ServiceScope.None && TattooEnabled) { return TattooCatalog.AvailableCount > 0; } return false; } } public bool IsDirty => !_current.Equals(Original); public string TattooLabel { get { TattooChoice tattoo = _current.Tattoo; if (!tattoo.IsNone) { return TattooCatalog.Patterns[tattoo.Pattern - 1].Name; } return "None"; } } public string InkLabel => TattooCatalog.Inks[_current.Tattoo.Ink].Name; public int BreastMin => ShapeLimits.BreastMin; public int BreastMax => ShapeLimits.BreastMax; public int ButtMin => ShapeLimits.ButtMin; public int ButtMax => ShapeLimits.ButtMax; public AppearanceDraft(AppearanceState original, BodyExposure exposure, bool eyesEnabled, CustomizationMode mode, ServiceScope scope, bool tattooEnabled = false, bool shapeEnabled = false) { Exposure = exposure ?? BodyExposure.Default(); EyesEnabled = eyesEnabled; TattooEnabled = tattooEnabled; ShapeEnabled = shapeEnabled; Mode = mode; Scope = ((mode == CustomizationMode.Creator) ? ServiceScope.All : scope); AppearanceState appearanceState = original ?? AppearanceState.Neutral; Original = new AppearanceState(appearanceState.Body.Clamped(Exposure), eyesEnabled ? appearanceState.Eyes : ((EyeColor?)null), (tattooEnabled && TattooCatalog.IsAvailable(appearanceState.Tattoo.Pattern)) ? appearanceState.Tattoo : TattooChoice.None, shapeEnabled ? appearanceState.Shape.Clamped() : ShapeChoice.None); _current = Original; } public IList<DraftRow> Rows() { List<DraftRow> list = new List<DraftRow>(); if (!CanEditBody) { return list; } SlotDef[] defs = BodyLimits.Defs; foreach (SlotDef slotDef in defs) { if (Exposure.IsEnabled(slotDef.Id)) { list.Add(new DraftRow { Slot = slotDef.Id, Label = slotDef.Label, Min = slotDef.MinPercent, Max = slotDef.MaxPercent, Value = _current.Body[slotDef.Id] }); } } return list; } public bool SetSlider(BodySlot slot, int percent) { if (!CanEditBody || !Exposure.IsEnabled(slot)) { return false; } int num = Math.Max(Exposure.EffectiveMin(slot), Math.Min(Exposure.EffectiveMax(slot), percent)); if (_current.Body[slot] == num) { return false; } return Apply(_current.WithBody(_current.Body.With(slot, num))); } public bool Nudge(BodySlot slot, int delta) { return SetSlider(slot, _current.Body[slot] + delta); } public bool ResetSlot(BodySlot slot) { return SetSlider(slot, 0); } public bool ResetBody() { if (!CanEditBody || _current.Body.IsNeutral) { return false; } return Apply(_current.WithBody(BodyValues.Neutral)); } public bool ApplyPreset(string name) { BodyPresets.Preset preset = BodyPresets.Find(name); if (preset == null || !CanEditBody) { return false; } BodyValues bodyValues = preset.Values.Clamped(Exposure); if (bodyValues.Equals(_current.Body)) { return false; } return Apply(_current.WithBody(bodyValues)); } public bool SetEyes(EyeColor? c) { if (!CanEditEyes) { return false; } if (_current.Eyes.Equals(c)) { return false; } return Apply(_current.WithEyes(c)); } public bool SetEyeHsv(float hue, float saturation, float value) { return SetEyes(EyeColor.FromHsv(hue, saturation, value)); } public bool ResetEyes() { return SetEyes(null); } public bool CycleTattoo(int dir) { if (!CanEditTattoo) { return false; } int num = TattooCatalog.Cycle(_current.Tattoo.Pattern, dir); if (num == _current.Tattoo.Pattern) { return false; } return Apply(_current.WithTattoo(new TattooChoice(num, _current.Tattoo.Ink))); } public bool CycleInk(int dir) { if (!CanEditTattoo) { return false; } int num = TattooCatalog.Inks.Length; int ink = ((_current.Tattoo.Ink + ((dir > 0) ? 1 : (-1))) % num + num) % num; return Apply(_current.WithTattoo(new TattooChoice(_current.Tattoo.Pattern, ink))); } public bool SetTattoo(TattooChoice t) { if (!CanEditTattoo || _current.Tattoo.Equals(t)) { return false; } return Apply(_current.WithTattoo(t)); } public bool SetBreast(int v) { if (!CanEditShape) { return false; } v = Math.Max(ShapeLimits.BreastMin, Math.Min(ShapeLimits.BreastMax, v)); if (_current.Shape.Breast == v) { return false; } return Apply(_current.WithShape(_current.Shape.WithBreast(v))); } public bool SetButt(int v) { if (!CanEditShape) { return false; } v = Math.Max(ShapeLimits.ButtMin, Math.Min(ShapeLimits.ButtMax, v)); if (_current.Shape.Butt == v) { return false; } return Apply(_current.WithShape(_current.Shape.WithButt(v))); } public bool ResetShape() { if (!CanEditShape || _current.Shape.IsNone) { return false; } return Apply(_current.WithShape(ShapeChoice.None)); } public string RangeSummary() { List<string> list = new List<string>(); foreach (DraftRow item in Rows()) { list.Add(item.Label + " " + item.Min + ".." + item.Max); } if (CanEditShape) { list.Add("Breast size " + BreastMin + ".." + BreastMax); list.Add("Butt size " + ButtMin + ".." + ButtMax); } return string.Join("; ", list.ToArray()); } public bool Revert() { if (!IsDirty) { return false; } return Apply(Original); } public AppearanceState Result() { BodyValues body = (CanEditBody ? _current.Body.Clamped(Exposure) : Original.Body); EyeColor? eyes = (CanEditEyes ? _current.Eyes : Original.Eyes); TattooChoice tattoo = (CanEditTattoo ? _current.Tattoo : Original.Tattoo); ShapeChoice shape = (CanEditShape ? _current.Shape.Clamped() : Original.Shape); return new AppearanceState(body, eyes, tattoo, shape); } private bool Apply(AppearanceState next) { _current = next; Changed?.Invoke(); return true; } } public sealed class AppearanceState : IEquatable<AppearanceState> { public readonly BodyValues Body; public readonly EyeColor? Eyes; public readonly TattooChoice Tattoo; public readonly ShapeChoice Shape; public static readonly AppearanceState Neutral = new AppearanceState(BodyValues.Neutral, null); public bool IsNeutral { get { if (Body.IsNeutral && !Eyes.HasValue && Tattoo.IsNone) { return Shape.IsNone; } return false; } } public AppearanceState(BodyValues body, EyeColor? eyes) : this(body, eyes, TattooChoice.None) { } public AppearanceState(BodyValues body, EyeColor? eyes, TattooChoice tattoo) : this(body, eyes, tattoo, ShapeChoice.None) { } public AppearanceState(BodyValues body, EyeColor? eyes, TattooChoice tattoo, ShapeChoice shape) { Body = body ?? BodyValues.Neutral; Eyes = eyes; Tattoo = tattoo; Shape = shape; } public AppearanceState WithBody(BodyValues b) { return new AppearanceState(b, Eyes, Tattoo, Shape); } public AppearanceState WithEyes(EyeColor? e) { return new AppearanceState(Body, e, Tattoo, Shape); } public AppearanceState WithTattoo(TattooChoice t) { return new AppearanceState(Body, Eyes, t, Shape); } public AppearanceState WithShape(ShapeChoice sh) { return new AppearanceState(Body, Eyes, Tattoo, sh); } public bool Equals(AppearanceState o) { if (o == null) { return false; } if (Body.Equals(o.Body) && Eyes.Equals(o.Eyes) && Tattoo.Equals(o.Tattoo)) { return Shape.Equals(o.Shape); } return false; } public override bool Equals(object obj) { return Equals(obj as AppearanceState); } public override int GetHashCode() { return ((Body.GetHashCode() * 31 + (Eyes.HasValue ? (Eyes.Value.GetHashCode() + 1) : 0)) * 31 + Tattoo.GetHashCode()) * 31 + Shape.GetHashCode(); } public override string ToString() { return "body=" + Body?.ToString() + " eyes=" + (Eyes.HasValue ? Eyes.Value.Hex() : "vanilla") + " tattoo=" + Tattoo.ToString() + " shape=" + Shape.ToString(); } } public static class AppearanceStore { public static AppearanceState Read(IDictionary<string, string> customData, BodyExposure exposure, bool eyesEnabled) { if (customData == null) { return AppearanceState.Neutral; } customData.TryGetValue("beastheim.body.v1", out var value); customData.TryGetValue("beastheim.eyes.v1", out var value2); return FromStrings(value, value2, exposure, eyesEnabled); } public static AppearanceState FromStrings(string body, string eyes, BodyExposure exposure, bool eyesEnabled) { return new AppearanceState(BodyCodec.ParseForRender(body, exposure), eyesEnabled ? EyeCodec.ParseForRender(eyes) : ((EyeColor?)null)); } public static AppearanceState Read(IDictionary<string, string> customData, BodyExposure exposure, bool eyesEnabled, bool tattooEnabled) { if (customData == null) { return AppearanceState.Neutral; } customData.TryGetValue("beastheim.body.v1", out var value); customData.TryGetValue("beastheim.eyes.v1", out var value2); customData.TryGetValue("beastheim.tattoo.v1", out var value3); return FromStrings(value, value2, value3, exposure, eyesEnabled, tattooEnabled); } public static AppearanceState FromStrings(string body, string eyes, string tattoo, BodyExposure exposure, bool eyesEnabled, bool tattooEnabled) { return new AppearanceState(BodyCodec.ParseForRender(body, exposure), eyesEnabled ? EyeCodec.ParseForRender(eyes) : ((EyeColor?)null), tattooEnabled ? TattooCodec.ParseForRender(tattoo) : TattooChoice.None); } public static string TattooString(AppearanceState s, bool tattooEnabled) { if (!tattooEnabled) { return ""; } return TattooCodec.Serialize(s.Tattoo) ?? ""; } public static void Write(IDictionary<string, string> customData, AppearanceState s, BodyExposure exposure, bool eyesEnabled, bool tattooEnabled) { Write(customData, s, exposure, eyesEnabled); if (tattooEnabled) { Put(customData, "beastheim.tattoo.v1", TattooString(s, tattooEnabled: true)); } } public static AppearanceState Read(IDictionary<string, string> customData, BodyExposure exposure, bool eyesEnabled, bool tattooEnabled, bool shapeEnabled) { if (customData == null) { return AppearanceState.Neutral; } customData.TryGetValue("beastheim.body.v1", out var value); customData.TryGetValue("beastheim.eyes.v1", out var value2); customData.TryGetValue("beastheim.tattoo.v1", out var value3); customData.TryGetValue("beastheim.shape.v1", out var value4); return FromStrings(value, value2, value3, value4, exposure, eyesEnabled, tattooEnabled, shapeEnabled); } public static AppearanceState FromStrings(string body, string eyes, string tattoo, string shape, BodyExposure exposure, bool eyesEnabled, bool tattooEnabled, bool shapeEnabled) { return new AppearanceState(BodyCodec.ParseForRender(body, exposure), eyesEnabled ? EyeCodec.ParseForRender(eyes) : ((EyeColor?)null), tattooEnabled ? TattooCodec.ParseForRender(tattoo) : TattooChoice.None, shapeEnabled ? ShapeCodec.ParseForRender(shape) : ShapeChoice.None); } public static string ShapeString(AppearanceState s, bool shapeEnabled) { if (!shapeEnabled) { return ""; } return ShapeCodec.Serialize(s.Shape.Clamped()) ?? ""; } public static void Write(IDictionary<string, string> customData, AppearanceState s, BodyExposure exposure, bool eyesEnabled, bool tattooEnabled, bool shapeEnabled) { Write(customData, s, exposure, eyesEnabled, tattooEnabled); if (shapeEnabled) { Put(customData, "beastheim.shape.v1", ShapeString(s, shapeEnabled: true)); } } public static string BodyString(AppearanceState s, BodyExposure exposure) { return BodyCodec.Serialize(s.Body, exposure) ?? ""; } public static string EyesString(AppearanceState s, bool eyesEnabled) { if (!eyesEnabled) { return ""; } return EyeCodec.Serialize(s.Eyes) ?? ""; } public static void Write(IDictionary<string, string> customData, AppearanceState s, BodyExposure exposure, bool eyesEnabled) { if (customData == null) { throw new ArgumentNullException("customData"); } Put(customData, "beastheim.body.v1", BodyString(s, exposure)); Put(customData, "beastheim.eyes.v1", EyesString(s, eyesEnabled)); } private static void Put(IDictionary<string, string> d, string key, string value) { if (string.IsNullOrEmpty(value)) { d.Remove(key); } else { d[key] = value; } } } public sealed class AppearanceFinding { public string Code; public string Detail; public AppearanceFinding(string code, string detail) { Code = code; Detail = detail; } public override string ToString() { return Code + ": " + Detail; } } public static class AppearanceValidator { public static List<AppearanceFinding> Check(IEnumerable<KeyValuePair<string, string>> customData) { List<AppearanceFinding> list = new List<AppearanceFinding>(); if (customData == null) { return list; } foreach (KeyValuePair<string, string> customDatum in customData) { if (customDatum.Key == "beastheim.body.v1") { BodyParseResult bodyParseResult = BodyCodec.ParseStrict(customDatum.Value); if (bodyParseResult.Status == CodecStatus.Malformed) { list.Add(new AppearanceFinding("A.BodyMalformed", Clip(customDatum.Value))); } else if (bodyParseResult.Status == CodecStatus.OutOfRange) { list.Add(new AppearanceFinding("A.BodyOutOfRange", Clip(customDatum.Value))); } } else if (customDatum.Key == "beastheim.eyes.v1") { if (EyeCodec.TryParse(customDatum.Value, out var _) == CodecStatus.Malformed) { list.Add(new AppearanceFinding("A.EyesMalformed", Clip(customDatum.Value))); } } else if (customDatum.Key == "beastheim.tattoo.v1") { TattooChoice c; CodecStatus codecStatus = TattooCodec.TryParse(customDatum.Value, out c); if (codecStatus == CodecStatus.Malformed || codecStatus == CodecStatus.OutOfRange) { list.Add(new AppearanceFinding("A.TattooInvalid", Clip(customDatum.Value))); } } else if (customDatum.Key == "beastheim.shape.v1") { ShapeChoice c2; CodecStatus codecStatus2 = ShapeCodec.TryParse(customDatum.Value, out c2); if (codecStatus2 == CodecStatus.Malformed || codecStatus2 == CodecStatus.OutOfRange) { list.Add(new AppearanceFinding("A.ShapeInvalid", Clip(customDatum.Value))); } } } return list; } private static string Clip(string s) { if (s != null) { if (s.Length > 80) { return s.Substring(0, 80) + "..."; } return s; } return ""; } } public struct Vec3f { public float X; public float Y; public float Z; public static Vec3f One => new Vec3f(1f, 1f, 1f); public Vec3f(float x, float y, float z) { X = x; Y = y; Z = z; } public static Vec3f operator *(Vec3f a, Vec3f b) { return new Vec3f(a.X * b.X, a.Y * b.Y, a.Z * b.Z); } } public sealed class BodyOptions { public bool CompensateRotated = true; public bool KeepFeetPlanted = true; } public sealed class BodyPlan { public float VisualScaleFactor = 1f; public readonly Dictionary<string, Vec3f> BoneScale = new Dictionary<string, Vec3f>(); public float ShoulderOffsetMeters; } public static class BodyCompose { public const float ShoulderHalfSpanMeters = 0.217f; public static readonly string[] Bones = new string[17] { "Hips", "Spine", "Spine1", "Spine2", "Neck", "LeftShoulder", "RightShoulder", "LeftUpLeg", "RightUpLeg", "LeftLeg", "RightLeg", "LeftFoot", "RightFoot", "LeftArm", "RightArm", "LeftForeArm", "RightForeArm" }; public static readonly Dictionary<string, string> Parent = new Dictionary<string, string> { { "Spine", "Hips" }, { "Spine1", "Spine" }, { "Spine2", "Spine1" }, { "Neck", "Spine2" }, { "LeftShoulder", "Spine2" }, { "RightShoulder", "Spine2" }, { "LeftArm", "LeftShoulder" }, { "RightArm", "RightShoulder" }, { "LeftForeArm", "LeftArm" }, { "RightForeArm", "RightArm" }, { "LeftUpLeg", "Hips" }, { "RightUpLeg", "Hips" }, { "LeftLeg", "LeftUpLeg" }, { "RightLeg", "RightUpLeg" }, { "LeftFoot", "LeftLeg" }, { "RightFoot", "RightLeg" } }; public static BodyPlan Compute(BodyValues v, BodyOptions o) { if (o == null) { o = new BodyOptions(); } BodyPlan bodyPlan = new BodyPlan(); bodyPlan.VisualScaleFactor = F(BodySlot.Height); float num = F(BodySlot.ChestWidth); float num2 = F(BodySlot.ChestDepth); float num3 = F(BodySlot.WaistWidth); float num4 = F(BodySlot.WaistDepth); float num5 = F(BodySlot.HipsWidth); float num6 = F(BodySlot.HipsDepth); float num7 = F(BodySlot.ThighThickness); float num8 = F(BodySlot.UpperArm); float num9 = F(BodySlot.CalfThickness); float num10 = F(BodySlot.LegLength); bodyPlan.BoneScale["Hips"] = new Vec3f(num5, 1f, num6); bodyPlan.BoneScale["Spine"] = new Vec3f(num3 / num5, 1f, num4 / num6); bodyPlan.BoneScale["Spine1"] = new Vec3f(num / num3, 1f, num2 / num4); bodyPlan.BoneScale["Spine2"] = new Vec3f(1f / num, 1f, 1f / num2); bodyPlan.BoneScale["Neck"] = Vec3f.One; bodyPlan.BoneScale["LeftShoulder"] = Vec3f.One; bodyPlan.BoneScale["RightShoulder"] = Vec3f.One; Vec3f value = new Vec3f(num7 / num5, num10, num7 / num6); Vec3f value2 = new Vec3f(num9 / num7, 1f, num9 / num7); Vec3f value3 = (o.CompensateRotated ? new Vec3f(1f / num9, 1f / num10, 1f / num9) : Vec3f.One); string[] array = new string[2] { "Left", "Right" }; foreach (string text in array) { bodyPlan.BoneScale[text + "UpLeg"] = value; bodyPlan.BoneScale[text + "Leg"] = value2; bodyPlan.BoneScale[text + "Foot"] = value3; bodyPlan.BoneScale[text + "Arm"] = new Vec3f(num8, num8, num8); bodyPlan.BoneScale[text + "ForeArm"] = new Vec3f(1f / num8, 1f / num8, 1f / num8); } bodyPlan.ShoulderOffsetMeters = v.Fraction(BodySlot.ShoulderWidth) * 0.217f * bodyPlan.VisualScaleFactor; return bodyPlan; float F(BodySlot k) { return 1f + v.Fraction(k); } } public static Vec3f NetScale(BodyPlan p, string bone) { Vec3f one = Vec3f.One; for (string text = bone; text != null; text = (Parent.ContainsKey(text) ? Parent[text] : null)) { if (p.BoneScale.TryGetValue(text, out var value)) { one *= value; } } return one; } } public static class AppearanceKeys { public const string BodyCustomDataKey = "beastheim.body.v1"; public const string EyesCustomDataKey = "beastheim.eyes.v1"; public const string TattooCustomDataKey = "beastheim.tattoo.v1"; public const string TattooZdoField = "beastheim.tattoo"; public const string ShapeCustomDataKey = "beastheim.shape.v1"; public const string ShapeZdoField = "beastheim.shape"; public const string BodyZdoField = "beastheim.body"; public const string EyesZdoField = "beastheim.eyes"; } public enum BodySlot { Height, ChestWidth, ChestDepth, WaistWidth, WaistDepth, HipsWidth, HipsDepth, ThighThickness, ShoulderWidth, UpperArm, CalfThickness, LegLength } public sealed class SlotDef { public BodySlot Id; public string Label; public int ProdMin; public int ProdMax; public int DiagMin; public int DiagMax; public bool DefaultExposed; public string Note; public int MinPercent => RangePolicy.Effective.Min((int)Id); public int MaxPercent => RangePolicy.Effective.Max((int)Id); public int PolicyMin => RangePolicy.Active.Min((int)Id); public int PolicyMax => RangePolicy.Active.Max((int)Id); } public static class BodyLimits { public const int Count = 12; public static readonly SlotDef[] Defs = new SlotDef[12] { new SlotDef { Id = BodySlot.Height, Label = "Height", ProdMin = -15, ProdMax = 20, DiagMin = -30, DiagMax = 40, DefaultExposed = true, Note = "Visual root scale; feet stay on the ground" }, new SlotDef { Id = BodySlot.ChestWidth, Label = "Chest width", ProdMin = -40, ProdMax = 40, DiagMin = -60, DiagMax = 60, DefaultExposed = true, Note = "Spine1 X, Spine2 compensates" }, new SlotDef { Id = BodySlot.ChestDepth, Label = "Chest depth", ProdMin = -40, ProdMax = 40, DiagMin = -60, DiagMax = 60, DefaultExposed = true, Note = "Spine1 Z, Spine2 compensates" }, new SlotDef { Id = BodySlot.WaistWidth, Label = "Waist width", ProdMin = -40, ProdMax = 40, DiagMin = -60, DiagMax = 60, DefaultExposed = true, Note = "Spine X, Spine1 compensates" }, new SlotDef { Id = BodySlot.WaistDepth, Label = "Waist depth", ProdMin = -40, ProdMax = 40, DiagMin = -60, DiagMax = 60, DefaultExposed = true, Note = "Spine Z, Spine1 compensates" }, new SlotDef { Id = BodySlot.HipsWidth, Label = "Hips width", ProdMin = -10, ProdMax = 10, DiagMin = -40, DiagMax = 40, DefaultExposed = true, Note = "Hips X, Spine and upper legs compensate" }, new SlotDef { Id = BodySlot.HipsDepth, Label = "Hips depth", ProdMin = -10, ProdMax = 10, DiagMin = -40, DiagMax = 40, DefaultExposed = true, Note = "Hips Z, Spine and upper legs compensate" }, new SlotDef { Id = BodySlot.ThighThickness, Label = "Thigh thickness", ProdMin = -20, ProdMax = 20, DiagMin = -40, DiagMax = 40, DefaultExposed = true, Note = "UpLeg X/Z, Leg compensates" }, new SlotDef { Id = BodySlot.ShoulderWidth, Label = "Shoulder width", ProdMin = -20, ProdMax = 20, DiagMin = -40, DiagMax = 40, DefaultExposed = true, Note = "Shoulder bones moved sideways (no scale)" }, new SlotDef { Id = BodySlot.UpperArm, Label = "Upper-arm thickness", ProdMin = -20, ProdMax = 20, DiagMin = -40, DiagMax = 40, DefaultExposed = true, Note = "Arm uniform scale, ForeArm compensates" }, new SlotDef { Id = BodySlot.CalfThickness, Label = "Calf thickness", ProdMin = -30, ProdMax = 30, DiagMin = -50, DiagMax = 50, DefaultExposed = true, Note = "Leg X/Z, Foot compensates (approximate)" }, new SlotDef { Id = BodySlot.LegLength, Label = "Leg length", ProdMin = -15, ProdMax = 20, DiagMin = -30, DiagMax = 40, DefaultExposed = true, Note = "UpLeg Y, hips lifted to keep feet planted" } }; public static bool Diagnostic { get { return RangePolicy.LabMode; } set { RangePolicy.LabMode = value; } } public static int MinPercent(BodySlot s) { return Defs[(int)s].MinPercent; } public static int MaxPercent(BodySlot s) { return Defs[(int)s].MaxPercent; } } public sealed class BodyExposure { private readonly bool[] _on = new bool[12]; public bool AnyEnabled { get { bool[] array = _on; for (int i = 0; i < array.Length; i++) { if (array[i]) { return true; } } return false; } } public BodyExposure() { for (int i = 0; i < _on.Length; i++) { _on[i] = BodyLimits.Defs[i].DefaultExposed; } } public static BodyExposure Default() { return new BodyExposure(); } public static BodyExposure All() { BodyExposure bodyExposure = new BodyExposure(); for (int i = 0; i < bodyExposure._on.Length; i++) { bodyExposure._on[i] = true; } return bodyExposure; } public bool IsEnabled(BodySlot s) { return _on[(int)s]; } public BodyExposure With(BodySlot s, bool enabled) { BodyExposure bodyExposure = new BodyExposure(); Array.Copy(_on, bodyExposure._on, _on.Length); bodyExposure._on[(int)s] = enabled; return bodyExposure; } public int EffectiveMin(BodySlot s) { if (!_on[(int)s]) { return 0; } return BodyLimits.MinPercent(s); } public int EffectiveMax(BodySlot s) { if (!_on[(int)s]) { return 0; } return BodyLimits.MaxPercent(s); } public override string ToString() { List<string> list = new List<string>(); for (int i = 0; i < _on.Length; i++) { list.Add(BodyLimits.Defs[i].Id.ToString() + "=" + (_on[i] ? "on" : "off")); } return string.Join(" ", list.ToArray()); } } public sealed class BodyValues : IEquatable<BodyValues> { private readonly int[] _p; public static readonly BodyValues Neutral = new BodyValues(new int[12]); public int this[BodySlot s] => _p[(int)s]; public bool IsNeutral { get { int[] p = _p; for (int i = 0; i < p.Length; i++) { if (p[i] != 0) { return false; } } return true; } } public BodyValues(int[] percents) { if (percents == null || percents.Length != 12) { throw new ArgumentException("exactly " + 12 + " values are required"); } _p = (int[])percents.Clone(); } public int Get(int index) { return _p[index]; } public float Fraction(BodySlot s) { return (float)_p[(int)s] / 100f; } public BodyValues With(BodySlot s, int percent) { int[] obj = (int[])_p.Clone(); obj[(int)s] = percent; return new BodyValues(obj); } public BodyValues Clamped(BodyExposure exposure) { int[] array = (int[])_p.Clone(); for (int i = 0; i < array.Length; i++) { int val = exposure.EffectiveMin((BodySlot)i); int val2 = exposure.EffectiveMax((BodySlot)i); array[i] = Math.Max(val, Math.Min(val2, array[i])); } return new BodyValues(array); } public BodyValues ClampedHard() { int[] array = (int[])_p.Clone(); for (int i = 0; i < array.Length; i++) { SlotDef slotDef = BodyLimits.Defs[i]; array[i] = Math.Max(slotDef.MinPercent, Math.Min(slotDef.MaxPercent, array[i])); } return new BodyValues(array); } public bool IsWithinHardLimits() { for (int i = 0; i < _p.Length; i++) { if (_p[i] < BodyLimits.Defs[i].MinPercent || _p[i] > BodyLimits.Defs[i].MaxPercent) { return false; } } return true; } public int[] ToArray() { return (int[])_p.Clone(); } public bool Equals(BodyValues other) { if (other == null) { return false; } for (int i = 0; i < _p.Length; i++) { if (_p[i] != other._p[i]) { return false; } } return true; } public override bool Equals(object obj) { return Equals(obj as BodyValues); } public override int GetHashCode() { int num = 17; int[] p = _p; foreach (int num2 in p) { num = num * 31 + num2; } return num; } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < _p.Length; i++) { if (_p[i] != 0) { if (stringBuilder.Length > 0) { stringBuilder.Append(' '); } stringBuilder.Append(BodyLimits.Defs[i].Id).Append((_p[i] > 0) ? "+" : "").Append(_p[i].ToString(CultureInfo.InvariantCulture)) .Append('%'); } } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return "neutral"; } } public static class BodyPresets { public sealed class Preset { public string Name; public BodyValues Values; } public static readonly Preset[] All = new Preset[8] { new Preset { Name = "Neutral", Values = BodyValues.Neutral }, new Preset { Name = "Hourglass", Values = V(0, 10, 5, -10, -10, 10, 10, 5) }, new Preset { Name = "Broad", Values = V(3, 10, 10, 5, 5, 0, 0, 0, 5, 8) }, new Preset { Name = "Slim", Values = V(0, -10, -10, -10, -10, -10, -10, -10, -5, -8, -5) }, new Preset { Name = "Tall", Values = V(5) }, new Preset { Name = "Short", Values = V(-5) }, new Preset { Name = "Heavy lower", Values = V(0, 0, 0, 5, 5, 10, 10, 10, 0, 0, 5) }, new Preset { Name = "Heavy build", Values = V(0, 8, 8, 10, 10, 6, 6, 6, 0, 4) } }; private static BodyValues V(params int[] v) { int[] array = new int[12]; Array.Copy(v, array, Math.Min(v.Length, array.Length)); return new BodyValues(array); } public static Preset Find(string name) { Preset[] all = All; foreach (Preset preset in all) { if (string.Equals(preset.Name, name, StringComparison.OrdinalIgnoreCase)) { return preset; } } return null; } } public enum CodecStatus { Absent, Ok, UnknownVersion, Malformed, OutOfRange } public struct BodyParseResult { public CodecStatus Status; public BodyValues Values; } public static class BodyCodec { public const string Prefix = "v1:"; public const int MaxLength = 64; public static string Serialize(BodyValues values, BodyExposure exposure) { BodyValues bodyValues = (values ?? BodyValues.Neutral).Clamped(exposure); if (!bodyValues.IsNeutral) { return SerializeAlways(bodyValues); } return null; } public static string SerializeAlways(BodyValues v) { StringBuilder stringBuilder = new StringBuilder("v1:"); for (int i = 0; i < 12; i++) { if (i > 0) { stringBuilder.Append(','); } stringBuilder.Append(v.Get(i).ToString(CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } public static BodyParseResult ParseStrict(string s) { BodyParseResult result = new BodyParseResult { Status = CodecStatus.Ok, Values = BodyValues.Neutral }; if (string.IsNullOrEmpty(s)) { result.Status = CodecStatus.Absent; return result; } if (s.Length > 64) { result.Status = CodecStatus.Malformed; return result; } if (!s.StartsWith("v1:", StringComparison.Ordinal)) { result.Status = (LooksLikeOtherVersion(s) ? CodecStatus.UnknownVersion : CodecStatus.Malformed); return result; } string[] array = s.Substring("v1:".Length).Split(new char[1] { ',' }); if (array.Length != 12) { result.Status = CodecStatus.Malformed; return result; } int[] array2 = new int[12]; bool flag = false; for (int i = 0; i < array.Length; i++) { if (!TryParseCanonicalInt(array[i], out var value)) { result.Status = CodecStatus.Malformed; return result; } array2[i] = value; if (value < BodyLimits.Defs[i].PolicyMin || value > BodyLimits.Defs[i].PolicyMax) { flag = true; } } result.Values = new BodyValues(array2); if (flag) { result.Status = CodecStatus.OutOfRange; } return result; } public static BodyValues ParseForRender(string s, BodyExposure exposure) { BodyParseResult bodyParseResult = ParseStrict(s); return bodyParseResult.Status switch { CodecStatus.Ok => bodyParseResult.Values.Clamped(exposure), CodecStatus.OutOfRange => bodyParseResult.Values.Clamped(exposure), _ => BodyValues.Neutral, }; } public static string Canonical(string s, BodyExposure exposure) { return Serialize(ParseForRender(s, exposure), exposure) ?? ""; } private static bool LooksLikeOtherVersion(string s) { if (s.Length < 3 || s[0] != 'v') { return false; } int i; for (i = 1; i < s.Length && s[i] >= '0' && s[i] <= '9'; i++) { } if (i > 1 && i < s.Length) { return s[i] == ':'; } return false; } internal static bool TryParseCanonicalInt(string f, out int value) { value = 0; if (string.IsNullOrEmpty(f) || f.Length > 3) { return false; } bool flag = f[0] == '-'; string text = (flag ? f.Substring(1) : f); if (text.Length == 0 || text.Length > 2) { return false; } string text2 = text; foreach (char c in text2) { if (c < '0' || c > '9') { return false; } } if (text.Length == 2 && text[0] == '0') { return false; } if (flag && text == "0") { return false; } value = int.Parse(text, CultureInfo.InvariantCulture) * ((!flag) ? 1 : (-1)); return true; } public static string FreshnessPattern() { StringBuilder stringBuilder = new StringBuilder("\\A").Append("v1:"); for (int i = 0; i < 12; i++) { if (i > 0) { stringBuilder.Append(','); } stringBuilder.Append(SignedRangePattern(BodyLimits.Defs[i].PolicyMin, BodyLimits.Defs[i].PolicyMax)); } return stringBuilder.Append("\\z").ToString(); } public static string SignedRangePattern(int max) { return SignedRangePattern(-max, max); } public static string SignedRangePattern(int min, int max) { if (min > 0 || max < 0) { throw new ArgumentException("min must be <= 0 and max >= 0"); } if (-min == max) { return "(?:0|-?(?:" + PositiveAlternatives(max) + "))"; } List<string> list = new List<string> { "0" }; if (min < 0) { list.Add("-(?:" + PositiveAlternatives(-min) + ")"); } if (max > 0) { list.Add("(?:" + PositiveAlternatives(max) + ")"); } return "(?:" + string.Join("|", list.ToArray()) + ")"; } private static string PositiveAlternatives(int max) { if (max < 1) { return "(?!)"; } List<string> list = new List<string>(); if (max < 10) { list.Add("[1-" + max.ToString(CultureInfo.InvariantCulture) + "]"); } else { list.Add("[1-9]"); int num = max / 10; for (int i = 1; i < num; i++) { list.Add(i.ToString(CultureInfo.InvariantCulture) + "[0-9]"); } list.Add(num.ToString(CultureInfo.InvariantCulture) + "[0-" + (max % 10).ToString(CultureInfo.InvariantCulture) + "]"); } return string.Join("|", list.ToArray()); } } public struct EyeColor : IEquatable<EyeColor> { public readonly byte R; public readonly byte G; public readonly byte B; public EyeColor(byte r, byte g, byte b) { R = r; G = g; B = b; } public bool Equals(EyeColor o) { if (R == o.R && G == o.G) { return B == o.B; } return false; } public override bool Equals(object obj) { if (obj is EyeColor) { return Equals((EyeColor)obj); } return false; } public override int GetHashCode() { return (R << 16) | (G << 8) | B; } public override string ToString() { return Hex(); } public string Hex() { byte r = R; string text = r.ToString("X2", CultureInfo.InvariantCulture); r = G; string text2 = r.ToString("X2", CultureInfo.InvariantCulture); r = B; return text + text2 + r.ToString("X2", CultureInfo.InvariantCulture); } public static EyeColor FromHsv(float h, float s, float v) { h = (h % 360f + 360f) % 360f; s = Clamp01(s); v = Clamp01(v); float num = v * s; float num2 = num * (1f - Math.Abs(h / 60f % 2f - 1f)); float num3 = v - num; float num4; float num5; float num6; if (h < 60f) { num4 = num; num5 = num2; num6 = 0f; } else if (h < 120f) { num4 = num2; num5 = num; num6 = 0f; } else if (h < 180f) { num4 = 0f; num5 = num; num6 = num2; } else if (h < 240f) { num4 = 0f; num5 = num2; num6 = num; } else if (h < 300f) { num4 = num2; num5 = 0f; num6 = num; } else { num4 = num; num5 = 0f; num6 = num2; } return new EyeColor(To8(num4 + num3), To8(num5 + num3), To8(num6 + num3)); } public void ToHsv(out float h, out float s, out float v) { float num = (float)(int)R / 255f; float num2 = (float)(int)G / 255f; float num3 = (float)(int)B / 255f; float num4 = Math.Max(num, Math.Max(num2, num3)); float num5 = Math.Min(num, Math.Min(num2, num3)); float num6 = num4 - num5; v = num4; s = ((num4 <= 0f) ? 0f : (num6 / num4)); if (num6 <= 0f) { h = 0f; } else if (num4 == num) { h = 60f * ((num2 - num3) / num6 % 6f); } else if (num4 == num2) { h = 60f * ((num3 - num) / num6 + 2f); } else { h = 60f * ((num - num2) / num6 + 4f); } if (h < 0f) { h += 360f; } } private static float Clamp01(float x) { if (!(x < 0f)) { if (!(x > 1f)) { return x; } return 1f; } return 0f; } private static byte To8(float x) { return (byte)Math.Max(0, Math.Min(255, (int)Math.Round(x * 255f))); } } public static class EyeCodec { public const string Prefix = "v1:"; public const int MaxLength = 9; public static string Serialize(EyeColor? c) { if (!c.HasValue) { return null; } return "v1:" + c.Value.Hex(); } public static CodecStatus TryParse(string s, out EyeColor color) { color = default(EyeColor); if (string.IsNullOrEmpty(s)) { return CodecStatus.Absent; } if (s.Length != 9) { if (!LooksLikeOtherVersion(s)) { return CodecStatus.Malformed; } return CodecStatus.UnknownVersion; } if (!s.StartsWith("v1:", StringComparison.Ordinal)) { if (!LooksLikeOtherVersion(s)) { return CodecStatus.Malformed; } return CodecStatus.UnknownVersion; } if (!Hex2(s, 3, out var v) || !Hex2(s, 5, out var v2) || !Hex2(s, 7, out var v3)) { return CodecStatus.Malformed; } color = new EyeColor(v, v2, v3); return CodecStatus.Ok; } public static EyeColor? ParseForRender(string s) { if (TryParse(s, out var color) != CodecStatus.Ok) { return null; } return color; } public static string Canonical(string s) { return Serialize(ParseForRender(s)) ?? ""; } public static string FreshnessPattern() { return "\\Av1:[0-9A-F]{6}\\z"; } private static bool LooksLikeOtherVersion(string s) { if (s.Length < 3 || s[0] != 'v') { return false; } int i; for (i = 1; i < s.Length && s[i] >= '0' && s[i] <= '9'; i++) { } if (i > 1 && i < s.Length) { return s[i] == ':'; } return false; } private static bool Hex2(string s, int at, out byte v) { v = 0; int num = Hex(s[at]); int num2 = Hex(s[at + 1]); if (num < 0 || num2 < 0) { return false; } v = (byte)(num * 16 + num2); return true; } private static int Hex(char c) { if (c >= '0' && c <= '9') { return c - 48; } if (c >= 'A' && c <= 'F') { return c - 65 + 10; } return -1; } } public static class EyePalette { public sealed class Swatch { public string Name; public EyeColor Color; } public static readonly Swatch[] Swatches = new Swatch[12] { new Swatch { Name = "Hazel", Color = new EyeColor(142, 107, 58) }, new Swatch { Name = "Brown", Color = new EyeColor(91, 58, 34) }, new Swatch { Name = "Dark", Color = new EyeColor(42, 28, 20) }, new Swatch { Name = "Amber", Color = new EyeColor(194, 132, 30) }, new Swatch { Name = "Green", Color = new EyeColor(79, 138, 75) }, new Swatch { Name = "Sea green", Color = new EyeColor(63, 143, 134) }, new Swatch { Name = "Blue", Color = new EyeColor(63, 111, 192) }, new Swatch { Name = "Ice", Color = new EyeColor(159, 200, 232) }, new Swatch { Name = "Grey", Color = new EyeColor(128, 136, 144) }, new Swatch { Name = "Violet", Color = new EyeColor(128, 79, 184) }, new Swatch { Name = "Ember", Color = new EyeColor(200, 75, 30) }, new Swatch { Name = "Gold", Color = new EyeColor(224, 184, 48) } }; } public sealed class CreatorLifecycle { public struct Frame { public bool ScreenActive; public bool PanelOpen; public int PreviewId; public bool OpenRequested; } public struct Actions { public bool RebuildEntry; public bool EnsureEntry; public bool HideEntry; public bool OpenPanel; public bool ClosePanel; public bool ReapplyToPreview; public bool Waiting; public bool Any { get { if (!RebuildEntry && !EnsureEntry && !HideEntry && !OpenPanel && !ClosePanel) { return ReapplyToPreview; } return true; } } } private bool _wasActive; private bool _pending; private int _appliedPreview; public bool Pending => _pending; public static bool CreatorOwnsOpenPanel(bool anyPanelOpen, bool creatorOpened) { return anyPanelOpen && creatorOpened; } public void NotifyEntered() { _wasActive = false; _pending = false; _appliedPreview = 0; } public void NotifyLeft() { _pending = false; _appliedPreview = 0; } public Actions Step(Frame f) { Actions result = default(Actions); if (!f.ScreenActive) { if (_wasActive || f.PanelOpen) { result.HideEntry = true; } if (f.PanelOpen) { result.ClosePanel = true; } _wasActive = false; _pending = false; _appliedPreview = 0; return result; } if (!_wasActive) { result.RebuildEntry = true; if (f.PanelOpen) { result.ClosePanel = true; } _pending = false; _appliedPreview = 0; _wasActive = true; } else if (!f.PanelOpen) { result.EnsureEntry = true; } else { result.HideEntry = true; } if (f.OpenRequested && !f.PanelOpen) { _pending = true; } if (_pending && !f.PanelOpen && !result.ClosePanel) { if (f.PreviewId != 0) { result.OpenPanel = true; _pending = false; _appliedPreview = f.PreviewId; } else { result.Waiting = true; } } else if (f.PanelOpen && !result.ClosePanel) { if (f.PreviewId != 0 && _appliedPreview != 0 && f.PreviewId != _appliedPreview) { result.ReapplyToPreview = true; _appliedPreview = f.PreviewId; } else if (f.PreviewId != 0 && _appliedPreview == 0) { _appliedPreview = f.PreviewId; } } return result; } } public sealed class EyePaintSpec { public float LeftX = 91.5f; public float LeftY = 42.5f; public float RightX = 102.4f; public float RightY = 42.5f; public float OffsetX; public float OffsetY; public float ScleraRx = 3.6f; public float ScleraRy = 1.8f; public float IrisR = 1.5f; public float PupilR = 0.55f; public float ScleraOpacity = 0.85f; public EyeColor Sclera = new EyeColor(230, 224, 212); public static EyePaintSpec Default() { return new EyePaintSpec(); } } public static class EyePainter { public const int PatchX0 = 117; public const int PatchX1 = 127; public const int PatchY0 = 19; public const int PatchY1 = 25; private const int MinBlueness = 25; private const int FullBlueness = 70; private const int Sub = 4; private const float MinSkin = 0.05f; public static void RecolorIris(byte[] rgba, int width, int height, EyeColor eye, float skinR, float skinG, float skinB, bool originBottomLeft) { if (rgba == null) { throw new ArgumentNullException("rgba"); } if (rgba.Length < width * height * 4) { throw new ArgumentException("buffer too small"); } float num = Math.Max(0.05f, skinR); float num2 = Math.Max(0.05f, skinG); float num3 = Math.Max(0.05f, skinB); float num4 = Math.Min(1f, (float)(int)eye.R / 255f / num); float num5 = Math.Min(1f, (float)(int)eye.G / 255f / num2); float num6 = Math.Min(1f, (float)(int)eye.B / 255f / num3); for (int i = Math.Max(0, 19); i <= Math.Min(height - 1, 25); i++) { int num7 = (originBottomLeft ? (height - 1 - i) : i); for (int j = Math.Max(0, 117); j <= Math.Min(width - 1, 127); j++) { int num8 = (num7 * width + j) * 4; int num9 = rgba[num8]; int num10 = rgba[num8 + 1]; int num11 = rgba[num8 + 2]; int num12 = num11 - Math.Max(num9, num10); if (num12 > 25) { float num13 = Math.Min(1f, (float)(num12 - 25) / 45f); rgba[num8] = To8((float)num9 / 255f + (num4 - (float)num9 / 255f) * num13); rgba[num8 + 1] = To8((float)num10 / 255f + (num5 - (float)num10 / 255f) * num13); rgba[num8 + 2] = To8((float)num11 / 255f + (num6 - (float)num11 / 255f) * num13); } } } } public static void Paint(byte[] rgba, int width, int height, EyeColor eye, float skinR, float skinG, float skinB, EyePaintSpec spec, bool originBottomLeft) { if (rgba == null) { throw new ArgumentNullException("rgba"); } if (rgba.Length < width * height * 4) { throw new ArgumentException("buffer too small"); } if (spec == null) { spec = EyePaintSpec.Default(); } float[] skin = new float[3] { Math.Max(0.05f, skinR), Math.Max(0.05f, skinG), Math.Max(0.05f, skinB) }; float[] iris = Comp((float)(int)eye.R / 255f, (float)(int)eye.G / 255f, (float)(int)eye.B / 255f, skin); float[] pupil = Comp((float)(int)eye.R / 255f * 0.16f, (float)(int)eye.G / 255f * 0.16f, (float)(int)eye.B / 255f * 0.16f, skin); float[] scl = Comp((float)(int)spec.Sclera.R / 255f, (float)(int)spec.Sclera.G / 255f, (float)(int)spec.Sclera.B / 255f, skin); PaintOne(rgba, width, height, spec.LeftX + spec.OffsetX, spec.LeftY + spec.OffsetY, iris, pupil, scl, spec, originBottomLeft); PaintOne(rgba, width, height, spec.RightX + spec.OffsetX, spec.RightY + spec.OffsetY, iris, pupil, scl, spec, originBottomLeft); } public static void Bounds(float cx, float cy, EyePaintSpec spec, out int x0, out int y0, out int x1, out int y1) { float num = Math.Max(spec.ScleraRx, spec.IrisR) + 1f; float num2 = Math.Max(spec.ScleraRy, spec.IrisR) + 1f; x0 = (int)Math.Floor(cx - num); x1 = (int)Math.Ceiling(cx + num); y0 = (int)Math.Floor(cy - num2); y1 = (int)Math.Ceiling(cy + num2); } private static float[] Comp(float r, float g, float b, float[] skin) { return new float[3] { Math.Min(1f, r / skin[0]), Math.Min(1f, g / skin[1]), Math.Min(1f, b / skin[2]) }; } private static void PaintOne(byte[] buf, int w, int h, float cx, float cy, float[] iris, float[] pupil, float[] scl, EyePaintSpec s, bool bottomLeft) { Bounds(cx, cy, s, out var x, out var y, out var x2, out var y2); for (int i = Math.Max(0, y); i <= Math.Min(h - 1, y2); i++) { int num = (bottomLeft ? (h - 1 - i) : i); for (int j = Math.Max(0, x); j <= Math.Min(w - 1, x2); j++) { int num2 = (num * w + j) * 4; float num3 = (float)(int)buf[num2] / 255f; float num4 = (float)(int)buf[num2 + 1] / 255f; float num5 = (float)(int)buf[num2 + 2] / 255f; float num6 = 0f; float num7 = 0f; float num8 = 0f; for (int k = 0; k < 4; k++) { for (int l = 0; l < 4; l++) { float num9 = (float)j + ((float)l + 0.5f) / 4f - cx; float num10 = (float)i + ((float)k + 0.5f) / 4f - cy; float num11 = num3; float num12 = num4; float num13 = num5; if (num9 * num9 / (s.ScleraRx * s.ScleraRx) + num10 * num10 / (s.ScleraRy * s.ScleraRy) <= 1f) { float scleraOpacity = s.ScleraOpacity; num11 = num3 + (scl[0] - num3) * scleraOpacity; num12 = num4 + (scl[1] - num4) * scleraOpacity; num13 = num5 + (scl[2] - num5) * scleraOpacity; } float num14 = num9 * num9 + num10 * num10; if (num14 <= s.IrisR * s.IrisR) { float num15 = (float)Math.Sqrt(num14) / s.IrisR; float num16 = 1f - 0.28f * num15 * num15; num11 = iris[0] * num16; num12 = iris[1] * num16; num13 = iris[2] * num16; if (num14 <= s.PupilR * s.PupilR) { num11 = pupil[0]; num12 = pupil[1]; num13 = pupil[2]; } } num6 += num11; num7 += num12; num8 += num13; } } float num17 = 16f; buf[num2] = To8(num6 / num17); buf[num2 + 1] = To8(num7 / num17); buf[num2 + 2] = To8(num8 / num17); } } } private static byte To8(float x) { return (byte)Math.Max(0, Math.Min(255, (int)Math.Round(x * 255f))); } } public enum RangeMode { Recommended, Custom, Unrestricted } public sealed class RangePolicy { public sealed class Violation { public string Setting; public int Value; public int Limit; public bool AboveMaximum; public string Message => "Mead Makeover: " + Setting + " " + Signed(Value) + (AboveMaximum ? " exceeds this server's maximum of " : " is below this server's minimum of ") + Signed(Limit) + "."; } public const int Slots = 14; public const int BreastIndex = 12; public const int ButtIndex = 13; public const string Prefix = "rp1:"; private readonly int[] _min; private readonly int[] _max; public readonly RangeMode Mode; public static readonly RangePolicy Recommended = Build(RangeMode.Recommended, RecommendedMin, RecommendedMax); public static readonly RangePolicy Unrestricted = Build(RangeMode.Unrestricted, HardMin, HardMax); public const string RejectionSummary = "This character uses Mead Makeover settings outside this server's allowed customization ranges. Adjust the character to meet this server's limits before joining."; private static volatile RangePolicy _active = Recommended; private static volatile bool _lab; private static int _revision; public int BreastMin => _min[12]; public int BreastMax => _max[12]; public int ButtMin => _min[13]; public int ButtMax => _max[13]; public bool IsRecommendedRanges => SameRanges(Recommended); public static RangePolicy Active => _active; public static bool LabMode { get { return _lab; } set { _lab = value; } } public static RangePolicy Effective { get { if (!_lab) { return _active; } return Unrestricted; } } public static int Revision => _revision; private RangePolicy(RangeMode mode, int[] min, int[] max) { Mode = mode; _min = min; _max = max; } public static int HardMin(int i) { if (i >= 12) { return -40; } return BodyLimits.Defs[i].DiagMin; } public static int HardMax(int i) { if (i >= 12) { return 40; } return BodyLimits.Defs[i].DiagMax; } public static int RecommendedMin(int i) { if (i >= 12) { if (i != 12) { return -10; } return -10; } return BodyLimits.Defs[i].ProdMin; } public static int RecommendedMax(int i) { if (i >= 12) { if (i != 12) { return 10; } return 10; } return BodyLimits.Defs[i].ProdMax; } public static string SlotName(int i) { if (i < 12) { return BodyLimits.Defs[i].Label; } if (i != 12) { return "Butt size"; } return "Breast size"; } private static RangePolicy Build(RangeMode mode, Func<int, int> min, Func<int, int> max) { int[] array = new int[14]; int[] array2 = new int[14]; for (int i = 0; i < 14; i++) { array[i] = min(i); array2[i] = max(i); } return new RangePolicy(mode, array, array2); } public static RangePolicy Custom(int[] min, int[] max, Action<string> warn = null) { if (min == null || max == null || min.Length != 14 || max.Length != 14) { throw new ArgumentException("exactly " + 14 + " ranges are required"); } return Sanitized(RangeMode.Custom, min, max, warn); } private static RangePolicy Sanitized(RangeMode mode, int[] min, int[] max, Action<string> warn) { int[] array = new int[14]; int[] array2 = new int[14]; for (int i = 0; i < 14; i++) { int num = min[i]; int num2 = max[i]; string text = SlotName(i); if (num > num2) { warn?.Invoke(text + ": minimum " + num + " is above maximum " + num2 + "; the recommended range " + RecommendedMin(i) + ".." + RecommendedMax(i) + " is used for this slider"); num = RecommendedMin(i); num2 = RecommendedMax(i); } int num3 = HardMin(i); int num4 = HardMax(i); if (num < num3) { warn?.Invoke(text + ": minimum " + num + " is below the technical limit " + num3 + "; using " + num3); num = num3; } if (num2 > num4) { warn?.Invoke(text + ": maximum " + num2 + " is above the technical limit " + num4 + "; using " + num4); num2 = num4; } if (num > 0) { warn?.Invoke(text + ": minimum " + num + " is above 0; a slider must be able to return to neutral, using 0"); num = 0; } if (num2 < 0) { warn?.Invoke(text + ": maximum " + num2 + " is below 0; a slider must be able to return to neutral, using 0"); num2 = 0; } array[i] = num; array2[i] = num2; } return new RangePolicy(mode, array, array2); } public int Min(int i) { return _min[i]; } public int Max(int i) { return _max[i]; } public int BodyMin(BodySlot s) { return _min[(int)s]; } public int BodyMax(BodySlot s) { return _max[(int)s]; } public bool SameRanges(RangePolicy o) { if (o == null) { return false; } for (int i = 0; i < 14; i++) { if (_min[i] != o._min[i] || _max[i] != o._max[i]) { return false; } } return true; } public string BodyFreshnessPattern() { StringBuilder stringBuilder = new StringBuilder("\\A").Append("v1:"); for (int i = 0; i < 12; i++) { if (i > 0) { stringBuilder.Append(','); } stringBuilder.Append(BodyCodec.SignedRangePattern(_min[i], _max[i])); } return stringBuilder.Append("\\z").ToString(); } public string ShapeFreshnessPattern() { return "\\Av1:" + BodyCodec.SignedRangePattern(_min[12], _max[12]) + "," + BodyCodec.SignedRangePattern(_min[13], _max[13]) + "\\z"; } public string Describe() { StringBuilder stringBuilder = new StringBuilder(Mode.ToString()).Append(": "); for (int i = 0; i < 14; i++) { if (i > 0) { stringBuilder.Append("; "); } stringBuilder.Append(SlotName(i)).Append(' ').Append(_min[i]) .Append("..") .Append(_max[i]); } return stringBuilder.ToString(); } public string Encode() { StringBuilder stringBuilder = new StringBuilder("rp1:"); stringBuilder.Append((Mode == RangeMode.Custom) ? 'c' : ((Mode == RangeMode.Unrestricted) ? 'u' : 'r')).Append(':'); for (int i = 0; i < 14; i++) { if (i > 0) { stringBuilder.Append(','); } stringBuilder.Append(_min[i].ToString(CultureInfo.InvariantCulture)).Append(',').Append(_max[i].ToString(CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } public static bool TryDecode(string s, out RangePolicy policy) { policy = null; if (string.IsNullOrEmpty(s) || s.Length > 400 || !s.StartsWith("rp1:", StringComparison.Ordinal) || s.Length < "rp1:".Length + 2 || s["rp1:".Length + 1] != ':') { return false; } RangeMode mode; switch (s["rp1:".Length]) { case 'r': mode = RangeMode.Recommended; break; case 'c': mode = RangeMode.Custom; break; case 'u': mode = RangeMode.Unrestricted; break; default: return false; } string[] array = s.Substring("rp1:".Length + 2).Split(new char[1] { ',' }); if (array.Length != 28) { return false; } int[] array2 = new int[14]; int[] array3 = new int[14]; for (int i = 0; i < 14; i++) { if (!ParseInt(array[i * 2], out array2[i]) || !ParseInt(array[i * 2 + 1], out array3[i])) { return false; } } policy = Sanitized(mode, array2, array3, null); return true; } private static bool ParseInt(string t, out int v) { v = 0; if (string.IsNullOrEmpty(t) || t.Length > 4) { return false; } return int.TryParse(t, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out v); } public static RangePolicy Resolve(bool inWorld, bool isServer, string serverValue, RangePolicy local, out string source) { if (!inWorld) { source = "local configuration (not in a world)"; return local ?? Recommended; } if (isServer) { source = "local configuration (this machine is the server)"; return local ?? Recommended; } if (TryDecode(serverValue, out var policy)) { source = "published by the server"; return policy; } source = (string.IsNullOrEmpty(serverValue) ? "the server published no policy: recommended ranges" : "the server's policy was unreadable: recommended ranges"); return Recommended; } public static string ActivePatternFor(string customDataKey) { RangePolicy active = _active; if (active.IsRecommendedRanges) { return null; } if (customDataKey == "beastheim.body.v1") { return active.BodyFreshnessPattern(); } if (customDataKey == "beastheim.shape.v1") { return active.ShapeFreshnessPattern(); } return null; } private static string Signed(int v) { return ((v > 0) ? "+" : "") + v.ToString(CultureInfo.InvariantCulture); } public static List<Violation> Violations(string customDataKey, string value) { List<Violation> list = new List<Violation>(); if (string.IsNullOrEmpty(value) || !value.StartsWith("v1:", StringComparison.Ordinal) || value.Length > 64) { return list; } string[] array = value.Substring(3).Split(new char[1] { ',' }); int num; if (customDataKey == "beastheim.body.v1") { if (array.Length != 12) { return list; } num = 0; } else { if (!(customDataKey == "beastheim.shape.v1")) { return list; } if (array.Length != 2) { return list; } num = 12; } RangePolicy active = _active; for (int i = 0; i < array.Length; i++) { if (!BodyCodec.TryParseCanonicalInt(array[i], out var value2)) { return new List<Violation>(); } int i2 = num + i; if (value2 > active.Max(i2)) { list.Add(new Violation { Setting = SlotName(i2), Value = value2, Limit = active.Max(i2), AboveMaximum = true }); } else if (value2 < active.Min(i2)) { list.Add(new Violation { Setting = SlotName(i2), Value = value2, Limit = active.Min(i2), AboveMaximum = false }); } } return list; } public static string Explain(string customDataKey, string value) { List<Violation> list = Violations(customDataKey, value); if (list.Count == 0) { return null; } StringBuilder stringBuilder = new StringBuilder("This character uses Mead Makeover settings outside this server's allowed customization ranges. Adjust the character to meet this server's limits before joining."); foreach (Violation item in list) { stringBuilder.Append('\n').Append(item.Message); } return stringBuilder.ToString(); } public static bool SetActive(RangePolicy p) { if (p == null) { p = Recommended; } RangePolicy active = _active; if (active.Mode == p.Mode && active.SameRanges(p)) { return false; } _active = p; _revision++; return true; } } [Flags] public enum ServiceScope { None = 0, Body = 1, Eyes = 2, Both = 3, Tattoo = 4, Shape = 8, All = 0xF } public sealed class ServiceRequest { public long PlayerId; public string PlayerName; public ServiceScope Scope = ServiceScope.Both; public string Origin; } public sealed class ServiceGrant { public string Id; public long PlayerId; public ServiceScope Scope; public long IssuedAtMs; public long ExpiresAtMs; public string Issuer; } public sealed class AuthResult { public bool Granted; public ServiceGrant Grant; public string DeniedReason; public static AuthResult Allow(ServiceGrant g) { return new AuthResult { Granted = true, Grant = g }; } public static AuthResult Deny(string reason) { return new AuthResult { Granted = false, DeniedReason = reason }; } } public interface IServiceAuthorizer { string Name { get; } AuthResult Authorize(ServiceRequest request, long nowMs); } public sealed class DevAdminAuthorizer : IServiceAuthorizer { private readonly Func<bool> _isAdmin; private readonly bool _allowAnyone; private readonly long _ttlMs; private long _counter; public string Name => "dev-admin"; public DevAdminAuthorizer(Func<bool> isAdmin, bool allowAnyone, long ttlMs) { _isAdmin = isAdmin ?? ((Func<bool>)(() => false)); _allowAnyone = allowAnyone; _ttlMs = ((ttlMs <= 0) ? 600000 : ttlMs); } public AuthResult Authorize(ServiceRequest request, long nowMs) { if (request == null) { return AuthResult.Deny("no request"); } if (!_allowAnyone && !_isAdmin()) { return AuthResult.Deny("only an admin or host may open Mead Makeover for testing"); } _counter++; return AuthResult.Allow(new ServiceGrant { Id = "dev-" + request.PlayerId + "-" + _counter, PlayerId = request.PlayerId, Scope = ((request.Scope == ServiceScope.None) ? ServiceScope.Both : request.Scope), IssuedAtMs = nowMs, ExpiresAtMs = nowMs + _ttlMs, Issuer = Name }); } } public sealed class ServiceAccess { private readonly List<IServiceAuthorizer> _authorizers = new List<IServiceAuthorizer>(); public int Count => _authorizers.Count; public void Register(IServiceAuthorizer a) { if (a == null) { throw new ArgumentNullException("a"); } _authorizers.RemoveAll((IServiceAuthorizer x) => x.Name == a.Name); _authorizers.Add(a); } public AuthResult Authorize(ServiceRequest request, long nowMs) { if (_authorizers.Count == 0) { return AuthResult.Deny("no service is available"); } string text = null; foreach (IServiceAuthorizer authorizer in _authorizers) { AuthResult authResult = authorizer.Authorize(request, nowMs); if (authResult != null && authResult.Granted && authResult.Grant != null) { return authResult; } if (text == null && authResult != null) { text = authResult.DeniedReason; } } return AuthResult.Deny(text ?? "not authorized"); } } public sealed class GrantLedger { private readonly HashSet<string> _consumed = new HashSet<string>(StringComparer.Ordinal); private readonly Queue<string> _order = new Queue<string>(); private readonly int _capacity; public GrantLedger(int capacity) { _capacity = ((capacity < 8) ? 8 : capacity); } public bool TryConsume(ServiceGrant g, long nowMs, out string reason) { reason = null; if (g == null || string.IsNullOrEmpty(g.Id)) { reason = "invalid grant"; return false; } if (nowMs > g.ExpiresAtMs) { reason = "the grant has expired"; return false; } if (_consumed.Contains(g.Id)) { reason = "the grant was already used"; return false; } _consumed.Add(g.Id); _order.Enqueue(g.Id); while (_order.Count > _capacity) { _consumed.Remove(_order.Dequeue()); } return true; } } public enum SessionState { Open, Committed, Cancelled, Expired, Refused } public sealed class ServiceSession { private readonly GrantLedger _ledger; public readonly ServiceGrant Grant; public readonly AppearanceDraft Draft; public SessionState State { get; private set; } public string RefusedReason { get; private set; } public ServiceSession(ServiceGrant grant, GrantLedger ledger, AppearanceDraft draft) { if (grant == null) { throw new ArgumentNullException("grant"); } if (draft == null) { throw new ArgumentNullException("draft"); } Grant = grant; _ledger = ledger ?? new GrantLedger(64); Draft = draft; State = SessionState.Open; } public bool Tick(long nowMs) { if (State == SessionState.Open && nowMs > Grant.ExpiresAtMs) { State = SessionState.Expired; Draft.Revert(); } return State == SessionState.Open; } public bool TryCommit(long nowMs, out AppearanceState result) { result = null; if (State != SessionState.Open) { return false; } if (!_ledger.TryConsume(Grant, nowMs, out var reason)) { State = ((nowMs > Grant.ExpiresAtMs) ? SessionState.Expired : SessionState.Refused); RefusedReason = reason; Draft.Revert(); return false; } State = SessionState.Committed; result = Draft.Result(); return true; } public void Cancel() { if (State == SessionState.Open) { State = SessionState.Cancelled; Draft.Revert(); } } } public struct ShapeChoice : IEquatable<ShapeChoice> { public readonly int Breast; public readonly int Butt; public static readonly ShapeChoice None = new ShapeChoice(0, 0); public bool IsNone { get { if (Breast == 0) { return Butt == 0; } return false; } } public ShapeChoice(int breast, int butt) { Breast = breast; Butt = butt; } public ShapeChoice WithBreast(int v) { return new ShapeChoice(v, Butt); } public ShapeChoice WithButt(int v) { return new ShapeChoice(Breast, v); } public bool Equals(ShapeChoice o) { if (Breast == o.Breast) { return Butt == o.Butt; } return false; } public override bool Equals(object obj) { if (obj is ShapeChoice) { return Equals((ShapeChoice)obj); } return false; } public override int GetHashCode() { return Breast * 1009 + Butt; } public override string ToString() { if (!IsNone) { int breast = Breast; string text = breast.ToString(); breast = Butt; return text + "/" + breast; } return "none"; } public ShapeChoice Clamped() { return new ShapeChoice(Math.Max(ShapeLimits.BreastMin, Math.Min(ShapeLimits.BreastMax, Breast)), Math.Max(ShapeLimits.ButtMin, Math.Min(ShapeLimits.ButtMax, Butt))); } } public static class ShapeLimits { public const int ProdBreastMin = -10; public const int ProdBreastMax = 10; public const int ProdButtMin = -10; public const int ProdButtMax = 10; public const int DiagMin = -40; public const int DiagMax = 40; public const float CmPerUnit = 0.5f; public static int BreastMin => RangePolicy.Effective.BreastMin; public static int BreastMax => RangePolicy.Effective.BreastMax; public static int ButtMin => RangePolicy.Effective.ButtMin; public static int ButtMax => RangePolicy.Effective.ButtMax; } public static class ShapeCodec { public const string Prefix = "v1:"; public const int MaxLength = 12; public static string Serialize(ShapeChoice c) { if (!c.IsNone) { int breast = c.Breast; string text = breast.ToString(CultureInfo.InvariantCulture); breast = c.Butt; return "v1:" + text + "," + breast.ToString(CultureInfo.InvariantCulture); } return null; } public static CodecStatus TryParse(string s, out ShapeChoice c) { c = ShapeChoice.None; if (string.IsNullOrEmpty(s)) { return CodecStatus.Absent; } if (!s.StartsWith("v1:", StringComparison.Ordinal)) { if (s.Length >= 3 && s[0] == 'v') { int i; for (i = 1; i < s.Length && s[i] >= '0' && s[i] <= '9'; i++) { } if (i > 1 && i < s.Length && s[i] == ':') { return CodecStatus.UnknownVersion; } } return CodecStatus.Malformed; } if (s.Length > 12) { return CodecStatus.Malformed; } string[] array = s.Substring("v1:".Length).Split(new char[1] { ',' }); if (array.Length != 2 || !BodyCodec.TryParseCanonicalInt(array[0], out var value) || !BodyCodec.TryParseCanonicalInt(array[1], out var value2)) { return CodecStatus.Malformed; } c = new ShapeChoice(value, value2); RangePolicy active = RangePolicy.Active; if (value >= active.BreastMin && value <= active.BreastMax && value2 >= active.ButtMin && value2 <= active.ButtMax) { return CodecStatus.Ok; } return CodecStatus.OutOfRange; } public static ShapeChoice ParseForRender(string s) { ShapeChoice c; CodecStatus codecStatus = TryParse(s, out c); if (codecStatus == CodecStatus.Ok || codecStatus == CodecStatus.OutOfRange) { return c.Clamped(); } return ShapeChoice.None; } public static string Canonical(string s) { return Serialize(ParseForRender(s)) ?? ""; } public static string FreshnessPattern() { return RangePolicy.Active.ShapeFreshnessPattern(); } } public enum ShapeStyle { Simple, Refined } public sealed class ShapeZone { public bool Front; public float CentreX; public float CentreY; public float RadiusX; public float RadiusUp; public float RadiusDown; public float FaceFrom; public float FaceTo; public float DepthFrom; public float DepthTo; public float GuardFrom = 0.005f; public float GuardTo = 0.02f; public float MinShare; public float Dome; public float OutX; public float FlatZ; public float SimpleCentreZ; public float SimpleRadius; public float SimpleRadiusX; public float SimpleRadiusUp; public float SimpleRadiusDown; public ShapeZone Clone() { return (ShapeZone)MemberwiseClone(); } public bool InRegion(float x, float y, float z, float margin) { if (Front ? (z < 0f) : (z > 0f)) { return false; } float num = (Math.Abs(x) - CentreX) / (RadiusX * margin); float num2 = y - CentreY; float num3 = num2 / (((num2 >= 0f) ? RadiusUp : RadiusDown) * margin); return num * num + num3 * num3 < 1f; } } public sealed class ShapeTuning { public ShapeZone Breast; public ShapeZone Butt; public static ShapeTuning For(int model, float breastDy = 0f, float buttDy = 0f, float radiusScale = 1f) { bool flag = model == 1; ShapeTuning shapeTuning = new ShapeTuning { Breast = new ShapeZone { Front = true, CentreX = (flag ? 0.095f : 0.09f), CentreY = (flag ? 1.462f : 1.405f), RadiusX = 0.085f, RadiusUp = (flag ? 0.09f : 0.085f), RadiusDown = 0.085f, FaceFrom = 0.45f, FaceTo = 0.8f, DepthFrom = 0.03f, DepthTo = 0.07f, MinShare = 0.5f, Dome = 0.3f, OutX = 0.15f, FlatZ = (flag ? 0.095f : 0.085f), SimpleCentreZ = 0.04f, SimpleRadius = 0.15f, SimpleRadiusX = 0.12f, SimpleRadiusUp = 0.1f, SimpleRadiusDown = (flag ? 0.09f : 0.1f) }, Butt = new ShapeZone { Front = false, CentreX = 0.095f, CentreY = (flag ? 0.98f : 0.97f), RadiusX = 0.095f, RadiusUp = 0.095f, RadiusDown = 0.09f, FaceFrom = 0.35f, FaceTo = 0.75f, DepthFrom = 0.06f, DepthTo = 0.11f, MinShare = 0.5f, Dome = 0.3f, OutX = 0.15f, FlatZ = -0.05f, SimpleCentreZ = -0.07f, SimpleRadius = 0.17f, SimpleRadiusX = 0.13f, SimpleRadiusUp = 0.12f, SimpleRadiusDown = 0.11f } }; shapeTuning.Breast.CentreY += breastDy; shapeTuning.Butt.CentreY += buttDy; ShapeZone[] array = new ShapeZone[2] { shapeTuning.Breast, shapeTuning.Butt }; foreach (ShapeZone obj in array) { obj.RadiusX *= radiusScale; obj.RadiusUp *= radiusScale; obj.RadiusDown *= radiusScale; obj.SimpleRadius *= radiusScale; obj.SimpleRadiusX *= radiusScale; obj.SimpleRadiusUp *= radiusScale; obj.SimpleRadiusDown *= radiusScale; } return shapeTuning; } } public sealed class ShapeMeshPlan { public int OriginalCount; public int[] ParentA = new int[0]; public int[] ParentB = new int[0]; public int[] SkinTris = new int[0]; public int NewCount => ParentA.Length; public int TotalCount => OriginalCount + ParentA.Length; } public static class ShapeRefine { public const float RegionMargin = 1.35f; public const int DefaultLevels = 3; private static long Key(int u, int v) { if (u >= v) { return ((long)v << 32) | (uint)u; } return ((long)u << 32) | (uint)v; } public static ShapeMeshPlan Build(float[] verts, int[] skinTris, ShapeTuning tuning, int levels) { int originalCount = verts.Length / 3; ShapeMeshPlan shapeMeshPlan = new ShapeMeshPlan { OriginalCount = originalCount }; List<float> list = new List<float>(verts); List<int> list2 = new List<int>(); List<int> list3 = new List<int>(); int[] array = (int[])skinTris.Clone(); for (int i = 0; i < levels; i++) { int num = list.Count / 3; bool[] array2 = new bool[num]; for (int j = 0; j < num; j++) { float x = 95f * list[j * 3]; float y = 95f * list[j * 3 + 2]; float z = -95f * list[j * 3 + 1]; array2[j] = tuning.Breast.InRegion(x, y, z, 1.35f) || tuning.Butt.InRegion(x, y, z, 1.35f); } Dictionary<string, int> dictionary = new Dictionary<string, int>(); int[] array3 = new int[num]; for (int k = 0; k < num; k++) { string key = Math.Round((double)list[k * 3] * 1000000.0) + "|" + Math.Round((double)list[k * 3 + 1] * 1000000.0) + "|" + Math.Round((double)list[k * 3 + 2] * 1000000.0); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = dictionary.Count); } array3[k] = value; } HashSet<long> hashSet = new HashSet<long>(); for (int l = 0; l + 2 < array.Length; l += 3) { int num2 = array[l]; int num3 = array[l + 1]; int num4 = array[l + 2]; if (array2[num2] || array2[num3] || array2[num4]) { hashSet.Add(Key(array3[num2], array3[num3])); hashSet.Add(Key(array3[num3], array3[num4])); hashSet.Add(Key(array3[num4], array3[num2])); } } if (hashSet.Count == 0) { break; } Dictionary<long, int> dictionary2 = new Dictionary<long, int>(); List<int> list4 = new List<int>(array.Length * 2); int[] array4 = new int[3]; int[] array5 = new int[3]; for (int m = 0; m + 2 < array.Length; m += 3) { array4[0] = array[m]; array4[1] = array[m + 1]; array4[2] = array[m + 2]; int num5 = 0; for (int n = 0; n < 3; n++) { int num6 = array4[n]; int num7 = array4[(n + 1) % 3]; long key2 = Key(num6, num7); if (hashSet.Contains(Key(array3[num6], array3[num7]))) { if (!dictionary2.TryGetValue(key2, out var value2)) { value2 = (dictionary2[key2] = list.Count / 3); int num9 = Math.Min(num6, num7); int num10 = Math.Max(num6, num7); list.Add((list[num9 * 3] + list[num10 * 3]) * 0.5f); list.Add((list[num9 * 3 + 1] + list[num10 * 3 + 1]) * 0.5f); list.Add((list[num9 * 3 + 2] + list[num10 * 3 + 2]) * 0.5f); list2.Add(num9); list3.Add(num10); } array5[n] = value2; num5++; } else { array5[n] = -1; } } int num11 = array4[0]; int num12 = array4[1]; int num13 = array4[2]; switch (num5) { case 0: list4.Add(num11); list4.Add(num12); list4.Add(num13); continue; case 3: Add(list4, num11, array5[0], array5[2]); Add(list4, array5[0], num12, array5[1]); Add(list4, array5[2], array5[1], num13); Add(list4, array5[0], array5[1], array5[2]); continue; case 1: { int num14 = ((array5[0] < 0) ? ((array5[1] >= 0) ? 1 : 2) : 0); int a = array4[num14]; int b = array4[(num14 + 1) % 3]; int c = array4[(num14 + 2) % 3]; int num15 = array5[num14]; Add(list4, a, num15, c); Add(list4, num15, b, c); continue; } } int num16 = ((array5[0] >= 0) ? ((array5[1] < 0) ? 1 : 2) : 0); int a2 = array4[(num16 + 1) % 3]; int b2 = array4[(num16 + 2) % 3]; int num17 = array4[num16]; int num18 = array5[(num16 + 1) % 3]; int num19 = array5[(num16 + 2) % 3]; Add(list4, num18, b2, num19); if (Dist(list, a2, num19) <= Dist(list, num18, num17)) { Add(list4, a2, num18, num19); Add(list4, a2, num19, num17); } else { Add(list4, a2, num18, num17); Add(list4, num18, num19, num17); } } array = list4.ToArray(); } shapeMeshPlan.ParentA = list2.ToArray(); shapeMeshPlan.ParentB = list3.ToArray(); shapeMeshPlan.SkinTris = array; return shapeMeshPlan; } private static void Add(List<int> l, int a, int b, int c) { l.Add(a); l.Add(b); l.Add(c); } private static float Dist(List<float> p, int a, int b) { float num = p[a * 3] - p[b * 3]; float num2 = p[a * 3 + 1] - p[b * 3 + 1]; float num3 = p[a * 3 + 2] - p[b * 3 + 2]; return num * num + num2 * num2 + num3 * num3; } public static float[] Expand(float[] attr, int stride, ShapeMeshPlan plan, bool normalize) { float[] array = new float[plan.TotalCount * stride]; Array.Copy(attr, array, Math.Min(attr.Length, plan.OriginalCount * stride)); for (int i = 0; i < plan.NewCount; i++) { int num = (plan.OriginalCount + i) * stride; int num2 = plan.ParentA[i] * stride; int num3 = plan.ParentB[i] * stride; for (int j = 0; j < stride; j++) { array[num + j] = (array[num2 + j] + array[num3 + j]) * 0.5f; } if (normalize && stride >= 3) { float num4 = (float)Math.Sqrt(array[num] * array[num] + array[num + 1] * array[num + 1] + array[num + 2] * array[num + 2]); if (num4 > 1E-09f) { array[num] /= num4; array[num + 1] /= num4; array[num + 2] /= num4; } } } return array; } } public static class BoneBlend { public static void Blend(int[] ia, float[] wa, int[] ib, float[] wb, int[] outIdx, float[] outW) { List<int> list = new List<int>(8); List<float> list2 = new List<float>(8); for (int i = 0; i < ia.Length; i++) { Accumulate(list, list2, ia[i], wa[i] * 0.5f); } for (int j = 0; j < ib.Length; j++) { Accumulate(list, list2, ib[j], wb[j] * 0.5f); } for (int k = 1; k < list.Count; k++) { int num = k; while (num > 0 && list2[num] > list2[num - 1]) { int value = list[num]; list[num] = list[num - 1]; list[num - 1] = value; float value2 = list2[num]; list2[num] = list2[num - 1]; list2[num - 1] = value2; num--; } } float num2 = 0f; for (int l = 0; l < outIdx.Length; l++) { outIdx[l] = ((l < list.Count) ? list[l] : 0); outW[l] = ((l < list.Count) ? list2[l] : 0f); num2 += outW[l]; } if (num2 > 1E-09f) { for (int m = 0; m < outW.Length; m++) { outW[m] /= num2; } } } private static void Accumulate(List<int> idx, List<float> w, int bone, float weight) { if (!(weight <= 0f)) { int num = idx.IndexOf(bone); if (num >= 0) { w[num] += weight; return; } idx.Add(bone); w.Add(weight); } } } public static class ShapeDeform { public const float MeshToWorld = 95f; public static int Apply(float[] verts, float[] restNormals, float[] breastShare, float[] buttShare, float breastCm, float buttCm, ShapeTuning tuning, float[] outVerts, ShapeStyle style = ShapeStyle.Refined) { if (verts == null || outVerts == null || outVerts.Length != verts.Length) { throw new ArgumentException("buffers"); } Array.Copy(verts, outVerts, verts.Length); if (breastCm == 0f && buttCm == 0f) { return 0; } if (restNormals == null || restNormals.Length != verts.Length) { throw new ArgumentException("normals"); } ShapeTuning shapeTuning = tuning ?? ShapeTuning.For(1); int num = verts.Length / 3; float[] array = new float[num]; float[] array2 = new float[num]; float[] array3 = new float[num]; float[] array4 = new float[num]; float[] array5 = new float[num]; float[] array6 = new float[num]; for (int i = 0; i < num; i++) { array[i] = 95f * verts[i * 3]; array2[i] = 95f * verts[i * 3 + 2]; array3[i] = -95f * verts[i * 3 + 1]; array4[i] = restNormals[i * 3]; array5[i] = restNormals[i * 3 + 2]; array6[i] = 0f - restNormals[i * 3 + 1]; } float[] array7 = new float[num]; float[] array8 = new float[num]; float[] array9 = new float[num]; if (style == ShapeStyle.Simple) { ZoneSimple(shapeTuning.Breast, breastCm, breastShare, array, array2, array3, array6, array7, array8, array9); ZoneSimple(shapeTuning.Butt, buttCm, buttShare, array, array2, array3, array6, array7, array8, array9); } else { Zone(shapeTuning.Breast, breastCm, breastShare, array, array2, array3, array6, array7, array8, array9); Zone(shapeTuning.Butt, buttCm, buttShare, array, array2, array3, array6, array7, array8, array9); } int num2 = 0; for (int j = 0; j < num; j++) { if (array7[j] != 0f || array8[j] != 0f || array9[j] != 0f) { outVerts[j * 3] += array7[j] / 95f; outVerts[j * 3 + 1] += (0f - array9[j]) / 95f; outVerts[j * 3 + 2] += array8[j] / 95f; num2++; } } return num2; } public static float Smooth(float a, float b, float x) { float num = (x - a) / (b - a); if (num <= 0f) { return 0f; } if (num >= 1f) { return 1f; } return num * num * (3f - 2f * num); } public static float Bell(float t2) { if (t2 >= 1f) { return 0f; } return 0.5f * (1f + (float)Math.Cos(Math.PI * Math.Sqrt(t2))); } public static float[] Mask(ShapeZone z, float[] share, float[] wx, float[] wy, float[] wz, float[] nz) { int num = wx.Length; float[] array = new float[num]; for (int i = 0; i < num; i++) { if (share == null || share[i] <= 0f) { continue; } float num2 = Math.Abs(wx[i]); float num3 = (num2 - z.CentreX) / z.RadiusX; float num4 = wy[i] - z.CentreY; num4 = ((num4 >= 0f) ? (num4 / z.RadiusUp) : (num4 / z.RadiusDown)); float num5 = Bell(num3 * num3 + num4 * num4); if (num5 == 0f) { continue; } float num6 = Smooth(z.FaceFrom, z.FaceTo, z.Front ? nz[i] : (0f - nz[i])); if (num6 != 0f) { float num7 = Smooth(z.DepthFrom, z.DepthTo, z.Front ? wz[i] : (0f - wz[i])); if (num7 != 0f) { float num8 = Smooth(z.GuardFrom, z.GuardTo, num2); float num9 = Smooth(z.MinShare - 0.15f, z.MinShare, share[i]); array[i] = num5 * num6 * num7 * num8 * num9; } } } return array; } public static float Falloff(float d, float radius) { float num = 1f - d / radius * (d / radius); if (!(num > 0f)) { return 0f; } return num * num; } private static void ZoneSimple(ShapeZone z, float cm, float[] share, float[] wx, float[] wy, float[] wz, float[] nz, float[] dx, float[] dy, float[] dz) { if (cm == 0f || share == null) { return; } int num = wx.Length; float[] array = new float[num]; float[] array2 = new float[num]; float[] array3 = new float[num]; float num2 = 0f; for (int i = 0; i < num; i++) { if (share[i] <= 0f) { continue; } float num3 = Math.Abs(wx[i]); float num4 = (num3 - z.CentreX) / z.SimpleRadiusX; float num5 = wy[i] - z.CentreY; num5 = ((num5 >= 0f) ? (num5 / z.SimpleRadiusUp) : (num5 / z.SimpleRadiusDown)); float num6 = (float)Math.Sqrt(num4 * num4 + num5 * num5); if (num6 >= 1f) { continue; } float num7 = Smooth(1f, 0.8f, num6); float num8 = Smooth(z.FaceFrom, z.FaceTo, z.Front ? nz[i] : (0f - nz[i])); float num9 = Smooth(z.DepthFrom, z.DepthTo, z.Front ? wz[i] : (0f - wz[i])); float num10 = Smooth(z.GuardFrom, z.GuardTo, num3); float num11 = Smooth(z.MinShare - 0.15f, z.MinShare, share[i]); float num12 = num7 * num8 * num9 * num10 * num11; if (num12 <= 0f) { continue; } int num13 = Math.Sign(wx[i]); float num14 = wx[i] - (float)num13 * z.CentreX; float num15 = wy[i] - z.CentreY; float num16 = wz[i] - z.SimpleCentreZ; float num17 = Falloff((float)Math.Sqrt(num14 * num14 + num15 * num15 + num16 * num16), z.SimpleRadius) * num12; if (!(num17 <= 0f)) { array[i] = num14 * num17; array2[i] = num15 * num17; array3[i] = num16 * num17; float num18 = (float)Math.Sqrt(array[i] * array[i] + array2[i] * array2[i] + array3[i] * array3[i]); if (num18 > num2) { num2 = num18; } } } if (num2 <= 0f) { return; } float num19 = cm / 100f / num2; for (int j = 0; j < num; j++) { if (array[j] == 0f && array2[j] == 0f && array3[j] == 0f) { continue; } float num20 = array[j] * num19; float num21 = array2[j] * num19; float num22 = array3[j] * num19; if (cm > 0f) { if (z.Front) { if (num22 < 0f) { num22 = 0f; } } else { if (num22 > 0f) { num22 = 0f; } if (num21 < 0f) { num21 = 0f; } } } else { float num23 = Math.Max(0f, z.Front ? (wz[j] - z.FlatZ) : (z.FlatZ - wz[j])); float num24 = Math.Abs(num22); if (num24 > 1E-09f) { float num25 = ((num23 > 1E-06f) ? ((float)((double)num23 * Math.Tanh(num24 / num23)) / num24) : 0f); num20 *= num25; num21 *= num25; num22 *= num25; } if (z.Front) { if (num22 > 0f) { num22 = 0f; } } else { if (num22 < 0f) { num22 = 0f; } if (num21 > 0f) { num21 = 0f; } } } dx[j] += num20; dy[j] += num21; dz[j] += num22; } } private static void Zone(ShapeZone z, float cm, float[] share, float[] wx, float[] wy, float[] wz, float[] nz, float[] dx, float[] dy, float[] dz) { if (cm == 0f || share == null) { return; } float[] array = Mask(z, share, wx, wy, wz, nz); float num = 0f; for (int i = 0; i < array.Length; i++) { if (array[i] > num) { num = array[i]; } } if (num <= 0f) { return; } float num2 = (z.Front ? 1f : (-1f)); for (int j = 0; j < array.Length; j++) { if (array[j] <= 0f) { continue; } int num3 = Math.Sign(wx[j]); float num4 = (float)num3 * (Math.Abs(wx[j]) - z.CentreX) / z.RadiusX; float num5 = (wy[j] - z.CentreY) / ((wy[j] >= z.CentreY) ? z.RadiusUp : z.RadiusDown); float num6 = z.Dome * num4 + (float)num3 * z.OutX; float num7 = z.Dome * num5; float num8 = num2; float num9 = (float)Math.Sqrt(num6 * num6 + num7 * num7 + num8 * num8); float num10 = cm / 100f * (array[j] / num); float num11; float num12; float num13; if (cm > 0f) { num11 = num6 / num9 * num10; num12 = num7 / num9 * num10; num13 = num8 / num9 * num10; if (z.Front ? (num13 < 0f) : (num13 > 0f)) { num13 = 0f; } } else { float num14 = Math.Max(0f, z.Front ? (wz[j] - z.FlatZ) : (z.FlatZ - wz[j])); float num15 = Math.Abs(num10); float num16 = ((num15 > 1E-09f && num14 > 1E-06f) ? ((float)((double)num14 * Math.Tanh(num15 / num14)) / num15) : 0f); num11 = num6 / num9 * num10 * num16; num12 = num7 / num9 * num10 * num16; num13 = num8 / num9 * num10 * num16; } dx[j] += num11; dy[j] += num12; dz[j] += num13; } } public static void RecomputeNormals(float[] verts, int[] tris, float[] normals, bool[] moved) { int num = verts.Length / 3; Dictionary<long, float[]> dictionary = new Dictionary<long, float[]>(); Func<int, long> func = (int i) => (Quant(verts[i * 3]) * 73856093) ^ (Quant(verts[i * 3 + 1]) * 19349663) ^ (Quant(verts[i * 3 + 2]) * 83492791); for (int num2 = 0; num2 + 2 < tris.Length; num2 += 3) { int num3 = tris[num2]; int num4 = tris[num2 + 1]; int num5 = tris[num2 + 2]; float num6 = verts[num4 * 3] - verts[num3 * 3]; float num7 = verts[num4 * 3 + 1] - verts[num3 * 3 + 1]; float num8 = verts[num4 * 3 + 2] - verts[num3 * 3 + 2]; float num9 = verts[num5 * 3] - verts[num3 * 3]; float num10 = verts[num5 * 3 + 1] - verts[num3 * 3 + 1]; float num11 = verts[num5 * 3 + 2] - verts[num3 * 3 + 2]; float num12 = num7 * num11 - num8 * num10; float num13 = num8 * num9 - num6 * num11; float num14 = num6 * num10 - num7 * num9; int[] array = new int[3] { num3, num4, num5 }; foreach (int arg in array) { long key = func(arg); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new float[3]); } value[0] += num12; value[1] += num13; value[2] += num14; } } for (int num16 = 0; num16 < num; num16++) { if (moved[num16] && dictionary.TryGetValue(func(num16), out var value2)) { float num17 = (float)Math.Sqrt(value2[0] * value2[0] + value2[1] * value2[1] + value2[2] * value2[2]); if (!(num17 < 1E-12f)) { normals[num16 * 3] = value2[0] / num17; normals[num16 * 3 + 1] = value2[1] / num17; normals[num16 * 3 + 2] = value2[2] / num17; } } } } private static long Quant(float v) { return (long)Math.Round((double)v * 1000000.0); } } public static class ArmorShapeCaps { public sealed class Caps { public float Breast; public float Butt; public Caps(float breast, float butt) { Breast = breast; Butt = butt; } } public const float Unlimited = 99f; public const float UnknownCm = 1f; private static readonly Dictionary<int, Caps> Table = Build(); private static readonly Dictionary<int, Caps> SimpleTable = BuildSimple(); private static readonly Caps NoneWorn = new Caps(99f, 99f); private static Dictionary<int, Caps> BuildSimple() { Dictionary<int, Caps> t = new Dictionary<int, Caps>(); Action<string, float, float> obj = delegate(string name, float br, float bu) { t[Hash(name)] = new Caps(br, bu); }; obj("ArmorRagsChest", 99f, 5f); obj("ArmorLeatherChest", 99f, 5f); obj("ArmorTrollLeatherChest", 99f, 5f); obj("ArmorBronzeChest", 2f, 8f); obj("ArmorIronChest", 1.5f, 8f); obj("ArmorMageChest", 1f, 6f); obj("ArmorWolfChest", 2f, 3f); obj("ArmorFenringChest", 0f, 6f); obj("ArmorPaddedCuirass", 0f, 2f); obj("ArmorCarapaceChest", 2f, 6f); obj("ArmorRootChest", 2f, 2f); obj("ArmorHarvester1", 10f, 6f); obj("ArmorDress1", 10f, 6f); obj("ArmorTunic1", 10f, 6f); obj("ArmorMageChest_Ashlands", 5f, 1f); obj("ArmorAshlandsMediumChest", 3f, 4f); obj("ArmorBerserkerChest", 1.5f, 10f); obj("ArmorIronLegs", 99f, 2f); obj("ArmorWolfLegs", 99f, 2f); obj("ArmorBronzeLegs", 99f, 2f); obj("ArmorMageLegs", 99f, 2f); obj("ArmorPaddedGreaves", 99f, 2f); obj("ArmorRagsLegs", 99f, 99f); obj("ArmorLeatherLegs", 99f, 99f); obj("ArmorTrollLeatherLegs", 99f, 99f); return t; } private static Dictionary<int, Caps> Build() { Dictionary<int, Caps> t = new Dictionary<int, Caps>(); Action<string, float, float> obj = delegate(string name, float br, float bu) { t[Hash(name)] = new Caps(br, bu); }; obj("ArmorRagsChest", 99f, 10f); obj("ArmorLeatherChest", 99f, 10f); obj("ArmorTrollLeatherChest", 99f, 10f); obj("ArmorBronzeChest", 10f, 10f); obj("ArmorIronChest", 6f, 10f); obj("ArmorMageChest", 2f, 10f); obj("ArmorWolfChest", 5f, 4f); obj("ArmorFenringChest", 0f, 10f); obj("ArmorPaddedCuirass", 0.5f, 5f); obj("ArmorCarapaceChest", 3f, 10f); obj("ArmorRootChest", 5f, 4f); obj("ArmorHarvester1", 10f, 5f); obj("ArmorDress1", 10f, 10f); obj("ArmorTunic1", 10f, 5f); obj("ArmorMageChest_Ashlands", 10f, 2f); obj("ArmorAshlandsMediumChest", 10f, 6f); obj("ArmorBerserkerChest", 10f, 10f); obj("ArmorIronLegs", 99f, 2f); obj("ArmorWolfLegs", 99f, 2f); obj("ArmorBronzeLegs", 99f, 2f); obj("ArmorMageLegs", 99f, 2f); obj("ArmorPaddedGreaves", 99f, 2f); obj("ArmorRagsLegs", 99f, 99f); obj("ArmorLeatherLegs", 99f, 99f); obj("ArmorTrollLeatherLegs", 99f, 99f); return t; } public static int Hash(string s) { int num = 5381; int num2 = num; for (int i = 0; i < s.Length && s[i] != 0; i += 2) { num = ((num << 5) + num) ^ s[i]; if (i == s.Length - 1 || s[i + 1] == '\0') { break; } num2 = ((num2 << 5) + num2) ^ s[i + 1]; } return num + num2 * 1566083941; } public static bool IsKnown(int itemHash) { return Table.ContainsKey(itemHash); } public static void Limits(int chestHash, int legsHash, float unknownCm, out float breastCm, out float buttCm, ShapeStyle style = ShapeStyle.Refined) { Dictionary<int, Caps> table = ((style == ShapeStyle.Simple) ? SimpleTable : Table); Caps caps = Lookup(table, chestHash, unknownCm); Caps caps2 = Lookup(table, legsHash, unknownCm); breastCm = caps.Breast; buttCm = Math.Min(caps.Butt, caps2.Butt); } private static Caps Lookup(Dictionary<int, Caps> table, int hash, float unknownCm) { if (hash == 0) { return NoneWorn; } if (!table.TryGetValue(hash, out var value)) { return new Caps(unknownCm, unknownCm); } return value; } public static float Limit(float requestedCm, float capCm) { if (!(requestedCm > 0f)) { return requestedCm; } return Math.Min(requestedCm, capCm); } } public sealed class PublishPlan { public bool Body; public bool Eyes; public bool Tattoo; public bool Shape; public string BodyValue; public string EyesValue; public string TattooValue; public string ShapeValue; public bool Any { get { if (!Body && !Eyes && !Tattoo) { return Shape; } return true; } } } public static class ZdoPublishLogic { public static PublishPlan Plan(bool isOwner, bool zdoAvailable, string localBody, string localEyes, string zdoBody, string zdoEyes, BodyExposure exposure, bool eyesEnabled) { PublishPlan publishPlan = new PublishPlan(); if (!isOwner || !zdoAvailable) { return publishPlan; } string text = BodyCodec.Canonical(localBody, exposure); string b = zdoBody ?? ""; if (!string.Equals(text, b, StringComparison.Ordinal)) { publishPlan.Body = true; publishPlan.BodyValue = text; } string text2 = (eyesEnabled ? EyeCodec.Canonical(localEyes) : ""); string b2 = zdoEyes ?? ""; if (!string.Equals(text2, b2, StringComparison.Ordinal)) { publishPlan.Eyes = true; publishPlan.EyesValue = text2; } return publishPlan; } } public static class ZdoPublishLogicTattoo { public static PublishPlan Plan(bool isOwner, bool zdoAvailable, string localBody, string localEyes, string localTattoo, string zdoBody, string zdoEyes, string zdoTattoo, BodyExposure exposure, bool eyesEnabled, bool tattooEnabled) { PublishPlan publishPlan = ZdoPublishLogic.Plan(isOwner, zdoAvailable, localBody, localEyes, zdoBody, zdoEyes, exposure, eyesEnabled); if (!isOwner || !zdoAvailable) { return publishPlan; } string text = (tattooEnabled ? TattooCodec.Canonical(localTattoo) : ""); if (!string.Equals(text, zdoTattoo ?? "", StringComparison.Ordinal)) { publishPlan.Tattoo = true; publishPlan.TattooValue = text; } return publishPlan; } } public static class ZdoPublishLogicShape { public static PublishPlan Plan(bool isOwner, bool zdoAvailable, string localBody, string localEyes, string localTattoo, string localShape, string zdoBody, string zdoEyes, string zdoTattoo, string zdoShape, BodyExposure exposure, bool eyesEnabled, bool tattooEnabled, bool shapeEnabled) { PublishPlan publishPlan = ZdoPublishLogicTattoo.Plan(isOwner, zdoAvailable, localBody, localEyes, localTattoo, zdoBody, zdoEyes, zdoTattoo, exposure, eyesEnabled, tattooEnabled); if (!isOwner || !zdoAvailable) { return publishPlan; } string text = (shapeEnabled ? ShapeCodec.Canonical(localShape) : ""); if (!string.Equals(text, zdoShape ?? "", StringComparison.Ordinal)) { publishPlan.Shape = true; publishPlan.ShapeValue = text; } return publishPlan; } } public sealed class PublishThrottle { private readonly double _steadyInterval; private readonly double _burstSeconds; private double _burstUntil = -1.0; private double _next; public PublishThrottle(double steadyIntervalSeconds, double burstSeconds) { _steadyInterval = steadyIntervalSeconds; _burstSeconds = burstSeconds; } public void Restart(double now) { _burstUntil = now + _burstSeconds; _next = now; } public bool ShouldCheck(double now) { if (now < _burstUntil) { return true; } if (now < _next) { return false; } _next = now + _steadyInterval; return true; } } public sealed class ResolvedAppearance { public AppearanceState State; public BodyPlan Plan; } public sealed class RemoteAppearanceCache<TKey> { private sealed class Entry { public string Body; public string Eyes; public string Tattoo; public string Shape; public BodyExposure Exposure; public bool EyesEnabled; public bool TattooEnabled; public bool ShapeEnabled; public ResolvedAppearance Resolved; } private readonly Dictionary<TKey, Entry> _entries = new Dictionary<TKey, Entry>(); private readonly BodyOptions _options; public int Count => _entries.Count; public RemoteAppearanceCache(BodyOptions options) { _options = options ?? new BodyOptions(); } public ResolvedAppearance Resolve(TKey key, string body, string eyes, BodyExposure exposure, bool eyesEnabled, out bool changed) { return Resolve(key, body, eyes, null, null, exposure, eyesEnabled, tattooEnabled: false, shapeEnabled: false, out changed); } public ResolvedAppearance Resolve(TKey key, string body, string eyes, string tattoo, BodyExposure exposure, bool eyesEnabled, bool tattooEnabled, out bool changed) { return Resolve(key, body, eyes, tattoo, null, exposure, eyesEnabled, tattooEnabled, shapeEnabled: false, out changed); } public ResolvedAppearance Resolve(TKey key, string body, string eyes, string tattoo, string shape, BodyExposure exposure, bool eyesEnabled, bool tattooEnabled, bool shapeEnabled, out bool changed) { body = body ?? ""; eyes = eyes ?? ""; tattoo = tattoo ?? ""; shape = shape ?? ""; if (_entries.TryGetValue(key, out var value) && value.Exposure == exposure && value.EyesEnabled == eyesEnabled && value.TattooEnabled == tattooEnabled && value.ShapeEnabled == shapeEnabled && string.Equals(value.Body, body, StringComparison.Ordinal) && string.Equals(value.Eyes, eyes, StringComparison.Ordinal) && string.Equals(value.Tattoo, tattoo, StringComparison.Ordinal) && string.Equals(value.Shape, shape, StringComparison.Ordinal)) { changed = false; return value.Resolved; } AppearanceState appearanceState = AppearanceStore.FromStrings(body, eyes, tattoo, shape, exposure, eyesEnabled, tattooEnabled, shapeEnabled); ResolvedAppearance resolvedAppearance = new ResolvedAppearance { State = appearanceState, Plan = (appearanceState.Body.IsNeutral ? null : BodyCompose.Compute(appearanceState.Body, _options)) }; _entries[key] = new Entry { Body = body, Eyes = eyes, Tattoo = tattoo, Shape = shape, Exposure = exposure, EyesEnabled = eyesEnabled, TattooEnabled = tattooEnabled, ShapeEnabled = shapeEnabled, Resolved = resolvedAppearance }; changed = true; return resolvedAppearance; } public void Remove(TKey key) { _entries.Remove(key); } public int Prune(Func<TKey, bool> isAlive) { List<TKey> list = null; foreach (TKey key in _entries.Keys) { if (!isAlive(key)) { if (list == null) { list = new List<TKey>(); } list.Add(key); } } if (list == null) { return 0; }