implements PLN-0004 journey commit validation
This commit is contained in:
parent
da4f1caaa7
commit
27b751fd40
150
Assets/Scripts/Editor/GON/WorldEditor/JourneyCommitter.cs
Normal file
150
Assets/Scripts/Editor/GON/WorldEditor/JourneyCommitter.cs
Normal file
@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Intrepid.Gameplay.Journey;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Editor.GON.WorldEditor
|
||||
{
|
||||
public static class JourneyCommitter
|
||||
{
|
||||
public static List<WorldValidationMessage> Commit(WorldLayoutSO layout, JGraphSO graph)
|
||||
{
|
||||
var messages = WorldLayoutValidator.Validate(layout);
|
||||
if (WorldLayoutValidator.HasErrors(messages) || graph is null)
|
||||
{
|
||||
if (graph is null)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error, "No target JGraphSO selected."));
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
layout.EnsureDefaults();
|
||||
graph.sourceWorldLayoutId = layout.id;
|
||||
graph.publishedAt = DateTime.UtcNow.ToString("O");
|
||||
graph.schemaVersion = 1;
|
||||
graph.stages = BuildStages(layout);
|
||||
graph.endpoints = BuildEndpoints(layout);
|
||||
graph.connections = BuildConnections(layout);
|
||||
graph.contentHash = ComputeContentHash(graph);
|
||||
graph.EnsureDefaults();
|
||||
|
||||
EditorUtility.SetDirty(graph);
|
||||
AssetDatabase.SaveAssets();
|
||||
return messages;
|
||||
}
|
||||
|
||||
private static List<JStage> BuildStages(WorldLayoutSO layout)
|
||||
{
|
||||
var stages = new List<JStage>();
|
||||
foreach (var source in layout.stages)
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
source.EnsureDefaults();
|
||||
var stage = new JStage
|
||||
{
|
||||
id = source.id,
|
||||
displayName = source.displayName,
|
||||
worldLayer = source.worldLayer,
|
||||
occupiedTiles = new List<JWorldTile>(source.occupiedTiles)
|
||||
};
|
||||
stages.Add(stage);
|
||||
}
|
||||
|
||||
return stages;
|
||||
}
|
||||
|
||||
private static List<JEndpoint> BuildEndpoints(WorldLayoutSO layout)
|
||||
{
|
||||
var endpoints = new List<JEndpoint>();
|
||||
foreach (var source in layout.endpoints)
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
endpoints.Add(new JEndpoint
|
||||
{
|
||||
id = source.id,
|
||||
stageId = source.stageId,
|
||||
worldTile = source.worldTile,
|
||||
kind = source.kind,
|
||||
required = source.required,
|
||||
socketFace = source.socketFace
|
||||
});
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private static List<JConnection> BuildConnections(WorldLayoutSO layout)
|
||||
{
|
||||
var connections = new List<JConnection>();
|
||||
foreach (var source in layout.connections)
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
connections.Add(new JConnection
|
||||
{
|
||||
id = source.id,
|
||||
endpointAId = source.endpointAId,
|
||||
endpointBId = source.endpointBId,
|
||||
directionality = source.directionality
|
||||
});
|
||||
}
|
||||
|
||||
return connections;
|
||||
}
|
||||
|
||||
private static string ComputeContentHash(JGraphSO graph)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(graph.sourceWorldLayoutId).Append('|').Append(graph.schemaVersion).AppendLine();
|
||||
|
||||
foreach (var stage in graph.stages)
|
||||
{
|
||||
builder.Append("S|").Append(stage.id).Append('|').Append(stage.displayName).Append('|')
|
||||
.Append(stage.worldLayer).AppendLine();
|
||||
foreach (var tile in stage.occupiedTiles)
|
||||
{
|
||||
builder.Append("T|").Append(tile.x).Append('|').Append(tile.y).AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var endpoint in graph.endpoints)
|
||||
{
|
||||
builder.Append("E|").Append(endpoint.id).Append('|').Append(endpoint.stageId).Append('|')
|
||||
.Append(endpoint.worldTile.x).Append('|').Append(endpoint.worldTile.y).Append('|')
|
||||
.Append(endpoint.kind).Append('|').Append(endpoint.required).Append('|')
|
||||
.Append(endpoint.socketFace).AppendLine();
|
||||
}
|
||||
|
||||
foreach (var connection in graph.connections)
|
||||
{
|
||||
builder.Append("C|").Append(connection.id).Append('|').Append(connection.endpointAId).Append('|')
|
||||
.Append(connection.endpointBId).Append('|').Append(connection.directionality).AppendLine();
|
||||
}
|
||||
|
||||
using var sha = SHA256.Create();
|
||||
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString()));
|
||||
var result = new StringBuilder(bytes.Length * 2);
|
||||
foreach (var value in bytes)
|
||||
{
|
||||
result.Append(value.ToString("x2"));
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b08cddf1c414e7b8d1f6ee4d7e67fb7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -11,10 +11,12 @@ namespace Editor.GON.WorldEditor
|
||||
public sealed class WorldEditorWindow : EditorWindow
|
||||
{
|
||||
private WorldLayoutSO layout;
|
||||
private JGraphSO graph;
|
||||
private WorldEditorState state;
|
||||
private VisualElement topBar;
|
||||
private WorldMapCanvas canvas;
|
||||
private Label infoLabel;
|
||||
private ScrollView validationView;
|
||||
|
||||
[MenuItem("Tools/Gardens Of Nil/World Editor")]
|
||||
public static void Open()
|
||||
@ -63,6 +65,17 @@ namespace Editor.GON.WorldEditor
|
||||
};
|
||||
rootVisualElement.Add(infoLabel);
|
||||
|
||||
validationView = new ScrollView
|
||||
{
|
||||
style =
|
||||
{
|
||||
maxHeight = 130,
|
||||
borderTopWidth = 1,
|
||||
borderTopColor = new Color(1f, 1f, 1f, 0.12f)
|
||||
}
|
||||
};
|
||||
rootVisualElement.Add(validationView);
|
||||
|
||||
RebuildTopBar();
|
||||
RebuildInfo();
|
||||
}
|
||||
@ -159,18 +172,41 @@ namespace Editor.GON.WorldEditor
|
||||
style = { marginLeft = 8 }
|
||||
});
|
||||
|
||||
topBar.Add(new Button(ValidatePlaceholder)
|
||||
var graphField = new ObjectField("J Graph")
|
||||
{
|
||||
objectType = typeof(JGraphSO),
|
||||
value = graph,
|
||||
style =
|
||||
{
|
||||
marginLeft = 8,
|
||||
minWidth = 200
|
||||
}
|
||||
};
|
||||
graphField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
graph = evt.newValue as JGraphSO;
|
||||
RebuildTopBar();
|
||||
});
|
||||
topBar.Add(graphField);
|
||||
|
||||
topBar.Add(new Button(CreateNewGraphAsset)
|
||||
{
|
||||
text = "+ JGraphSO",
|
||||
style = { marginLeft = 4 }
|
||||
});
|
||||
|
||||
topBar.Add(new Button(ValidateLayout)
|
||||
{
|
||||
text = "Validate",
|
||||
style = { marginLeft = 8 }
|
||||
});
|
||||
|
||||
var commit = new Button(CommitPlaceholder)
|
||||
var commit = new Button(CommitJourney)
|
||||
{
|
||||
text = "Commit Journey",
|
||||
style = { marginLeft = 4 }
|
||||
};
|
||||
commit.SetEnabled(false);
|
||||
commit.SetEnabled(graph is not null);
|
||||
topBar.Add(commit);
|
||||
|
||||
topBar.Add(new Button(SaveLayout)
|
||||
@ -233,6 +269,26 @@ namespace Editor.GON.WorldEditor
|
||||
SetLayout(asset);
|
||||
}
|
||||
|
||||
private void CreateNewGraphAsset()
|
||||
{
|
||||
var path = EditorUtility.SaveFilePanelInProject(
|
||||
"Create JGraphSO",
|
||||
"JGraph",
|
||||
"asset",
|
||||
"Choose where to create the published Journey graph asset.");
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var asset = CreateInstance<JGraphSO>();
|
||||
AssetDatabase.CreateAsset(asset, path);
|
||||
AssetDatabase.SaveAssets();
|
||||
graph = asset;
|
||||
RebuildTopBar();
|
||||
}
|
||||
|
||||
private void CreateStage()
|
||||
{
|
||||
MutateLayout("Create world stage", delegate
|
||||
@ -262,14 +318,57 @@ namespace Editor.GON.WorldEditor
|
||||
Debug.Log("Saved WorldLayoutSO: " + AssetDatabase.GetAssetPath(layout));
|
||||
}
|
||||
|
||||
private void ValidatePlaceholder()
|
||||
private void ValidateLayout()
|
||||
{
|
||||
EditorUtility.DisplayDialog("World Editor", "Validation is implemented by PLN-0004.", "OK");
|
||||
ShowValidationMessages(WorldLayoutValidator.Validate(layout));
|
||||
}
|
||||
|
||||
private void CommitPlaceholder()
|
||||
private void CommitJourney()
|
||||
{
|
||||
EditorUtility.DisplayDialog("World Editor", "Journey commit is implemented by PLN-0004.", "OK");
|
||||
var messages = JourneyCommitter.Commit(layout, graph);
|
||||
ShowValidationMessages(messages);
|
||||
if (!WorldLayoutValidator.HasErrors(messages))
|
||||
{
|
||||
Debug.Log("Committed Journey graph: " + AssetDatabase.GetAssetPath(graph));
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowValidationMessages(System.Collections.Generic.List<WorldValidationMessage> messages)
|
||||
{
|
||||
validationView?.Clear();
|
||||
if (validationView is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (messages is null || messages.Count == 0)
|
||||
{
|
||||
validationView.Add(new Label("Validation passed."));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
if (message is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var label = new Label(message.severity + ": " + message.message +
|
||||
(string.IsNullOrEmpty(message.objectId) ? string.Empty : " [" + message.objectId + "]"))
|
||||
{
|
||||
style =
|
||||
{
|
||||
color = message.severity == WorldValidationSeverity.Error
|
||||
? new Color(1f, 0.45f, 0.4f)
|
||||
: new Color(1f, 0.82f, 0.35f),
|
||||
paddingLeft = 8,
|
||||
paddingTop = 2,
|
||||
paddingBottom = 2
|
||||
}
|
||||
};
|
||||
validationView.Add(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,6 +76,20 @@ namespace Editor.GON.WorldEditor
|
||||
return null;
|
||||
}
|
||||
|
||||
public WorldEndpointAuthoring FindEndpoint(string endpointId)
|
||||
{
|
||||
EnsureDefaults();
|
||||
foreach (var endpoint in endpoints)
|
||||
{
|
||||
if (endpoint != null && endpoint.id == endpointId)
|
||||
{
|
||||
return endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsTileOccupiedByOther(string stageId, int worldLayer, JWorldTile tile)
|
||||
{
|
||||
var occupyingStage = FindStageAt(worldLayer, tile);
|
||||
|
||||
291
Assets/Scripts/Editor/GON/WorldEditor/WorldLayoutValidator.cs
Normal file
291
Assets/Scripts/Editor/GON/WorldEditor/WorldLayoutValidator.cs
Normal file
@ -0,0 +1,291 @@
|
||||
using System.Collections.Generic;
|
||||
using Intrepid.Gameplay.Journey;
|
||||
|
||||
namespace Editor.GON.WorldEditor
|
||||
{
|
||||
public static class WorldLayoutValidator
|
||||
{
|
||||
public static List<WorldValidationMessage> Validate(WorldLayoutSO layout)
|
||||
{
|
||||
var messages = new List<WorldValidationMessage>();
|
||||
if (layout is null)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error, "No WorldLayoutSO selected."));
|
||||
return messages;
|
||||
}
|
||||
|
||||
layout.EnsureDefaults();
|
||||
ValidateStages(layout, messages);
|
||||
ValidateEndpoints(layout, messages);
|
||||
ValidateConnections(layout, messages);
|
||||
return messages;
|
||||
}
|
||||
|
||||
public static bool HasErrors(List<WorldValidationMessage> messages)
|
||||
{
|
||||
if (messages is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
if (message != null && message.severity == WorldValidationSeverity.Error)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ValidateStages(WorldLayoutSO layout, List<WorldValidationMessage> messages)
|
||||
{
|
||||
var occupied = new Dictionary<string, string>();
|
||||
foreach (var stage in layout.stages)
|
||||
{
|
||||
if (stage is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
stage.EnsureDefaults();
|
||||
var endpointCount = 0;
|
||||
foreach (var endpoint in layout.endpoints)
|
||||
{
|
||||
if (endpoint != null && endpoint.stageId == stage.id)
|
||||
{
|
||||
endpointCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (endpointCount == 0)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Warning,
|
||||
"Stage has no Journey endpoints.", stage.id));
|
||||
}
|
||||
|
||||
foreach (var tile in stage.occupiedTiles)
|
||||
{
|
||||
var key = TileKey(stage.worldLayer, tile);
|
||||
if (occupied.TryGetValue(key, out var otherStageId) && otherStageId != stage.id)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"WorldTile is occupied by more than one Stage in the same WorldLayer.", stage.id));
|
||||
}
|
||||
else
|
||||
{
|
||||
occupied[key] = stage.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateEndpoints(WorldLayoutSO layout, List<WorldValidationMessage> messages)
|
||||
{
|
||||
var nonSocketByTile = new Dictionary<string, JEndpointKind>();
|
||||
var socketByFace = new HashSet<string>();
|
||||
|
||||
foreach (var endpoint in layout.endpoints)
|
||||
{
|
||||
if (endpoint is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var stage = layout.FindStage(endpoint.stageId);
|
||||
if (stage is null)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JEndpoint references a missing Stage.", endpoint.id));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!stage.ContainsTile(endpoint.worldTile))
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JEndpoint is placed on a WorldTile not occupied by its Stage.", endpoint.id));
|
||||
}
|
||||
|
||||
if (endpoint.kind == JEndpointKind.Socket)
|
||||
{
|
||||
var socketKey = endpoint.stageId + ":" + endpoint.worldTile.x + ":" + endpoint.worldTile.y + ":" +
|
||||
endpoint.socketFace;
|
||||
if (!socketByFace.Add(socketKey))
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"More than one JEndpointSocket exists on the same tile face.", endpoint.id));
|
||||
}
|
||||
|
||||
var adjacent = Adjacent(endpoint.worldTile, endpoint.socketFace);
|
||||
if (stage.ContainsTile(adjacent))
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JEndpointSocket is placed on an internal Stage face.", endpoint.id));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = TileKey(stage.worldLayer, endpoint.worldTile);
|
||||
if (nonSocketByTile.TryGetValue(key, out var existingKind) && existingKind != endpoint.kind)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JEndpointAnchor and JEndpointLift cannot coexist on the same WorldTile.", endpoint.id));
|
||||
}
|
||||
else
|
||||
{
|
||||
nonSocketByTile[key] = endpoint.kind;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateConnections(WorldLayoutSO layout, List<WorldValidationMessage> messages)
|
||||
{
|
||||
var endpointConnectionCounts = new Dictionary<string, int>();
|
||||
foreach (var connection in layout.connections)
|
||||
{
|
||||
if (connection is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var endpointA = layout.FindEndpoint(connection.endpointAId);
|
||||
var endpointB = layout.FindEndpoint(connection.endpointBId);
|
||||
|
||||
if (endpointA is null || endpointB is null)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JConnection references a missing endpoint.", connection.id));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (endpointA.id == endpointB.id)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JConnection links an endpoint to itself.", connection.id));
|
||||
}
|
||||
|
||||
if (endpointA.kind != endpointB.kind)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JConnection links incompatible endpoint types.", connection.id));
|
||||
}
|
||||
|
||||
Increment(endpointConnectionCounts, endpointA.id);
|
||||
Increment(endpointConnectionCounts, endpointB.id);
|
||||
|
||||
ValidateConnectionGeometry(layout, connection, endpointA, endpointB, messages);
|
||||
}
|
||||
|
||||
foreach (var endpoint in layout.endpoints)
|
||||
{
|
||||
if (endpoint is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
endpointConnectionCounts.TryGetValue(endpoint.id, out var count);
|
||||
switch (endpoint.kind)
|
||||
{
|
||||
case JEndpointKind.Socket when count > 1:
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JEndpointSocket has more than one JConnection.", endpoint.id));
|
||||
break;
|
||||
case JEndpointKind.Lift when count > 1:
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JEndpointLift has more than one JConnection.", endpoint.id));
|
||||
break;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(endpoint.required
|
||||
? WorldValidationSeverity.Error
|
||||
: WorldValidationSeverity.Warning,
|
||||
endpoint.required
|
||||
? "Required JEndpoint has no JConnection."
|
||||
: "Optional JEndpoint has no JConnection.",
|
||||
endpoint.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateConnectionGeometry(
|
||||
WorldLayoutSO layout,
|
||||
WorldConnectionAuthoring connection,
|
||||
WorldEndpointAuthoring endpointA,
|
||||
WorldEndpointAuthoring endpointB,
|
||||
List<WorldValidationMessage> messages)
|
||||
{
|
||||
if (endpointA.kind == JEndpointKind.Socket && endpointB.kind == JEndpointKind.Socket)
|
||||
{
|
||||
if (!AreOpposite(endpointA.socketFace, endpointB.socketFace) ||
|
||||
!IsAdjacentForFaces(endpointA.worldTile, endpointA.socketFace, endpointB.worldTile))
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JEndpointSocket connections must use opposite faces on adjacent WorldTiles.", connection.id));
|
||||
}
|
||||
}
|
||||
|
||||
if (endpointA.kind == JEndpointKind.Lift && endpointB.kind == JEndpointKind.Lift)
|
||||
{
|
||||
var stageA = layout.FindStage(endpointA.stageId);
|
||||
var stageB = layout.FindStage(endpointB.stageId);
|
||||
if (endpointA.worldTile.x != endpointB.worldTile.x || endpointA.worldTile.y != endpointB.worldTile.y)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JEndpointLift connections must be aligned on the same 2D WorldTile coordinate.",
|
||||
connection.id));
|
||||
}
|
||||
|
||||
if (stageA != null && stageB != null && stageA.worldLayer == stageB.worldLayer)
|
||||
{
|
||||
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
|
||||
"JEndpointLift connections must connect different WorldLayers.", connection.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Increment(Dictionary<string, int> counts, string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
counts.TryGetValue(key, out var count);
|
||||
counts[key] = count + 1;
|
||||
}
|
||||
|
||||
private static string TileKey(int layer, JWorldTile tile)
|
||||
{
|
||||
return layer + ":" + tile.x + ":" + tile.y;
|
||||
}
|
||||
|
||||
private static JWorldTile Adjacent(JWorldTile tile, JSocketFace face)
|
||||
{
|
||||
return face switch
|
||||
{
|
||||
JSocketFace.North => new JWorldTile(tile.x, tile.y - 1),
|
||||
JSocketFace.South => new JWorldTile(tile.x, tile.y + 1),
|
||||
JSocketFace.East => new JWorldTile(tile.x + 1, tile.y),
|
||||
_ => new JWorldTile(tile.x - 1, tile.y)
|
||||
};
|
||||
}
|
||||
|
||||
private static bool AreOpposite(JSocketFace a, JSocketFace b)
|
||||
{
|
||||
return (a == JSocketFace.North && b == JSocketFace.South) ||
|
||||
(a == JSocketFace.South && b == JSocketFace.North) ||
|
||||
(a == JSocketFace.East && b == JSocketFace.West) ||
|
||||
(a == JSocketFace.West && b == JSocketFace.East);
|
||||
}
|
||||
|
||||
private static bool IsAdjacentForFaces(JWorldTile a, JSocketFace faceA, JWorldTile b)
|
||||
{
|
||||
var expected = Adjacent(a, faceA);
|
||||
return expected.x == b.x && expected.y == b.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86f52d9a97c44cb1b8593c99d411f5b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
25
Assets/Scripts/Editor/GON/WorldEditor/WorldValidation.cs
Normal file
25
Assets/Scripts/Editor/GON/WorldEditor/WorldValidation.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace Editor.GON.WorldEditor
|
||||
{
|
||||
public enum WorldValidationSeverity
|
||||
{
|
||||
Warning = 0,
|
||||
Error = 1
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class WorldValidationMessage
|
||||
{
|
||||
public WorldValidationSeverity severity;
|
||||
public string message;
|
||||
public string objectId;
|
||||
|
||||
public WorldValidationMessage(WorldValidationSeverity severity, string message, string objectId = null)
|
||||
{
|
||||
this.severity = severity;
|
||||
this.message = message;
|
||||
this.objectId = objectId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e037255368a041d491478b61b7e0e75e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,2 +1,2 @@
|
||||
{"type":"meta","next_id":{"DSC":2,"AGD":2,"DEC":2,"PLN":5,"LSN":1,"CLSN":1}}
|
||||
{"type":"discussion","id":"DSC-0001","status":"accepted","ticket":"world-editor-journey-spec","title":"World Editor / Journey Spec","created_at":"2026-06-19","updated_at":"2026-06-19","tags":["world-editor","journey","stage-contract","architecture"],"agendas":[{"id":"AGD-0001","file":"AGD-0001-world-editor-journey-spec.md","status":"accepted","created_at":"2026-06-19","updated_at":"2026-06-19"}],"decisions":[{"id":"DEC-0001","file":"DEC-0001-world-editor-journey-v1.md","status":"accepted","created_at":"2026-06-19","updated_at":"2026-06-19","ref_agenda":"AGD-0001"}],"plans":[{"id":"PLN-0001","file":"PLN-0001-world-journey-spec.md","status":"done","created_at":"2026-06-19","updated_at":"2026-06-19","ref_decisions":["DEC-0001"]},{"id":"PLN-0002","file":"PLN-0002-journey-runtime-model.md","status":"done","created_at":"2026-06-19","updated_at":"2026-06-19","ref_decisions":["DEC-0001"]},{"id":"PLN-0003","file":"PLN-0003-world-editor-authoring.md","status":"done","created_at":"2026-06-19","updated_at":"2026-06-19","ref_decisions":["DEC-0001"]},{"id":"PLN-0004","file":"PLN-0004-journey-commit-validation.md","status":"review","created_at":"2026-06-19","updated_at":"2026-06-19","ref_decisions":["DEC-0001"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0001","status":"accepted","ticket":"world-editor-journey-spec","title":"World Editor / Journey Spec","created_at":"2026-06-19","updated_at":"2026-06-19","tags":["world-editor","journey","stage-contract","architecture"],"agendas":[{"id":"AGD-0001","file":"AGD-0001-world-editor-journey-spec.md","status":"accepted","created_at":"2026-06-19","updated_at":"2026-06-19"}],"decisions":[{"id":"DEC-0001","file":"DEC-0001-world-editor-journey-v1.md","status":"accepted","created_at":"2026-06-19","updated_at":"2026-06-19","ref_agenda":"AGD-0001"}],"plans":[{"id":"PLN-0001","file":"PLN-0001-world-journey-spec.md","status":"done","created_at":"2026-06-19","updated_at":"2026-06-19","ref_decisions":["DEC-0001"]},{"id":"PLN-0002","file":"PLN-0002-journey-runtime-model.md","status":"done","created_at":"2026-06-19","updated_at":"2026-06-19","ref_decisions":["DEC-0001"]},{"id":"PLN-0003","file":"PLN-0003-world-editor-authoring.md","status":"done","created_at":"2026-06-19","updated_at":"2026-06-19","ref_decisions":["DEC-0001"]},{"id":"PLN-0004","file":"PLN-0004-journey-commit-validation.md","status":"done","created_at":"2026-06-19","updated_at":"2026-06-19","ref_decisions":["DEC-0001"]}],"lessons":[]}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
id: PLN-0004
|
||||
ticket: world-editor-journey-spec
|
||||
title: Journey Commit and Validation
|
||||
status: review
|
||||
status: done
|
||||
created: 2026-06-19
|
||||
decisions:
|
||||
- DEC-0001
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user