implements PLN-0006
This commit is contained in:
parent
da79d61e9f
commit
21cece1977
@ -31,6 +31,15 @@ namespace Editor.GON.StageEditor
|
||||
window.Show();
|
||||
}
|
||||
|
||||
public static void Open(StageDefinition stageDefinition)
|
||||
{
|
||||
var window = GetWindow<StageEditorWindow>();
|
||||
window.titleContent = new GUIContent("Gardens Stage Editor");
|
||||
window.minSize = new Vector2(900, 600);
|
||||
window.Show();
|
||||
window.SetStage(stageDefinition);
|
||||
}
|
||||
|
||||
public void CreateGUI()
|
||||
{
|
||||
state ??= new StageEditorState();
|
||||
|
||||
253
Assets/Scripts/Editor/GON/WorldEditor/StageDefinitionSyncer.cs
Normal file
253
Assets/Scripts/Editor/GON/WorldEditor/StageDefinitionSyncer.cs
Normal file
@ -0,0 +1,253 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Editor.GON.StageEditor;
|
||||
using Intrepid.Gameplay.Journey;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Editor.GON.WorldEditor
|
||||
{
|
||||
public static class StageDefinitionSyncer
|
||||
{
|
||||
public const int ContractVersion = 1;
|
||||
|
||||
public static StageDefinition CreateOrSyncStageDefinition(WorldLayoutSO layout, WorldStageAuthoring stage)
|
||||
{
|
||||
if (layout is null || stage is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
layout.EnsureDefaults();
|
||||
stage.EnsureDefaults();
|
||||
|
||||
var definition = stage.stageDefinition;
|
||||
if (definition is null)
|
||||
{
|
||||
definition = CreateStageDefinitionAsset(layout, stage);
|
||||
if (definition is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
stage.stageDefinition = definition;
|
||||
EditorUtility.SetDirty(layout);
|
||||
}
|
||||
|
||||
SyncStageDefinition(layout, stage, definition);
|
||||
return definition;
|
||||
}
|
||||
|
||||
public static void SyncStageDefinition(WorldLayoutSO layout, WorldStageAuthoring stage, StageDefinition definition)
|
||||
{
|
||||
if (layout is null || stage is null || definition is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Undo.RecordObject(definition, "Sync stage definition world contract");
|
||||
definition.EnsureDefaults();
|
||||
definition.worldSync = BuildSyncData(layout, stage);
|
||||
EditorUtility.SetDirty(definition);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
public static StageWorldSyncData BuildSyncData(WorldLayoutSO layout, WorldStageAuthoring stage)
|
||||
{
|
||||
layout.EnsureDefaults();
|
||||
stage.EnsureDefaults();
|
||||
|
||||
var data = new StageWorldSyncData
|
||||
{
|
||||
worldLayoutId = layout.id,
|
||||
worldStageId = stage.id,
|
||||
worldStageDisplayName = stage.displayName,
|
||||
worldStageKind = stage.stageKind,
|
||||
worldLayer = stage.worldLayer,
|
||||
worldTileSize = layout.worldTileSize,
|
||||
worldLayerHeight = layout.worldLayerHeight,
|
||||
syncedAt = DateTime.UtcNow.ToString("O"),
|
||||
contractVersion = ContractVersion
|
||||
};
|
||||
|
||||
foreach (var tile in stage.occupiedTiles)
|
||||
{
|
||||
data.worldTiles.Add(tile);
|
||||
}
|
||||
|
||||
foreach (var endpoint in GetStageEndpoints(layout, stage.id))
|
||||
{
|
||||
data.expectedEndpoints.Add(BuildEndpoint(layout, endpoint));
|
||||
}
|
||||
|
||||
data.contractHash = ComputeContractHash(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public static string ComputeExpectedContractHash(WorldLayoutSO layout, WorldStageAuthoring stage)
|
||||
{
|
||||
var data = BuildSyncData(layout, stage);
|
||||
return data.contractHash;
|
||||
}
|
||||
|
||||
public static string ComputeContractHash(StageWorldSyncData data)
|
||||
{
|
||||
if (data is null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
data.EnsureDefaults();
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("v=").Append(ContractVersion).Append('\n');
|
||||
builder.Append("layout=").Append(data.worldLayoutId).Append('\n');
|
||||
builder.Append("stage=").Append(data.worldStageId).Append('\n');
|
||||
builder.Append("name=").Append(data.worldStageDisplayName).Append('\n');
|
||||
builder.Append("kind=").Append((int)data.worldStageKind).Append('\n');
|
||||
builder.Append("layer=").Append(data.worldLayer).Append('\n');
|
||||
builder.Append("tileSize=").Append(data.worldTileSize.ToString("R", CultureInfo.InvariantCulture)).Append('\n');
|
||||
builder.Append("layerHeight=").Append(data.worldLayerHeight.ToString("R", CultureInfo.InvariantCulture)).Append('\n');
|
||||
|
||||
var tiles = new List<JWorldTile>(data.worldTiles);
|
||||
tiles.Sort(CompareTiles);
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
builder.Append("tile=").Append(tile.x).Append(',').Append(tile.y).Append('\n');
|
||||
}
|
||||
|
||||
var endpoints = new List<StageWorldSyncEndpoint>(data.expectedEndpoints);
|
||||
endpoints.Sort(CompareEndpoints);
|
||||
foreach (var endpoint in endpoints)
|
||||
{
|
||||
builder.Append("endpoint=")
|
||||
.Append(endpoint.endpointId).Append('|')
|
||||
.Append((int)endpoint.kind).Append('|')
|
||||
.Append(endpoint.worldTile.x).Append(',').Append(endpoint.worldTile.y).Append('|')
|
||||
.Append((int)endpoint.socketFace).Append('|')
|
||||
.Append(endpoint.required ? "1" : "0").Append('|')
|
||||
.Append(endpoint.connectionId).Append('|')
|
||||
.Append(endpoint.connectedEndpointId).Append('|')
|
||||
.Append(endpoint.debugLabel).Append('\n');
|
||||
}
|
||||
|
||||
using var sha = SHA256.Create();
|
||||
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString()));
|
||||
var hash = new StringBuilder(bytes.Length * 2);
|
||||
foreach (var b in bytes)
|
||||
{
|
||||
hash.Append(b.ToString("x2"));
|
||||
}
|
||||
|
||||
return hash.ToString();
|
||||
}
|
||||
|
||||
private static StageDefinition CreateStageDefinitionAsset(WorldLayoutSO layout, WorldStageAuthoring stage)
|
||||
{
|
||||
var layoutPath = AssetDatabase.GetAssetPath(layout);
|
||||
if (string.IsNullOrEmpty(layoutPath))
|
||||
{
|
||||
Debug.LogError("Cannot create StageDefinition because the WorldLayoutSO is not saved as an asset.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var layoutDirectory = Path.GetDirectoryName(layoutPath)?.Replace('\\', '/');
|
||||
if (string.IsNullOrEmpty(layoutDirectory))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var stagesFolder = layoutDirectory + "/Stages";
|
||||
if (!AssetDatabase.IsValidFolder(stagesFolder))
|
||||
{
|
||||
AssetDatabase.CreateFolder(layoutDirectory, "Stages");
|
||||
}
|
||||
|
||||
var fileName = MakeSafeFileName(string.IsNullOrWhiteSpace(stage.displayName) ? stage.id : stage.displayName);
|
||||
var path = AssetDatabase.GenerateUniqueAssetPath(stagesFolder + "/" + fileName + ".stage-definition.asset");
|
||||
|
||||
var definition = ScriptableObject.CreateInstance<StageDefinition>();
|
||||
definition.Id = Path.GetFileNameWithoutExtension(path).ToLowerInvariant();
|
||||
definition.EnsureDefaults();
|
||||
AssetDatabase.CreateAsset(definition, path);
|
||||
return definition;
|
||||
}
|
||||
|
||||
private static StageWorldSyncEndpoint BuildEndpoint(WorldLayoutSO layout, WorldEndpointAuthoring endpoint)
|
||||
{
|
||||
var connection = FindFirstConnection(layout, endpoint.id);
|
||||
var connectedEndpointId = connection is null
|
||||
? string.Empty
|
||||
: connection.endpointAId == endpoint.id
|
||||
? connection.endpointBId
|
||||
: connection.endpointAId;
|
||||
|
||||
return new StageWorldSyncEndpoint
|
||||
{
|
||||
endpointId = endpoint.id,
|
||||
kind = endpoint.kind,
|
||||
worldTile = endpoint.worldTile,
|
||||
socketFace = endpoint.socketFace,
|
||||
required = endpoint.required,
|
||||
connectionId = connection?.id ?? string.Empty,
|
||||
connectedEndpointId = connectedEndpointId,
|
||||
debugLabel = endpoint.kind + " " + endpoint.id
|
||||
};
|
||||
}
|
||||
|
||||
private static List<WorldEndpointAuthoring> GetStageEndpoints(WorldLayoutSO layout, string stageId)
|
||||
{
|
||||
var endpoints = new List<WorldEndpointAuthoring>();
|
||||
foreach (var endpoint in layout.endpoints)
|
||||
{
|
||||
if (endpoint != null && endpoint.stageId == stageId)
|
||||
{
|
||||
endpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
endpoints.Sort((a, b) => string.CompareOrdinal(a.id, b.id));
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private static WorldConnectionAuthoring FindFirstConnection(WorldLayoutSO layout, string endpointId)
|
||||
{
|
||||
foreach (var connection in layout.connections)
|
||||
{
|
||||
if (connection != null &&
|
||||
(connection.endpointAId == endpointId || connection.endpointBId == endpointId))
|
||||
{
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int CompareTiles(JWorldTile a, JWorldTile b)
|
||||
{
|
||||
var x = a.x.CompareTo(b.x);
|
||||
return x != 0 ? x : a.y.CompareTo(b.y);
|
||||
}
|
||||
|
||||
private static int CompareEndpoints(StageWorldSyncEndpoint a, StageWorldSyncEndpoint b)
|
||||
{
|
||||
return string.CompareOrdinal(a?.endpointId, b?.endpointId);
|
||||
}
|
||||
|
||||
private static string MakeSafeFileName(string value)
|
||||
{
|
||||
var safe = new StringBuilder();
|
||||
foreach (var c in value)
|
||||
{
|
||||
safe.Append(char.IsLetterOrDigit(c) || c == '-' || c == '_' ? c : '-');
|
||||
}
|
||||
|
||||
var result = safe.ToString().Trim('-');
|
||||
return string.IsNullOrEmpty(result) ? "StageDefinition" : result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26d7437e85a64a85a49c28877b7e145b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Editor.GON.StageEditor;
|
||||
using Intrepid.Gameplay.Journey;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
@ -128,6 +129,39 @@ namespace Editor.GON.WorldEditor
|
||||
stage.stageDefinition = evt.newValue as StageDefinition;
|
||||
}));
|
||||
Add(definition);
|
||||
|
||||
var syncButton = new Button(delegate
|
||||
{
|
||||
owner.MutateLayout(
|
||||
stage.stageDefinition is null ? "Create stage definition" : "Sync stage definition",
|
||||
delegate
|
||||
{
|
||||
StageDefinitionSyncer.CreateOrSyncStageDefinition(worldLayout, stage);
|
||||
});
|
||||
})
|
||||
{
|
||||
text = stage.stageDefinition is null ? "Create StageDefinition" : "Sync StageDefinition",
|
||||
style =
|
||||
{
|
||||
marginTop = 8
|
||||
}
|
||||
};
|
||||
Add(syncButton);
|
||||
|
||||
var openButton = new Button(delegate
|
||||
{
|
||||
Selection.activeObject = stage.stageDefinition;
|
||||
StageEditorWindow.Open(stage.stageDefinition);
|
||||
})
|
||||
{
|
||||
text = "Open Stage Editor",
|
||||
style =
|
||||
{
|
||||
marginTop = 4
|
||||
}
|
||||
};
|
||||
openButton.SetEnabled(stage.stageDefinition is not null);
|
||||
Add(openButton);
|
||||
}
|
||||
|
||||
private void BuildEndpointInspector(WorldEndpointAuthoring endpoint)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user