feat(ui): port retail creature appraisal presentation
Render assessed creatures through the shared private viewport with retail heading, bounding-box camera, and light. Build the exact authored nine-row stat list and resolve creature names from the retail EnumMapper while keeping remaining font/sequencer adaptations explicit. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
f1a7912160
commit
7eaa68a5f4
26 changed files with 2780 additions and 237 deletions
|
|
@ -0,0 +1,196 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class CreatureAppraisalPresentationTests
|
||||
{
|
||||
[Fact]
|
||||
public void CameraFitsRetailVerticalSpanAndKeepsIdentityLookDirection()
|
||||
{
|
||||
var camera = new CreatureAppraisalCamera { Aspect = 2f };
|
||||
|
||||
camera.Fit(
|
||||
new Vector3(-1f, -0.5f, 0f),
|
||||
new Vector3(1f, 0.5f, 4f));
|
||||
|
||||
Assert.Equal(0f, camera.Eye.X, 5);
|
||||
Assert.Equal(-(4f * 1.20710683f + 0.5f), camera.Eye.Y, 5);
|
||||
Assert.Equal(2f, camera.Eye.Z, 5);
|
||||
Vector3 forward = -new Vector3(
|
||||
camera.View.M13,
|
||||
camera.View.M23,
|
||||
camera.View.M33);
|
||||
Assert.Equal(Vector3.UnitY, forward);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CameraUsesViewportAspectWhenCreatureIsWiderThanTall()
|
||||
{
|
||||
var camera = new CreatureAppraisalCamera { Aspect = 1f };
|
||||
|
||||
camera.Fit(
|
||||
new Vector3(-4f, -0.5f, 0f),
|
||||
new Vector3(4f, 0.5f, 2f));
|
||||
|
||||
Assert.Equal(-(8f * 1.20710683f + 0.5f), camera.Eye.Y, 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloneFactoryReusesIdentityAndFollowsCurrentAnimatedMeshRefs()
|
||||
{
|
||||
var firstMeshes = new[]
|
||||
{
|
||||
new MeshRef(0x01000001u, Matrix4x4.Identity),
|
||||
};
|
||||
var source = Entity(
|
||||
id: 17u,
|
||||
setup: 0x02000001u,
|
||||
firstMeshes);
|
||||
source.SetLocalBounds(
|
||||
new Vector3(-1f, -0.5f, 0f),
|
||||
new Vector3(1f, 0.5f, 2f));
|
||||
var lookup = new Lookup { Entity = source };
|
||||
var factory = new RetailCreatureAppraisalCloneFactory(lookup);
|
||||
|
||||
Assert.True(factory.TrySynchronize(
|
||||
17u,
|
||||
currentClone: null,
|
||||
out WorldEntity? firstClone,
|
||||
out Vector3 firstMin,
|
||||
out Vector3 firstMax));
|
||||
var nextMeshes = new[]
|
||||
{
|
||||
new MeshRef(
|
||||
0x01000001u,
|
||||
Matrix4x4.CreateTranslation(0f, 0f, 0.25f)),
|
||||
};
|
||||
source.MeshRefs = nextMeshes;
|
||||
Assert.True(factory.TrySynchronize(
|
||||
17u,
|
||||
firstClone,
|
||||
out WorldEntity? nextClone,
|
||||
out Vector3 nextMin,
|
||||
out Vector3 nextMax));
|
||||
|
||||
Assert.Same(firstClone, nextClone);
|
||||
Assert.Same(nextMeshes, nextClone!.MeshRefs);
|
||||
Assert.Equal(CreatureAppraisalEntityBuilder.RenderId, nextClone.Id);
|
||||
Assert.Equal(firstMin, nextMin);
|
||||
Assert.Equal(firstMax, nextMax);
|
||||
Assert.Equal([17u, 17u], lookup.Requests);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PresenterRendersVisibleTargetAndClearsWhenProjectionDisappears()
|
||||
{
|
||||
WorldEntity clone = Entity(
|
||||
CreatureAppraisalEntityBuilder.RenderId,
|
||||
0x02000001u,
|
||||
[new MeshRef(0x01000001u, Matrix4x4.Identity)]);
|
||||
var renderer = new Renderer { Texture = 73u };
|
||||
var view = new View();
|
||||
var factory = new Factory { Clone = clone };
|
||||
var presenter = new CreatureAppraisalFramePresenter(
|
||||
renderer,
|
||||
view,
|
||||
factory);
|
||||
|
||||
presenter.Render();
|
||||
factory.Available = false;
|
||||
presenter.Render();
|
||||
|
||||
Assert.Equal([17u, 17u], factory.Requests);
|
||||
Assert.Same(clone, renderer.Creatures[0]);
|
||||
Assert.Null(renderer.Creatures[1]);
|
||||
Assert.Equal([(300, 265)], renderer.RenderSizes);
|
||||
Assert.Equal([73u, 0u], view.Textures);
|
||||
}
|
||||
|
||||
private static WorldEntity Entity(
|
||||
uint id,
|
||||
uint setup,
|
||||
IReadOnlyList<MeshRef> meshes)
|
||||
=> new()
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = id,
|
||||
SourceGfxObjOrSetupId = setup,
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = meshes,
|
||||
};
|
||||
|
||||
private sealed class Lookup : ICreatureAppraisalEntityLookup
|
||||
{
|
||||
public WorldEntity? Entity { get; init; }
|
||||
public List<uint> Requests { get; } = [];
|
||||
|
||||
public bool TryGet(uint serverGuid, out WorldEntity entity)
|
||||
{
|
||||
Requests.Add(serverGuid);
|
||||
entity = Entity!;
|
||||
return Entity is not null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Renderer : ICreatureAppraisalRenderer
|
||||
{
|
||||
public uint Texture { get; init; }
|
||||
public List<WorldEntity?> Creatures { get; } = [];
|
||||
public List<(int Width, int Height)> RenderSizes { get; } = [];
|
||||
|
||||
public void SetCreature(
|
||||
WorldEntity? creature,
|
||||
Vector3 boundsMin,
|
||||
Vector3 boundsMax)
|
||||
=> Creatures.Add(creature);
|
||||
|
||||
public uint Render(int width, int height)
|
||||
{
|
||||
RenderSizes.Add((width, height));
|
||||
return Texture;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class View : ICreatureAppraisalFrameView
|
||||
{
|
||||
public List<uint> Textures { get; } = [];
|
||||
|
||||
public bool TryGetVisibleTarget(
|
||||
out uint serverGuid,
|
||||
out int width,
|
||||
out int height)
|
||||
{
|
||||
serverGuid = 17u;
|
||||
width = 300;
|
||||
height = 265;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetTextureHandle(uint textureHandle) =>
|
||||
Textures.Add(textureHandle);
|
||||
}
|
||||
|
||||
private sealed class Factory : ICreatureAppraisalCloneFactory
|
||||
{
|
||||
public bool Available { get; set; } = true;
|
||||
public WorldEntity? Clone { get; init; }
|
||||
public List<uint> Requests { get; } = [];
|
||||
|
||||
public bool TrySynchronize(
|
||||
uint serverGuid,
|
||||
WorldEntity? currentClone,
|
||||
out WorldEntity? synchronizedClone,
|
||||
out Vector3 boundsMin,
|
||||
out Vector3 boundsMax)
|
||||
{
|
||||
Requests.Add(serverGuid);
|
||||
synchronizedClone = Available ? Clone : null;
|
||||
boundsMin = new Vector3(-1f);
|
||||
boundsMax = new Vector3(1f);
|
||||
return Available;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ public sealed class GameWindowRenderLeafCompositionTests
|
|||
AssertAppearsInOrder(
|
||||
presentation,
|
||||
"_portal.Draw(",
|
||||
"_paperdoll?.Render();",
|
||||
"_entityViewports?.Render();",
|
||||
"_gameplayUi?.Render(",
|
||||
"_devTools?.Render(",
|
||||
"_screenshots?.CapturePending(");
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ public sealed class PrivatePresentationRendererTests
|
|||
var presentation = new PrivatePresentationRenderer(
|
||||
new Portal(calls),
|
||||
new FoundationSource(portalVisible: true),
|
||||
paperdoll: null,
|
||||
entityViewports: null,
|
||||
gameplayUi: null,
|
||||
devTools: null,
|
||||
screenshots: null);
|
||||
|
|
@ -64,6 +64,20 @@ public sealed class PrivatePresentationRendererTests
|
|||
Assert.False(outcome.ScreenshotCaptured);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrivateEntityViewportGroup_RendersEveryViewportInOrder()
|
||||
{
|
||||
List<string> calls = [];
|
||||
var group = new PrivateEntityViewportFrameGroup(
|
||||
new NamedViewport(calls, "paperdoll"),
|
||||
null,
|
||||
new NamedViewport(calls, "examination"));
|
||||
|
||||
group.Render();
|
||||
|
||||
Assert.Equal(["paperdoll", "examination"], calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ReportsThePreparedPortalSnapshotWhenDrawMutatesTheSource()
|
||||
{
|
||||
|
|
@ -72,7 +86,7 @@ public sealed class PrivatePresentationRendererTests
|
|||
var presentation = new PrivatePresentationRenderer(
|
||||
new MutatingPortal(calls, () => foundation.PortalVisible = false),
|
||||
foundation,
|
||||
paperdoll: null,
|
||||
entityViewports: null,
|
||||
gameplayUi: null,
|
||||
devTools: null,
|
||||
screenshots: null);
|
||||
|
|
@ -209,11 +223,17 @@ public sealed class PrivatePresentationRendererTests
|
|||
}
|
||||
}
|
||||
|
||||
private sealed class Paperdoll(List<string> calls) : IPrivatePaperdollFrame
|
||||
private sealed class Paperdoll(List<string> calls) : IPrivateEntityViewportFrame
|
||||
{
|
||||
public void Render() => calls.Add("paperdoll");
|
||||
}
|
||||
|
||||
private sealed class NamedViewport(List<string> calls, string name) :
|
||||
IPrivateEntityViewportFrame
|
||||
{
|
||||
public void Render() => calls.Add(name);
|
||||
}
|
||||
|
||||
private sealed class GameplayUi(List<string> calls) : IRetainedGameplayUiFrame
|
||||
{
|
||||
public RenderFrameInput Input { get; private set; }
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ public sealed class RenderFrameRecoveryIntegrationTests
|
|||
public void Draw(int width, int height) => failure.ThrowIf("portal");
|
||||
}
|
||||
|
||||
private sealed class Paperdoll(FailureSwitch failure) : IPrivatePaperdollFrame
|
||||
private sealed class Paperdoll(FailureSwitch failure) : IPrivateEntityViewportFrame
|
||||
{
|
||||
public void Render() => failure.ThrowIf("paperdoll");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace AcDream.App.Tests.UI.Layout;
|
|||
public sealed class AppraisalUiControllerTests
|
||||
{
|
||||
private const uint ObjectId = 0x50000001u;
|
||||
private static (uint, int, int) NoTexture(uint _) => (0u, 0, 0);
|
||||
|
||||
[Fact]
|
||||
public void ItemResponse_UsesAuthoredItemSubviewTitleAndScrollbars()
|
||||
|
|
@ -146,6 +147,114 @@ public sealed class AppraisalUiControllerTests
|
|||
Assert.Equal(1, shown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatureResponse_UsesRetailHeaderAndNineOrderedTemplateRows()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadExamination();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = ObjectId,
|
||||
Name = "Specter",
|
||||
Type = ItemType.Creature,
|
||||
});
|
||||
using var interaction = NewInteraction(objects, []);
|
||||
UiText characterLabel = Assert.IsType<UiText>(
|
||||
layout.FindElement(0x1000014Au));
|
||||
UiText levelLabel = Assert.IsType<UiText>(
|
||||
layout.FindElement(0x1000014Bu));
|
||||
characterLabel.LinesProvider = () =>
|
||||
[new UiText.Line("Character", characterLabel.DefaultColor)];
|
||||
levelLabel.LinesProvider = () =>
|
||||
[new UiText.Line("Level", levelLabel.DefaultColor)];
|
||||
var templates = new CreatureAppraisalRowTemplateFactory(
|
||||
FixtureLoader.LoadExaminationRowTemplateInfos(),
|
||||
NoTexture,
|
||||
defaultFont: null);
|
||||
using AppraisalUiController controller = Bind(
|
||||
layout,
|
||||
objects,
|
||||
interaction,
|
||||
new CombatState(),
|
||||
[],
|
||||
[],
|
||||
() => { },
|
||||
() => { },
|
||||
templates,
|
||||
new CreatureDisplayNameResolver(
|
||||
new Dictionary<uint, string> { [77u] = "Ghost" }))!;
|
||||
|
||||
interaction.ExamineSelectedOrEnterMode(ObjectId);
|
||||
var properties = new PropertyBundle();
|
||||
properties.Ints[2u] = 77;
|
||||
properties.Ints[25u] = 80;
|
||||
var profile = new AppraiseInfoParser.CreatureProfile(
|
||||
Flags: 0x08u,
|
||||
Health: 295u,
|
||||
HealthMax: 295u,
|
||||
Strength: 120u,
|
||||
Endurance: 190u,
|
||||
Quickness: 190u,
|
||||
Coordination: 190u,
|
||||
Focus: 330u,
|
||||
Self: 350u,
|
||||
Stamina: 190u,
|
||||
Mana: 550u,
|
||||
StaminaMax: 190u,
|
||||
ManaMax: 550u,
|
||||
AttributeHighlights: (ushort)0,
|
||||
AttributeColors: (ushort)0);
|
||||
|
||||
Assert.True(controller.Apply(Parsed(properties, profile)));
|
||||
|
||||
Assert.Equal(
|
||||
"Character",
|
||||
Assert.Single(characterLabel.LinesProvider()).Text);
|
||||
Assert.Equal("Level", Assert.Single(levelLabel.LinesProvider()).Text);
|
||||
Assert.Equal(
|
||||
"80",
|
||||
Assert.Single(((UiText)layout.FindElement(
|
||||
AppraisalUiController.CreatureLevelValueId)!)
|
||||
.LinesProvider()).Text);
|
||||
Assert.Equal(
|
||||
"Ghost",
|
||||
Assert.Single(((UiText)layout.FindElement(
|
||||
AppraisalUiController.CreatureDisplayNameId)!)
|
||||
.LinesProvider()).Text);
|
||||
|
||||
UiElement host = layout.FindElement(
|
||||
AppraisalUiController.CreatureStatsListId)!;
|
||||
UiItemList list = Assert.Single(host.Children.OfType<UiItemList>());
|
||||
Assert.Equal(9, list.GetNumUIItems());
|
||||
string[] labels = new string[9];
|
||||
string[] values = new string[9];
|
||||
for (int index = 0; index < 9; index++)
|
||||
{
|
||||
UiTemplateListSlot row =
|
||||
Assert.IsType<UiTemplateListSlot>(list.GetItem(index));
|
||||
labels[index] = Assert.Single(
|
||||
((UiText)row.Content.FindElement(
|
||||
CreatureAppraisalRowTemplateFactory.LabelId)!)
|
||||
.LinesProvider()).Text;
|
||||
values[index] = Assert.Single(
|
||||
((UiText)row.Content.FindElement(
|
||||
CreatureAppraisalRowTemplateFactory.ValueId)!)
|
||||
.LinesProvider()).Text;
|
||||
}
|
||||
Assert.Equal(
|
||||
[
|
||||
"Strength", "Endurance", "Coordination", "Quickness",
|
||||
"Focus", "Self", "Health", "Stamina", "Mana",
|
||||
],
|
||||
labels);
|
||||
Assert.Equal(
|
||||
[
|
||||
"120", "190", "190", "190", "330", "350",
|
||||
"295/295 (100 %)", "190/190", "550/550",
|
||||
],
|
||||
values);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResponseForNeitherPendingNorCurrent_IsIgnored()
|
||||
{
|
||||
|
|
@ -429,7 +538,9 @@ public sealed class AppraisalUiControllerTests
|
|||
List<(uint ObjectId, string Text)> inscriptions,
|
||||
List<string> messages,
|
||||
Action show,
|
||||
Action close)
|
||||
Action close,
|
||||
CreatureAppraisalRowTemplateFactory? creatureRows = null,
|
||||
CreatureDisplayNameResolver? creatureNames = null)
|
||||
=> AppraisalUiController.Bind(
|
||||
layout,
|
||||
objects,
|
||||
|
|
@ -440,7 +551,9 @@ public sealed class AppraisalUiControllerTests
|
|||
(objectId, text) => inscriptions.Add((objectId, text)),
|
||||
messages.Add,
|
||||
show,
|
||||
close);
|
||||
close,
|
||||
creatureRows,
|
||||
creatureNames);
|
||||
|
||||
private static ItemInteractionController NewInteraction(
|
||||
ClientObjectTable objects,
|
||||
|
|
|
|||
100
tests/AcDream.App.Tests/UI/Layout/CreatureAppraisalRowsTests.cs
Normal file
100
tests/AcDream.App.Tests/UI/Layout/CreatureAppraisalRowsTests.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Net.Messages;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
public sealed class CreatureAppraisalRowsTests
|
||||
{
|
||||
[Fact]
|
||||
public void FailedAssessmentShowsOnlyHealthPercentAndUnknownOtherValues()
|
||||
{
|
||||
var profile = Profile(
|
||||
health: 25u,
|
||||
healthMax: 100u,
|
||||
highlights: (ushort)0,
|
||||
colors: (ushort)0);
|
||||
|
||||
IReadOnlyList<CreatureAppraisalRow> rows =
|
||||
CreatureAppraisalRows.Build(profile, success: false);
|
||||
|
||||
Assert.All(
|
||||
rows,
|
||||
row => Assert.Equal(
|
||||
CreatureAppraisalValueStyle.Incomplete,
|
||||
row.Style));
|
||||
Assert.Equal("25 %", rows[6].Value);
|
||||
Assert.Equal("???", rows[0].Value);
|
||||
Assert.Equal("???", rows[7].Value);
|
||||
Assert.Equal("???", rows[8].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnchantmentBitsSelectRetailPositiveAndNegativeStyles()
|
||||
{
|
||||
var profile = Profile(
|
||||
health: 100u,
|
||||
healthMax: 100u,
|
||||
highlights: (ushort)((1 << 0) | (1 << 6)),
|
||||
colors: (ushort)(1 << 0));
|
||||
|
||||
IReadOnlyList<CreatureAppraisalRow> rows =
|
||||
CreatureAppraisalRows.Build(profile, success: true);
|
||||
|
||||
Assert.Equal(CreatureAppraisalValueStyle.Positive, rows[0].Style);
|
||||
Assert.Equal(CreatureAppraisalValueStyle.Negative, rows[6].Style);
|
||||
Assert.Equal(CreatureAppraisalValueStyle.Normal, rows[1].Style);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthoredRowTemplateCarriesRetailOverlappingLabelValueGeometry()
|
||||
{
|
||||
ElementInfo template =
|
||||
FixtureLoader.LoadExaminationRowTemplateInfos();
|
||||
|
||||
Assert.Equal(CreatureAppraisalRowTemplateFactory.TemplateId, template.Id);
|
||||
Assert.Equal((292f, 20f), (template.Width, template.Height));
|
||||
ElementInfo label = Assert.Single(
|
||||
template.Children,
|
||||
child => child.Id == CreatureAppraisalRowTemplateFactory.LabelId);
|
||||
ElementInfo value = Assert.Single(
|
||||
template.Children,
|
||||
child => child.Id == CreatureAppraisalRowTemplateFactory.ValueId);
|
||||
Assert.Equal((0f, 128f), (label.X, label.Width));
|
||||
Assert.Equal((27f, 256f), (value.X, value.Width));
|
||||
Assert.Equal(0x40000001u, label.FontDid);
|
||||
Assert.Equal(0x40000001u, value.FontDid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatureNameResolverUsesLoadedRetailMappingAndSafeFallback()
|
||||
{
|
||||
var resolver = new CreatureDisplayNameResolver(
|
||||
new Dictionary<uint, string> { [77u] = "Ghost" });
|
||||
|
||||
Assert.Equal("Ghost", resolver.Resolve(77));
|
||||
Assert.Equal(string.Empty, resolver.Resolve(0));
|
||||
Assert.Equal(string.Empty, resolver.Resolve(999));
|
||||
}
|
||||
|
||||
private static AppraiseInfoParser.CreatureProfile Profile(
|
||||
uint health,
|
||||
uint healthMax,
|
||||
ushort highlights,
|
||||
ushort colors)
|
||||
=> new(
|
||||
Flags: 0x09u,
|
||||
Health: health,
|
||||
HealthMax: healthMax,
|
||||
Strength: null,
|
||||
Endurance: null,
|
||||
Quickness: null,
|
||||
Coordination: null,
|
||||
Focus: null,
|
||||
Self: null,
|
||||
Stamina: 50u,
|
||||
Mana: 75u,
|
||||
StaminaMax: 100u,
|
||||
ManaMax: 100u,
|
||||
AttributeHighlights: highlights,
|
||||
AttributeColors: colors);
|
||||
}
|
||||
|
|
@ -113,6 +113,9 @@ public static class FixtureLoader
|
|||
public static ElementInfo LoadExaminationInfos()
|
||||
=> LoadInfos("examine_2100006B_100005F2.json");
|
||||
|
||||
public static ElementInfo LoadExaminationRowTemplateInfos()
|
||||
=> LoadInfos("examine_row_2100006B_10000166.json");
|
||||
|
||||
public static ImportedLayout LoadPowerbar()
|
||||
=> LayoutImporter.Build(LoadPowerbarInfos(), _ => (0u, 0, 0), null);
|
||||
|
||||
|
|
|
|||
|
|
@ -132,6 +132,8 @@ public sealed class RetailLayoutFixtureGenerator
|
|||
(CharacterController.LayoutId, CharacterController.RootId,
|
||||
"character_info_2100006E_10000183.json"),
|
||||
(0x2100006Bu, 0x100005F2u, "examine_2100006B_100005F2.json"),
|
||||
(AppraisalUiController.LayoutId, CreatureAppraisalRowTemplateFactory.TemplateId,
|
||||
"examine_row_2100006B_10000166.json"),
|
||||
})
|
||||
{
|
||||
ElementInfo? panelPart = LayoutImporter.ImportInfos(dats, layoutId, rootId);
|
||||
|
|
@ -145,6 +147,10 @@ public sealed class RetailLayoutFixtureGenerator
|
|||
}));
|
||||
}
|
||||
|
||||
CreatureDisplayNameResolver creatureNames =
|
||||
CreatureDisplayNameResolver.Load(dats);
|
||||
Assert.Equal("Ghost", creatureNames.Resolve(77));
|
||||
|
||||
ElementInfo? miniGame = LayoutImporter.ImportInfos(
|
||||
dats, MiniGameUiController.LayoutId, MiniGameUiController.RootId);
|
||||
Assert.NotNull(miniGame);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue