Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of Personnel v1.0.0
Personnel.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using Il2CppScheduleOne.AvatarFramework; using Il2CppSystem.Collections.Generic; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Personnel; using Personnel.Appearance; using Personnel.Config; using Personnel.Content; using Personnel.Model; using Personnel.Registration; using Personnel.Util; using S1API.Entities; using S1API.Rendering; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "Personnel", "1.0.0", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-Personnel")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonOptionalDependencies(new string[] { "ModManager&PhoneApp" })] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("Personnel")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+7b88287a9d7bee54a28488151a34a6d25416a250")] [assembly: AssemblyProduct("Personnel")] [assembly: AssemblyTitle("Personnel")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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; } } [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 Personnel { public static class API { public static IReadOnlyList<NpcDef> All => NpcRegistry.AllDefs; public static event Action OnReloaded { add { NpcRegistry.OnReloaded += value; } remove { NpcRegistry.OnReloaded -= value; } } public static bool TryGet(string id, out NpcDef def) { return NpcRegistry.TryGet(id, out def); } public static bool Register(NpcDef def) { return NpcRegistry.Add(def); } public static AvatarSettings BuildAvatarSettings(NpcDef def) { return AvatarSettingsFactory.BuildAvatarSettings(def); } public static bool ApplyAppearance(Avatar avatar, NpcDef def) { if ((Object)(object)avatar == (Object)null || def == null) { return false; } AvatarSettings val = BuildAvatarSettings(def); if ((Object)(object)val == (Object)null) { return false; } avatar.LoadAvatarSettings(val); return true; } public static void Reload() { NpcRegistry.Reload(); } public static void ConfigureFromDef(NPCPrefabBuilder builder, NpcDef def) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) if (builder != null && def != null) { SplitName(def.DisplayName, out var first, out var last); builder.WithIdentity(def.Id, first, last); builder.WithAppearanceDefaults((Action<AvatarDefaultsBuilder>)delegate(AvatarDefaultsBuilder ab) { AvatarSettingsFactory.ApplyToDefaults(ab, def); }); NpcSpawn spawn = def.Spawn; if (spawn != null && spawn.Position.HasValue) { builder.WithSpawnPosition(def.Spawn.Position.Value, Quaternion.identity); } } } private static void SplitName(string display, out string first, out string last) { first = "NPC"; last = string.Empty; if (!string.IsNullOrWhiteSpace(display)) { display = display.Trim(); int num = display.IndexOf(' '); if (num > 0) { first = display.Substring(0, num); last = display.Substring(num + 1).Trim(); } else { first = display; } } } } public sealed class Core : MelonMod { public static Core Instance { get; private set; } public static Instance Log { get; private set; } public override void OnInitializeMelon() { Instance = this; Log = ((MelonBase)this).LoggerInstance; Preferences.Initialize(); ExamplePack.ExtractIfEnabled(); int value = NpcRegistry.LoadPacks(); Log.Msg($"Personnel {((MelonBase)this).Info.Version} - {value} NPC def(s) from packs ({NpcRegistry.AllDefs.Count} total)."); Log.Msg("Drop packs in: " + PackLoader.PacksRoot); } } public abstract class PersonnelNpc : NPC { protected abstract string DefId { get; } protected override void ConfigurePrefab(NPCPrefabBuilder builder) { if (API.TryGet(DefId, out var def) && def != null) { API.ConfigureFromDef(builder, def); return; } Instance log = Core.Log; if (log != null) { log.Warning("[Personnel] PersonnelNpc: no definition '" + DefId + "' found - is the pack installed?"); } } } } namespace Personnel.Util { public static class ColorParse { public static bool TryParse(string s, out Color color) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) color = Color.white; if (string.IsNullOrWhiteSpace(s)) { return false; } s = s.Trim(); if (s.StartsWith("#")) { s = s.Substring(1); } if (s.Length != 6 && s.Length != 8) { return false; } try { byte b = Convert.ToByte(s.Substring(0, 2), 16); byte b2 = Convert.ToByte(s.Substring(2, 2), 16); byte b3 = Convert.ToByte(s.Substring(4, 2), 16); byte b4 = ((s.Length == 8) ? Convert.ToByte(s.Substring(6, 2), 16) : byte.MaxValue); color = Color32.op_Implicit(new Color32(b, b2, b3, b4)); return true; } catch { return false; } } public static Color Parse(string s, Color fallback) { //IL_000c: 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) if (!TryParse(s, out var color)) { return fallback; } return color; } } public static class Ids { public static string Normalize(string s) { if (string.IsNullOrWhiteSpace(s)) { return ""; } StringBuilder stringBuilder = new StringBuilder(s.Length); bool flag = false; string text = s.Trim().ToLowerInvariant(); foreach (char c in text) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(c); flag = false; } else if (!flag) { stringBuilder.Append('_'); flag = true; } } return stringBuilder.ToString().Trim('_'); } public static string Make(string pack, string name) { string text = Normalize(pack); string text2 = Normalize(name); if (string.IsNullOrEmpty(text)) { return text2; } if (string.IsNullOrEmpty(text2)) { return text; } return text + "_" + text2; } } } namespace Personnel.Registration { internal static class CustomLayerRegistry { private static readonly Dictionary<string, string> _resolved = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static string SourceLayer(bool face) { if (!face) { return "Avatar/Layers/Tattoos/chest/Chest_Bird"; } return "Avatar/Layers/Tattoos/face/Face_Teardrop"; } public static string EnsureLayer(string npcSource, string npcId, string packDir, string file, Texture2D tex, bool face) { string text = ((!string.IsNullOrEmpty(file)) ? Path.GetFileNameWithoutExtension(file) : "tex"); string text2 = (face ? "Face" : "body"); string text3 = "Avatar/Layers/Tattoos/personnel/" + text2 + "/" + Sanitize(npcSource) + "_" + Sanitize(npcId) + "_" + Sanitize(text); if (_resolved.TryGetValue(text3, out var value)) { return value; } try { if ((Object)(object)tex == (Object)null) { if (string.IsNullOrWhiteSpace(file)) { Instance log = Core.Log; if (log != null) { log.Warning($"Custom layer for '{npcSource}/{npcId}': no texture or file."); } return null; } string text4 = (Path.IsPathRooted(file) ? file : Path.Combine(packDir ?? "", file)); if (!File.Exists(text4)) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning($"Custom layer for '{npcSource}/{npcId}': PNG not found at '{text4}'."); } return null; } tex = TextureUtils.LoadTextureFromFile(text4, (FilterMode)1, (TextureWrapMode)1); if ((Object)(object)tex == (Object)null) { Instance log3 = Core.Log; if (log3 != null) { log3.Warning($"Custom layer for '{npcSource}/{npcId}': failed to load '{text4}'."); } return null; } } if (!AvatarLayerFactory.CreateAndRegisterAvatarLayer(SourceLayer(face), text3, npcId ?? text, tex)) { Instance log4 = Core.Log; if (log4 != null) { log4.Warning($"Custom layer for '{npcSource}/{npcId}': CreateAndRegisterAvatarLayer failed."); } return null; } _resolved[text3] = text3; Instance log5 = Core.Log; if (log5 != null) { log5.Msg($"Registered custom layer '{npcSource}/{npcId}' ({text}) -> {text3}"); } return text3; } catch (Exception ex) { Instance log6 = Core.Log; if (log6 != null) { log6.Warning($"Custom layer for '{npcSource}/{npcId}': registration error - {ex.Message}"); } return null; } } private static string Sanitize(string s) { if (string.IsNullOrEmpty(s)) { return "x"; } StringBuilder stringBuilder = new StringBuilder(s.Length); foreach (char c in s) { stringBuilder.Append((char.IsLetterOrDigit(c) || c == '-' || c == '_') ? c : '_'); } return stringBuilder.ToString(); } } internal static class NpcRegistry { private static readonly List<NpcDef> _all = new List<NpcDef>(); private static readonly HashSet<string> _keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public static IReadOnlyList<NpcDef> AllDefs => _all; public static event Action OnReloaded; public static bool Add(NpcDef def) { if (def == null || string.IsNullOrWhiteSpace(def.Id)) { return false; } if (string.IsNullOrWhiteSpace(def.Source)) { def.Source = "API"; } if (!_keys.Add(def.Key)) { return false; } _all.Add(def); return true; } public static int AddRange(IEnumerable<NpcDef> defs) { int num = 0; if (defs == null) { return 0; } foreach (NpcDef def in defs) { if (Add(def)) { num++; } } return num; } public static bool TryGet(string id, out NpcDef def) { def = null; if (string.IsNullOrWhiteSpace(id)) { return false; } foreach (NpcDef item in _all) { if (string.Equals(item.Id, id, StringComparison.OrdinalIgnoreCase)) { def = item; return true; } } return false; } public static int LoadPacks() { return AddRange(PackLoader.LoadAll()); } public static void Reload() { for (int num = _all.Count - 1; num >= 0; num--) { if (_all[num].PackDir != null) { _keys.Remove(_all[num].Key); _all.RemoveAt(num); } } LoadPacks(); try { NpcRegistry.OnReloaded?.Invoke(); } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("OnReloaded handler threw: " + ex.Message); } } } } } namespace Personnel.Model { public sealed class NpcDef { public string Id; public string DisplayName; public string Source; public string PackDir; public NpcAppearance Appearance = new NpcAppearance(); public NpcBehavior Behavior; public NpcSpawn Spawn; public IReadOnlyDictionary<string, string> Extensions = new Dictionary<string, string>(); public string Key => (Source ?? "?") + "/" + (Id ?? "?"); } public sealed class NpcAppearance { public float Gender; public float Height = 0.98f; public float Weight = 0.4f; public Color SkinColor = Color32.op_Implicit(new Color32((byte)150, (byte)120, (byte)95, byte.MaxValue)); public string HairPath = ""; public Color HairColor = Color.black; public float EyebrowScale = 1f; public float EyebrowThickness = 1f; public float EyebrowRestingHeight; public float EyebrowRestingAngle; public Color LeftEyeLidColor = Color32.op_Implicit(new Color32((byte)150, (byte)120, (byte)95, byte.MaxValue)); public Color RightEyeLidColor = Color32.op_Implicit(new Color32((byte)150, (byte)120, (byte)95, byte.MaxValue)); public float LeftEyeTop = 0.5f; public float LeftEyeBottom = 0.5f; public float RightEyeTop = 0.5f; public float RightEyeBottom = 0.5f; public string EyeballMaterial = "Default"; public Color EyeBallTint = Color.white; public float PupilDilation = 1f; public List<NpcLayer> FaceLayers = new List<NpcLayer>(); public List<NpcLayer> BodyLayers = new List<NpcLayer>(); public List<NpcLayer> Accessories = new List<NpcLayer>(); } public sealed class NpcLayer { public string Path; public string File; public Texture2D Texture; public Color Tint = Color.white; } public sealed class NpcBehavior { public float Aggression; public float MaxHealth = 100f; public float Scale = 1f; public string Conversation = "none"; } public sealed class NpcSpawn { public Vector3? Position; public string Region = ""; } } namespace Personnel.Content { internal static class ExamplePack { private const string ExampleManifestJson = "{\n \"name\": \"Personnel Examples\",\n \"author\": \"DooDesch\",\n \"npcs\": [\n {\n \"name\": \"Pale\",\n \"appearance\": {\n \"gender\": 0.5,\n \"height\": 1.0,\n \"weight\": 0.4,\n \"skinColor\": \"#8899AA\",\n \"hairPath\": \"\",\n \"hairColor\": \"#101014\",\n \"eyeBallTint\": \"#FFFFFF\",\n \"pupilDilation\": 0.8\n },\n \"extensions\": {\n \"backrooms\": {\n \"archetype\": \"faceling\",\n \"tierMin\": 1, \"tierMax\": 5,\n \"biomes\": [\"L0\", \"L1\"],\n \"weight\": 14, \"maxAlive\": 3, \"hostile\": false\n }\n }\n },\n {\n \"name\": \"Ashen\",\n \"appearance\": {\n \"gender\": 0.2,\n \"height\": 1.1,\n \"weight\": 0.6,\n \"skinColor\": \"#4A4A50\",\n \"hairColor\": \"#000000\",\n \"eyeBallTint\": \"#FFCC66\"\n },\n \"extensions\": {\n \"backrooms\": {\n \"archetype\": \"wanderer_hollow\",\n \"tierMin\": 3, \"tierMax\": 5,\n \"weight\": 10, \"maxAlive\": 1, \"hostile\": true\n }\n }\n }\n ]\n}\n"; public static void ExtractIfEnabled() { if (!Preferences.LoadExamplePack) { return; } string text = Path.Combine(PackLoader.PacksRoot, "Examples"); try { string path = Path.Combine(text, "manifest.json"); if (File.Exists(path)) { Instance log = Core.Log; if (log != null) { log.Msg("Example pack already present - leaving it untouched."); } return; } Directory.CreateDirectory(text); File.WriteAllText(path, "{\n \"name\": \"Personnel Examples\",\n \"author\": \"DooDesch\",\n \"npcs\": [\n {\n \"name\": \"Pale\",\n \"appearance\": {\n \"gender\": 0.5,\n \"height\": 1.0,\n \"weight\": 0.4,\n \"skinColor\": \"#8899AA\",\n \"hairPath\": \"\",\n \"hairColor\": \"#101014\",\n \"eyeBallTint\": \"#FFFFFF\",\n \"pupilDilation\": 0.8\n },\n \"extensions\": {\n \"backrooms\": {\n \"archetype\": \"faceling\",\n \"tierMin\": 1, \"tierMax\": 5,\n \"biomes\": [\"L0\", \"L1\"],\n \"weight\": 14, \"maxAlive\": 3, \"hostile\": false\n }\n }\n },\n {\n \"name\": \"Ashen\",\n \"appearance\": {\n \"gender\": 0.2,\n \"height\": 1.1,\n \"weight\": 0.6,\n \"skinColor\": \"#4A4A50\",\n \"hairColor\": \"#000000\",\n \"eyeBallTint\": \"#FFCC66\"\n },\n \"extensions\": {\n \"backrooms\": {\n \"archetype\": \"wanderer_hollow\",\n \"tierMin\": 3, \"tierMax\": 5,\n \"weight\": 10, \"maxAlive\": 1, \"hostile\": true\n }\n }\n }\n ]\n}\n"); Instance log2 = Core.Log; if (log2 != null) { log2.Msg("Wrote example NPC pack -> " + text); } } catch (Exception ex) { Instance log3 = Core.Log; if (log3 != null) { log3.Warning("Example pack write failed: " + ex.Message); } } } } public sealed class NpcPackManifest { public string name; public string author; public List<NpcEntry> npcs; } public sealed class NpcEntry { public string id; public string name; public AppearanceJson appearance; public BehaviorJson behavior; public SpawnJson spawn; public JObject extensions; } public sealed class AppearanceJson { public float? gender; public float? height; public float? weight; public string skinColor; public string hairPath; public string hairColor; public float? eyebrowScale; public float? eyebrowThickness; public float? eyebrowRestingHeight; public float? eyebrowRestingAngle; public string leftEyeLidColor; public string rightEyeLidColor; public EyeJson leftEye; public EyeJson rightEye; public string eyeballMaterial; public string eyeBallTint; public float? pupilDilation; public List<LayerJson> faceLayers; public List<LayerJson> bodyLayers; public List<LayerJson> accessories; } public sealed class EyeJson { public float? top; public float? bottom; } public sealed class LayerJson { public string path; public string file; public string kind; public string tint; public string color; } public sealed class BehaviorJson { public float? aggression; public float? maxHealth; public float? scale; public string conversation; } public sealed class SpawnJson { public float? x; public float? y; public float? z; public string region; } internal static class PackLoader { public static string PacksRoot => Path.Combine(MelonEnvironment.UserDataDirectory, "Personnel", "Packs"); public static List<NpcDef> LoadAll() { List<NpcDef> list = new List<NpcDef>(); string packsRoot = PacksRoot; try { Directory.CreateDirectory(packsRoot); WriteReadmeIfMissing(packsRoot); } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("Could not prepare packs folder '" + packsRoot + "': " + ex.Message); } return list; } string[] directories = Directory.GetDirectories(packsRoot); foreach (string text in directories) { string path = Path.Combine(text, "manifest.json"); if (!File.Exists(path)) { continue; } string name = new DirectoryInfo(text).Name; try { NpcPackManifest npcPackManifest = JsonConvert.DeserializeObject<NpcPackManifest>(File.ReadAllText(path)); if (npcPackManifest?.npcs == null) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning("Pack '" + name + "': manifest has no 'npcs' array - skipped."); } continue; } int num = 0; List<string> list2 = new List<string>(); foreach (NpcEntry npc in npcPackManifest.npcs) { NpcDef npcDef = ToDef(name, text, npc); if (npcDef != null) { list.Add(npcDef); num++; list2.Add(npcDef.Id); } } Instance log3 = Core.Log; if (log3 != null) { log3.Msg($"Pack '{name}' ({npcPackManifest.name ?? "unnamed"}): {num} NPC(s) [{string.Join(", ", list2)}]."); } } catch (Exception ex2) { Instance log4 = Core.Log; if (log4 != null) { log4.Warning("Pack '" + name + "': failed to read manifest.json - " + ex2.Message); } } } return list; } private static NpcDef ToDef(string packName, string packDir, NpcEntry e) { if (e == null) { return null; } string text = ((!string.IsNullOrWhiteSpace(e.name)) ? e.name : e.id); if (string.IsNullOrWhiteSpace(text)) { Instance log = Core.Log; if (log != null) { log.Warning("Pack '" + packName + "': an NPC entry has no 'name' (or 'id') - skipped."); } return null; } return new NpcDef { Id = Ids.Make(packName, text), DisplayName = text, Source = packName, PackDir = packDir, Appearance = BuildAppearance(e.appearance), Behavior = BuildBehavior(e.behavior), Spawn = BuildSpawn(e.spawn), Extensions = BuildExtensions(e.extensions) }; } private static NpcAppearance BuildAppearance(AppearanceJson a) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: 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_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) NpcAppearance npcAppearance = new NpcAppearance(); if (a == null) { return npcAppearance; } if (a.gender.HasValue) { npcAppearance.Gender = a.gender.Value; } if (a.height.HasValue) { npcAppearance.Height = a.height.Value; } if (a.weight.HasValue) { npcAppearance.Weight = a.weight.Value; } npcAppearance.SkinColor = ColorParse.Parse(a.skinColor, npcAppearance.SkinColor); if (a.hairPath != null) { npcAppearance.HairPath = a.hairPath; } npcAppearance.HairColor = ColorParse.Parse(a.hairColor, npcAppearance.HairColor); if (a.eyebrowScale.HasValue) { npcAppearance.EyebrowScale = a.eyebrowScale.Value; } if (a.eyebrowThickness.HasValue) { npcAppearance.EyebrowThickness = a.eyebrowThickness.Value; } if (a.eyebrowRestingHeight.HasValue) { npcAppearance.EyebrowRestingHeight = a.eyebrowRestingHeight.Value; } if (a.eyebrowRestingAngle.HasValue) { npcAppearance.EyebrowRestingAngle = a.eyebrowRestingAngle.Value; } npcAppearance.LeftEyeLidColor = ColorParse.Parse(a.leftEyeLidColor, npcAppearance.LeftEyeLidColor); npcAppearance.RightEyeLidColor = ColorParse.Parse(a.rightEyeLidColor, npcAppearance.RightEyeLidColor); if (a.leftEye != null) { if (a.leftEye.top.HasValue) { npcAppearance.LeftEyeTop = a.leftEye.top.Value; } if (a.leftEye.bottom.HasValue) { npcAppearance.LeftEyeBottom = a.leftEye.bottom.Value; } } if (a.rightEye != null) { if (a.rightEye.top.HasValue) { npcAppearance.RightEyeTop = a.rightEye.top.Value; } if (a.rightEye.bottom.HasValue) { npcAppearance.RightEyeBottom = a.rightEye.bottom.Value; } } if (!string.IsNullOrWhiteSpace(a.eyeballMaterial)) { npcAppearance.EyeballMaterial = a.eyeballMaterial; } npcAppearance.EyeBallTint = ColorParse.Parse(a.eyeBallTint, npcAppearance.EyeBallTint); if (a.pupilDilation.HasValue) { npcAppearance.PupilDilation = a.pupilDilation.Value; } AppendLayers(a.faceLayers, npcAppearance.FaceLayers); AppendLayers(a.bodyLayers, npcAppearance.BodyLayers); AppendLayers(a.accessories, npcAppearance.Accessories); return npcAppearance; } private static void AppendLayers(List<LayerJson> src, List<NpcLayer> dst) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) if (src == null) { return; } foreach (LayerJson item in src) { if (item != null && (!string.IsNullOrWhiteSpace(item.path) || !string.IsNullOrWhiteSpace(item.file))) { dst.Add(new NpcLayer { Path = item.path, File = item.file, Tint = ColorParse.Parse(item.tint ?? item.color, Color.white) }); } } } private static NpcBehavior BuildBehavior(BehaviorJson b) { if (b == null) { return null; } NpcBehavior npcBehavior = new NpcBehavior(); if (b.aggression.HasValue) { npcBehavior.Aggression = b.aggression.Value; } if (b.maxHealth.HasValue) { npcBehavior.MaxHealth = b.maxHealth.Value; } if (b.scale.HasValue) { npcBehavior.Scale = b.scale.Value; } if (!string.IsNullOrWhiteSpace(b.conversation)) { npcBehavior.Conversation = b.conversation; } return npcBehavior; } private static NpcSpawn BuildSpawn(SpawnJson s) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) if (s == null) { return null; } NpcSpawn npcSpawn = new NpcSpawn { Region = (s.region ?? "") }; if (s.x.HasValue && s.y.HasValue && s.z.HasValue) { npcSpawn.Position = new Vector3(s.x.Value, s.y.Value, s.z.Value); } return npcSpawn; } private static IReadOnlyDictionary<string, string> BuildExtensions(JObject ext) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if (ext == null) { return dictionary; } foreach (JProperty item in ext.Properties()) { try { dictionary[item.Name] = item.Value.ToString((Formatting)0); } catch { } } return dictionary; } private static void WriteReadmeIfMissing(string root) { string path = Path.Combine(root, "README.txt"); if (!File.Exists(path)) { File.WriteAllText(path, "Personnel - custom NPC packs\n============================\n\nDrop one folder per pack in this directory. Each pack needs a manifest.json and (optionally) the PNG files\nits custom layers reference. Build packs visually with the \"Personify\" editor (Side Hustle gamemode).\n\nFolder layout:\n Packs/\n MyPack/\n manifest.json\n grin.png (optional custom layer texture)\n\nmanifest.json:\n{\n \"name\": \"My Pack\",\n \"author\": \"you\",\n \"npcs\": [\n {\n \"id\": \"faceling_pale\",\n \"name\": \"Pale Faceling\",\n \"appearance\": {\n \"gender\": 0.5, \"height\": 1.0, \"weight\": 0.4, \"skinColor\": \"#8899AA\",\n \"hairPath\": \"\", \"hairColor\": \"#000000\",\n \"eyeBallTint\": \"#FFFFFF\", \"pupilDilation\": 1,\n \"faceLayers\": [ { \"file\": \"grin.png\", \"kind\": \"face\", \"tint\": \"#FFFFFF\" } ],\n \"bodyLayers\": [ { \"path\": \"Avatar/Layers/Body/...\", \"tint\": \"#334455\" } ],\n \"accessories\": [ ]\n },\n \"extensions\": {\n \"backrooms\": { \"archetype\": \"faceling\", \"tierMin\": 1, \"tierMax\": 5,\n \"biomes\": [\"L0\",\"L1\"], \"weight\": 14, \"maxAlive\": 3, \"hostile\": false }\n }\n }\n ]\n}\n\nLayers: use \"path\" to reference an existing in-game layer, OR \"file\" (a PNG next to manifest.json)\n with \"kind\": face|body for a custom layer. Custom PNGs must match the game's body/face UV.\nextensions: free-form per-consumer data. The Backrooms mod reads the \"backrooms\" block; other mods\n ignore it.\n"); } } } } namespace Personnel.Config { internal static class Preferences { private const string CategoryId = "Personnel_01_Main"; private static MelonPreferences_Category _category; private static MelonPreferences_Entry<bool> _loadExamplePack; internal static bool LoadExamplePack => _loadExamplePack?.Value ?? false; internal static void Initialize() { if (_category == null) { _category = MelonPreferences.CreateCategory("Personnel_01_Main", "Personnel (Custom NPCs)"); _loadExamplePack = _category.CreateEntry<bool>("LoadExamplePack", false, "Load example NPC pack", "OFF by default. When ON, Personnel drops a small example pack into UserData/Personnel/Packs/Examples on startup (if not already there) so you get a working manifest template to copy for your own pack. Requires a game restart.", false, false, (ValueValidator)null, (string)null); } } } } namespace Personnel.Appearance { public static class AvatarSettingsFactory { public static AvatarSettings BuildAvatarSettings(NpcDef def) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) if (def == null) { return null; } NpcAppearance npcAppearance = def.Appearance ?? new NpcAppearance(); AvatarSettings obj = ScriptableObject.CreateInstance<AvatarSettings>(); ((Object)obj).hideFlags = (HideFlags)32; obj.Gender = npcAppearance.Gender; obj.Height = npcAppearance.Height; obj.Weight = npcAppearance.Weight; obj.SkinColor = npcAppearance.SkinColor; obj.HairPath = npcAppearance.HairPath ?? string.Empty; obj.HairColor = npcAppearance.HairColor; obj.EyebrowScale = npcAppearance.EyebrowScale; obj.EyebrowThickness = npcAppearance.EyebrowThickness; obj.EyebrowRestingHeight = npcAppearance.EyebrowRestingHeight; obj.EyebrowRestingAngle = npcAppearance.EyebrowRestingAngle; obj.LeftEyeLidColor = npcAppearance.LeftEyeLidColor; obj.RightEyeLidColor = npcAppearance.RightEyeLidColor; obj.LeftEyeRestingState = new EyeLidConfiguration { topLidOpen = npcAppearance.LeftEyeTop, bottomLidOpen = npcAppearance.LeftEyeBottom }; obj.RightEyeRestingState = new EyeLidConfiguration { topLidOpen = npcAppearance.RightEyeTop, bottomLidOpen = npcAppearance.RightEyeBottom }; obj.EyeballMaterialIdentifier = (string.IsNullOrEmpty(npcAppearance.EyeballMaterial) ? "Default" : npcAppearance.EyeballMaterial); obj.EyeBallTint = npcAppearance.EyeBallTint; obj.PupilDilation = npcAppearance.PupilDilation; List<LayerSetting> val = new List<LayerSetting>(); List<LayerSetting> val2 = new List<LayerSetting>(); List<AccessorySetting> val3 = new List<AccessorySetting>(); AddLayers(def, npcAppearance.FaceLayers, val, face: true); AddLayers(def, npcAppearance.BodyLayers, val2, face: false); AddAccessories(npcAppearance.Accessories, val3); obj.FaceLayerSettings = val; obj.BodyLayerSettings = val2; obj.AccessorySettings = val3; return obj; } private static void AddLayers(NpcDef def, List<NpcLayer> layers, List<LayerSetting> dst, bool face) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (layers == null) { return; } foreach (NpcLayer layer in layers) { if (layer != null) { string text = ResolvePath(def, layer, face); if (!string.IsNullOrEmpty(text)) { LayerSetting val = new LayerSetting(); val.layerPath = text; val.layerTint = layer.Tint; dst.Add(val); } } } } private static void AddAccessories(List<NpcLayer> layers, List<AccessorySetting> dst) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (layers == null) { return; } foreach (NpcLayer layer in layers) { if (layer != null && !string.IsNullOrWhiteSpace(layer.Path)) { AccessorySetting val = new AccessorySetting(); val.path = layer.Path; val.color = layer.Tint; dst.Add(val); } } } public static void ApplyToDefaults(AvatarDefaultsBuilder ab, NpcDef def) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_009f: 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_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) if (ab == null || def == null) { return; } NpcAppearance npcAppearance = def.Appearance ?? new NpcAppearance(); ab.Gender = npcAppearance.Gender; ab.Height = npcAppearance.Height; ab.Weight = npcAppearance.Weight; ab.SkinColor = Color32.op_Implicit(npcAppearance.SkinColor); ab.HairPath = npcAppearance.HairPath ?? string.Empty; ab.HairColor = npcAppearance.HairColor; ab.EyebrowScale = npcAppearance.EyebrowScale; ab.EyebrowThickness = npcAppearance.EyebrowThickness; ab.EyebrowRestingHeight = npcAppearance.EyebrowRestingHeight; ab.EyebrowRestingAngle = npcAppearance.EyebrowRestingAngle; ab.LeftEyeLidColor = npcAppearance.LeftEyeLidColor; ab.RightEyeLidColor = npcAppearance.RightEyeLidColor; ab.LeftEye = (npcAppearance.LeftEyeTop, npcAppearance.LeftEyeBottom); ab.RightEye = (npcAppearance.RightEyeTop, npcAppearance.RightEyeBottom); ab.EyeballMaterialIdentifier = (string.IsNullOrEmpty(npcAppearance.EyeballMaterial) ? "Default" : npcAppearance.EyeballMaterial); ab.EyeBallTint = npcAppearance.EyeBallTint; ab.PupilDilation = npcAppearance.PupilDilation; foreach (NpcLayer faceLayer in npcAppearance.FaceLayers) { string text = ResolvePath(def, faceLayer, face: true); if (!string.IsNullOrEmpty(text)) { ab.WithFaceLayer(text, faceLayer.Tint); } } foreach (NpcLayer bodyLayer in npcAppearance.BodyLayers) { string text2 = ResolvePath(def, bodyLayer, face: false); if (!string.IsNullOrEmpty(text2)) { ab.WithBodyLayer(text2, bodyLayer.Tint); } } foreach (NpcLayer accessory in npcAppearance.Accessories) { if (accessory != null && !string.IsNullOrWhiteSpace(accessory.Path)) { ab.WithAccessoryLayer(accessory.Path, accessory.Tint); } } } internal static string ResolvePath(NpcDef def, NpcLayer l, bool face) { if ((Object)(object)l.Texture != (Object)null) { return CustomLayerRegistry.EnsureLayer(def.Source, def.Id, def.PackDir, null, l.Texture, face); } if (!string.IsNullOrWhiteSpace(l.File)) { return CustomLayerRegistry.EnsureLayer(def.Source, def.Id, def.PackDir, l.File, null, face); } return l.Path; } } }