Editor/SceneAdapter.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Editor;
using Sandbox;
namespace TeamCreate;
public static class SceneAdapter
{
public static string Root => Project.Current.RootDirectory.FullName;
public static string PathOf(SceneEditorSession session)
{
return session?.Scene?.Source?.ResourcePath?.Replace('\\', '/').ToLowerInvariant();
}
public static DateTime DiskStamp(string path)
{
return File.GetLastWriteTimeUtc(Path.Combine(Root, "Assets", path));
}
public static IEnumerable<GameObject> Walk(GameObject parent)
{
GameObject[] array = parent.Children.ToArray();
foreach (GameObject child in array)
{
if (!child.IsValid() || child.Flags.HasFlag(GameObjectFlags.NotSaved))
{
continue;
}
yield return child;
foreach (GameObject item in Walk(child))
{
yield return item;
}
}
}
public static Dictionary<string, ObjectState> Capture(Scene scene)
{
Dictionary<string, ObjectState> result = new Dictionary<string, ObjectState>();
using (scene.Push())
{
GameObject[] array = scene.Children.ToArray();
foreach (GameObject gameObject in array)
{
if (gameObject.IsValid())
{
JsonObject jsonObject = gameObject.Serialize();
if (jsonObject != null)
{
Flatten(JsonNode.Parse(jsonObject.ToJsonString()).AsObject(), "");
}
}
}
return result;
}
void Flatten(JsonObject data, string parent)
{
string value = data["__guid"].GetValue<string>();
JsonArray jsonArray = data["Children"] as JsonArray;
data.Remove("Children");
result.Add(value, new ObjectState(value, parent, data));
if (jsonArray != null)
{
foreach (JsonNode item in jsonArray)
{
Flatten(item.AsObject(), value);
}
}
}
}
public static void Apply(SceneEditorSession session, Dictionary<string, ObjectState> target)
{
using (session.Scene.Push())
{
Dictionary<string, GameObject> dictionary = Walk(session.Scene).ToDictionary((GameObject x) => x.Id.ToString());
Dictionary<string, ObjectState> before = Capture(session.Scene);
List<Change> list = StateRules.Diff(before, target);
foreach (Change item in list)
{
StateRules.ValidateChange(item);
}
StateRules.ValidateGraph(target);
foreach (Change item2 in list.Where((Change x) => (object)x.Before == null))
{
GameObject gameObject = session.Scene.CreateObject(enabled: false);
gameObject.Deserialize((JsonObject)item2.After.Data.DeepClone());
if (gameObject.Id.ToString() != item2.Id)
{
throw new InvalidOperationException("Engine did not preserve object GUID");
}
dictionary.Add(item2.Id, gameObject);
}
foreach (ObjectState value2 in target.Values)
{
if (dictionary.TryGetValue(value2.Id, out var value))
{
GameObject gameObject2 = (string.IsNullOrEmpty(value2.Parent) ? session.Scene : dictionary[value2.Parent]);
if (value.Parent != gameObject2)
{
value.SetParent(gameObject2, keepWorldPosition: false);
}
}
}
foreach (Change item3 in list.Where((Change x) => (object)x.After != null))
{
GameObject gameObject3 = dictionary[item3.Id];
JsonObject data = item3.After.Data;
gameObject3.Name = data["Name"].GetValue<string>();
gameObject3.LocalPosition = data["Position"].Deserialize<Vector3>();
gameObject3.LocalRotation = data["Rotation"].Deserialize<Rotation>();
gameObject3.LocalScale = data["Scale"].Deserialize<Vector3>();
gameObject3.Enabled = data["Enabled"].GetValue<bool>();
gameObject3.Tags.RemoveAll();
gameObject3.Tags.Add(data["Tags"].GetValue<string>().Split(',', StringSplitOptions.RemoveEmptyEntries));
if ((object)item3.Before == null || JsonNode.DeepEquals(item3.Before.Data["Components"], data["Components"]))
{
continue;
}
foreach (JsonNode item4 in data["Components"].AsArray())
{
string id = item4["__guid"].GetValue<string>();
Component component = gameObject3.Components.GetAll().Single((Component x) => x.Id.ToString() == id);
component.DeserializeImmediately((JsonObject)item4.DeepClone());
}
}
foreach (Change item5 in list.Where((Change x) => (object)x.After == null))
{
if (dictionary[item5.Id].IsValid())
{
dictionary[item5.Id].Destroy();
}
}
session.HasUnsavedChanges |= list.Count > 0;
}
}
public static void ValidateSiblingOrder(Dictionary<string, ObjectState> before, Dictionary<string, ObjectState> after)
{
foreach (string parent in before.Values.Select((ObjectState x) => x.Parent).Distinct())
{
string[] ids = (from x in before.Values
where x.Parent == parent && after.TryGetValue(x.Id, out var value) && value.Parent == parent
select x.Id).ToArray();
if (!ids.SequenceEqual(from x in after.Values
where x.Parent == parent && ids.Contains(x.Id)
select x.Id))
{
throw new InvalidOperationException("Sibling reordering is not supported; local order retained");
}
}
}
public static string Baseline(bool includeScenes = true)
{
List<string> list = new List<string> { "teamcreate-1" };
Assembly[] array = new Assembly[2]
{
typeof(Scene).Assembly,
typeof(Widget).Assembly
};
foreach (Assembly assembly in array)
{
list.Add(assembly.ManifestModule.ModuleVersionId.ToString());
}
string[] array2 = new string[5] { "Assets", "Code", "Editor", "Libraries", "ProjectSettings" };
foreach (string path in array2)
{
string path2 = Path.Combine(Root, path);
if (!Directory.Exists(path2))
{
continue;
}
foreach (string item in Directory.EnumerateFiles(path2, "*", SearchOption.AllDirectories).OrderBy((string x) => x, StringComparer.Ordinal))
{
string text = Path.GetRelativePath(Root, item).Replace('\\', '/');
if (!text.Split('/').Any((string x) =>
{
switch (x)
{
case "obj":
case "bin":
case "Properties":
case ".sbox":
return true;
default:
return false;
}
}) && !item.EndsWith(".csproj") && !item.EndsWith("_c") && !item.EndsWith("_d") && !item.Contains(".generated.") && (includeScenes || !item.EndsWith(".scene", StringComparison.OrdinalIgnoreCase)))
{
list.Add(text + ":" + Convert.ToHexString(SHA256.HashData(MetaFileRules.Canonical(item, File.ReadAllBytes(item)))));
}
}
}
JsonNode jsonNode = JsonNode.Parse(File.ReadAllText(Directory.GetFiles(Root, "*.sbproj").Single()));
list.Add(jsonNode.ToJsonString());
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(string.Join("\n", list))));
}
}