implements PLN-0053
This commit is contained in:
parent
ea8c81368b
commit
bbd588eed4
@ -4,6 +4,7 @@ import java.util.Locale;
|
|||||||
|
|
||||||
public enum AssetFamilyCatalog {
|
public enum AssetFamilyCatalog {
|
||||||
GLYPH_BANK("glyph_bank"),
|
GLYPH_BANK("glyph_bank"),
|
||||||
|
SCENE_BANK("scene_bank"),
|
||||||
SOUND_BANK("sound_bank"),
|
SOUND_BANK("sound_bank"),
|
||||||
UNKNOWN("unknown");
|
UNKNOWN("unknown");
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import java.util.Locale;
|
|||||||
|
|
||||||
public enum OutputFormatCatalog {
|
public enum OutputFormatCatalog {
|
||||||
GLYPH_INDEXED_V1(AssetFamilyCatalog.GLYPH_BANK, "GLYPH/indexed_v1", "GLYPH/indexed_v1"),
|
GLYPH_INDEXED_V1(AssetFamilyCatalog.GLYPH_BANK, "GLYPH/indexed_v1", "GLYPH/indexed_v1"),
|
||||||
|
SCENE_TILED_V1(AssetFamilyCatalog.SCENE_BANK, "SCENE/tiled_v1", "SCENE/tiled_v1"),
|
||||||
SOUND_V1(AssetFamilyCatalog.SOUND_BANK, "SOUND/v1", "SOUND/v1"),
|
SOUND_V1(AssetFamilyCatalog.SOUND_BANK, "SOUND/v1", "SOUND/v1"),
|
||||||
UNKNOWN(AssetFamilyCatalog.UNKNOWN, "unknown", "Unknown");
|
UNKNOWN(AssetFamilyCatalog.UNKNOWN, "unknown", "Unknown");
|
||||||
|
|
||||||
|
|||||||
@ -100,6 +100,9 @@ public class PackerAssetWalker {
|
|||||||
diagnostics.addAll(walkResult.diagnostics());
|
diagnostics.addAll(walkResult.diagnostics());
|
||||||
return new PackerWalkResult(walkResult.probeResults(), diagnostics);
|
return new PackerWalkResult(walkResult.probeResults(), diagnostics);
|
||||||
}
|
}
|
||||||
|
case SCENE_BANK -> {
|
||||||
|
return new PackerWalkResult(List.of(), diagnostics);
|
||||||
|
}
|
||||||
case UNKNOWN -> {
|
case UNKNOWN -> {
|
||||||
diagnostics.add(new PackerDiagnostic(
|
diagnostics.add(new PackerDiagnostic(
|
||||||
PackerDiagnosticSeverity.WARNING,
|
PackerDiagnosticSeverity.WARNING,
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
public final class FileSystemPackerWorkspaceService implements PackerWorkspaceService {
|
public final class FileSystemPackerWorkspaceService implements PackerWorkspaceService {
|
||||||
private static final int GLYPH_BANK_COLOR_KEY_RGB565 = 0xF81F;
|
private static final int GLYPH_BANK_COLOR_KEY_RGB565 = 0xF81F;
|
||||||
|
private static final String SCENE_BANK_SUPPORT_FILE = "scene-bank.studio.json";
|
||||||
|
|
||||||
private final ObjectMapper mapper;
|
private final ObjectMapper mapper;
|
||||||
private final PackerWorkspaceFoundation workspaceFoundation;
|
private final PackerWorkspaceFoundation workspaceFoundation;
|
||||||
@ -287,6 +288,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
final PackerRegistryEntry entry = workspaceFoundation.allocateIdentity(project, registry, assetRoot);
|
final PackerRegistryEntry entry = workspaceFoundation.allocateIdentity(project, registry, assetRoot);
|
||||||
Files.createDirectories(assetRoot);
|
Files.createDirectories(assetRoot);
|
||||||
writeManifest(manifestPath, request, entry.assetUuid());
|
writeManifest(manifestPath, request, entry.assetUuid());
|
||||||
|
writeStudioSupportFiles(assetRoot, request);
|
||||||
final PackerRegistryState updated = workspaceFoundation.appendAllocatedEntry(registry, entry);
|
final PackerRegistryState updated = workspaceFoundation.appendAllocatedEntry(registry, entry);
|
||||||
workspaceFoundation.saveRegistry(project, updated);
|
workspaceFoundation.saveRegistry(project, updated);
|
||||||
final var runtime = runtimeRegistry.update(project, (snapshot, generation) -> runtimePatchService.afterCreateAsset(
|
final var runtime = runtimeRegistry.update(project, (snapshot, generation) -> runtimePatchService.afterCreateAsset(
|
||||||
@ -667,6 +669,21 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeStudioSupportFiles(Path assetRoot, CreateAssetRequest request) throws IOException {
|
||||||
|
if (request.assetFamily() != AssetFamilyCatalog.SCENE_BANK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Map<String, Object> supportFile = new LinkedHashMap<>();
|
||||||
|
supportFile.put("schema_version", 1);
|
||||||
|
supportFile.put("layer_count", 1);
|
||||||
|
supportFile.put("layers", List.of(Map.of(
|
||||||
|
"index", 1,
|
||||||
|
"tilemap", "layer-1.tmx")));
|
||||||
|
mapper.writerWithDefaultPrettyPrinter().writeValue(
|
||||||
|
assetRoot.resolve(SCENE_BANK_SUPPORT_FILE).toFile(),
|
||||||
|
supportFile);
|
||||||
|
}
|
||||||
|
|
||||||
private String normalizeRelativeAssetRoot(String candidate) {
|
private String normalizeRelativeAssetRoot(String candidate) {
|
||||||
final String raw = Objects.requireNonNullElse(candidate, "").trim().replace('\\', '/');
|
final String raw = Objects.requireNonNullElse(candidate, "").trim().replace('\\', '/');
|
||||||
if (raw.isBlank()) {
|
if (raw.isBlank()) {
|
||||||
|
|||||||
@ -121,7 +121,7 @@ public final class PackerAssetDeclarationParser {
|
|||||||
diagnostics.add(new PackerDiagnostic(
|
diagnostics.add(new PackerDiagnostic(
|
||||||
PackerDiagnosticSeverity.ERROR,
|
PackerDiagnosticSeverity.ERROR,
|
||||||
PackerDiagnosticCategory.STRUCTURAL,
|
PackerDiagnosticCategory.STRUCTURAL,
|
||||||
"Field 'type' must be one of: glyph_bank, palette_bank, sound_bank.",
|
"Field 'type' must be one of: glyph_bank, scene_bank, sound_bank.",
|
||||||
manifestPath,
|
manifestPath,
|
||||||
true));
|
true));
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -512,6 +512,29 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
assertTrue(events.stream().anyMatch(event -> event.kind() == PackerEventKind.ACTION_APPLIED));
|
assertTrue(events.stream().anyMatch(event -> event.kind() == PackerEventKind.ACTION_APPLIED));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createsSceneBankAssetAndWritesStudioSupportFile() throws Exception {
|
||||||
|
final Path projectRoot = tempDir.resolve("created-scene-bank");
|
||||||
|
final FileSystemPackerWorkspaceService service = service();
|
||||||
|
|
||||||
|
final var result = service.createAsset(new CreateAssetRequest(
|
||||||
|
project(projectRoot),
|
||||||
|
"scenes/overworld",
|
||||||
|
"overworld",
|
||||||
|
AssetFamilyCatalog.SCENE_BANK,
|
||||||
|
OutputFormatCatalog.SCENE_TILED_V1,
|
||||||
|
OutputCodecCatalog.NONE,
|
||||||
|
false));
|
||||||
|
|
||||||
|
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
||||||
|
final Path assetRoot = projectRoot.resolve("assets/scenes/overworld");
|
||||||
|
assertTrue(Files.isRegularFile(assetRoot.resolve("asset.json")));
|
||||||
|
assertTrue(Files.isRegularFile(assetRoot.resolve("scene-bank.studio.json")));
|
||||||
|
final var supportFile = MAPPER.readTree(assetRoot.resolve("scene-bank.studio.json").toFile());
|
||||||
|
assertEquals(1, supportFile.path("layer_count").asInt());
|
||||||
|
assertEquals("layer-1.tmx", supportFile.path("layers").get(0).path("tilemap").asText());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void returnsCreatedAssetThroughRuntimeBackedDetailsWithoutRescanMismatch() throws Exception {
|
void returnsCreatedAssetThroughRuntimeBackedDetailsWithoutRescanMismatch() throws Exception {
|
||||||
final Path projectRoot = tempDir.resolve("created-details");
|
final Path projectRoot = tempDir.resolve("created-details");
|
||||||
|
|||||||
@ -67,6 +67,31 @@ final class PackerAssetDeclarationParserTest {
|
|||||||
assertEquals(128, result.declaration().outputPipelineMetadata().get("samples").path("1").path("length").asInt());
|
assertEquals(128, result.declaration().outputPipelineMetadata().get("samples").path("1").path("length").asInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parsesSceneBankDeclaration() throws Exception {
|
||||||
|
final Path manifest = tempDir.resolve("scene-asset.json");
|
||||||
|
Files.writeString(manifest, """
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"asset_uuid": "uuid-scene",
|
||||||
|
"name": "overworld_scene",
|
||||||
|
"type": "scene_bank",
|
||||||
|
"output": {
|
||||||
|
"format": "SCENE/tiled_v1",
|
||||||
|
"codec": "NONE"
|
||||||
|
},
|
||||||
|
"preload": { "enabled": false }
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
final var result = parser.parse(manifest);
|
||||||
|
|
||||||
|
assertTrue(result.valid());
|
||||||
|
assertEquals(AssetFamilyCatalog.SCENE_BANK, result.declaration().assetFamily());
|
||||||
|
assertEquals("SCENE/tiled_v1", result.declaration().outputFormat().displayName());
|
||||||
|
assertEquals(OutputCodecCatalog.NONE, result.declaration().outputCodec());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rejectsNonObjectPipelineMetadata() throws Exception {
|
void rejectsNonObjectPipelineMetadata() throws Exception {
|
||||||
final Path manifest = tempDir.resolve("asset.json");
|
final Path manifest = tempDir.resolve("asset.json");
|
||||||
|
|||||||
@ -190,10 +190,19 @@ public enum I18n {
|
|||||||
ASSETS_LABEL_BUILD_PARTICIPATION("assets.label.buildParticipation"),
|
ASSETS_LABEL_BUILD_PARTICIPATION("assets.label.buildParticipation"),
|
||||||
ASSETS_LABEL_ASSET_ID("assets.label.assetId"),
|
ASSETS_LABEL_ASSET_ID("assets.label.assetId"),
|
||||||
ASSETS_LABEL_TYPE("assets.label.type"),
|
ASSETS_LABEL_TYPE("assets.label.type"),
|
||||||
|
ASSETS_LABEL_STUDIO_ROLE("assets.label.studioRole"),
|
||||||
|
ASSETS_LABEL_SCENE_LAYERS("assets.label.sceneLayers"),
|
||||||
|
ASSETS_LABEL_TILEMAPS("assets.label.tilemaps"),
|
||||||
|
ASSETS_LABEL_SUPPORT_FILE("assets.label.supportFile"),
|
||||||
ASSETS_TYPE_GLYPH_BANK("assets.type.glyphBank"),
|
ASSETS_TYPE_GLYPH_BANK("assets.type.glyphBank"),
|
||||||
|
ASSETS_TYPE_SCENE_BANK("assets.type.sceneBank"),
|
||||||
ASSETS_TYPE_PALETTE_BANK("assets.type.paletteBank"),
|
ASSETS_TYPE_PALETTE_BANK("assets.type.paletteBank"),
|
||||||
ASSETS_TYPE_SOUND_BANK("assets.type.soundBank"),
|
ASSETS_TYPE_SOUND_BANK("assets.type.soundBank"),
|
||||||
ASSETS_TYPE_UNKNOWN("assets.type.unknown"),
|
ASSETS_TYPE_UNKNOWN("assets.type.unknown"),
|
||||||
|
ASSETS_SPECIALIZATION_NONE("assets.specialization.none"),
|
||||||
|
ASSETS_SPECIALIZATION_TILESET("assets.specialization.tileset"),
|
||||||
|
ASSETS_SPECIALIZATION_SPRITES("assets.specialization.sprites"),
|
||||||
|
ASSETS_SPECIALIZATION_UI("assets.specialization.ui"),
|
||||||
ASSETS_LABEL_LOCATION("assets.label.location"),
|
ASSETS_LABEL_LOCATION("assets.label.location"),
|
||||||
ASSETS_LABEL_BANK("assets.label.bank"),
|
ASSETS_LABEL_BANK("assets.label.bank"),
|
||||||
ASSETS_LABEL_TARGET_LOCATION("assets.label.targetLocation"),
|
ASSETS_LABEL_TARGET_LOCATION("assets.label.targetLocation"),
|
||||||
@ -254,10 +263,12 @@ public enum I18n {
|
|||||||
ASSETS_ADD_WIZARD_LABEL_NAME("assets.addWizard.label.name"),
|
ASSETS_ADD_WIZARD_LABEL_NAME("assets.addWizard.label.name"),
|
||||||
ASSETS_ADD_WIZARD_LABEL_ROOT("assets.addWizard.label.root"),
|
ASSETS_ADD_WIZARD_LABEL_ROOT("assets.addWizard.label.root"),
|
||||||
ASSETS_ADD_WIZARD_LABEL_TYPE("assets.addWizard.label.type"),
|
ASSETS_ADD_WIZARD_LABEL_TYPE("assets.addWizard.label.type"),
|
||||||
|
ASSETS_ADD_WIZARD_LABEL_SPECIALIZATION("assets.addWizard.label.specialization"),
|
||||||
ASSETS_ADD_WIZARD_LABEL_FORMAT("assets.addWizard.label.format"),
|
ASSETS_ADD_WIZARD_LABEL_FORMAT("assets.addWizard.label.format"),
|
||||||
ASSETS_ADD_WIZARD_LABEL_CODEC("assets.addWizard.label.codec"),
|
ASSETS_ADD_WIZARD_LABEL_CODEC("assets.addWizard.label.codec"),
|
||||||
ASSETS_ADD_WIZARD_LABEL_PRELOAD("assets.addWizard.label.preload"),
|
ASSETS_ADD_WIZARD_LABEL_PRELOAD("assets.addWizard.label.preload"),
|
||||||
ASSETS_ADD_WIZARD_PROMPT_TYPE("assets.addWizard.prompt.type"),
|
ASSETS_ADD_WIZARD_PROMPT_TYPE("assets.addWizard.prompt.type"),
|
||||||
|
ASSETS_ADD_WIZARD_PROMPT_SPECIALIZATION("assets.addWizard.prompt.specialization"),
|
||||||
ASSETS_ADD_WIZARD_PROMPT_FORMAT("assets.addWizard.prompt.format"),
|
ASSETS_ADD_WIZARD_PROMPT_FORMAT("assets.addWizard.prompt.format"),
|
||||||
ASSETS_ADD_WIZARD_PROMPT_CODEC("assets.addWizard.prompt.codec"),
|
ASSETS_ADD_WIZARD_PROMPT_CODEC("assets.addWizard.prompt.codec"),
|
||||||
ASSETS_ADD_WIZARD_ASSETS_ROOT_HINT("assets.addWizard.assetsRootHint"),
|
ASSETS_ADD_WIZARD_ASSETS_ROOT_HINT("assets.addWizard.assetsRootHint"),
|
||||||
|
|||||||
@ -23,6 +23,8 @@ import p.studio.workspaces.assets.details.bank.AssetDetailsBankCompositionContro
|
|||||||
import p.studio.workspaces.assets.details.contract.AssetDetailsContractControl;
|
import p.studio.workspaces.assets.details.contract.AssetDetailsContractControl;
|
||||||
import p.studio.workspaces.assets.details.palette.AssetDetailsPaletteOverhaulingControl;
|
import p.studio.workspaces.assets.details.palette.AssetDetailsPaletteOverhaulingControl;
|
||||||
import p.studio.workspaces.assets.details.summary.AssetDetailsSummaryControl;
|
import p.studio.workspaces.assets.details.summary.AssetDetailsSummaryControl;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioMetadataService;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioMetadataSnapshot;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetAction;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetAction;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionDetails;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionDetails;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
|
||||||
@ -53,6 +55,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
|||||||
private final AssetDetailsContractControl contractControl;
|
private final AssetDetailsContractControl contractControl;
|
||||||
private final AssetDetailsBankCompositionControl bankCompositionControl;
|
private final AssetDetailsBankCompositionControl bankCompositionControl;
|
||||||
private final AssetDetailsPaletteOverhaulingControl paletteOverhaulingControl;
|
private final AssetDetailsPaletteOverhaulingControl paletteOverhaulingControl;
|
||||||
|
private final AssetStudioMetadataService studioMetadataService = new AssetStudioMetadataService();
|
||||||
private final VBox actionsContent = new VBox(10);
|
private final VBox actionsContent = new VBox(10);
|
||||||
private final ScrollPane actionsScroll = new ScrollPane();
|
private final ScrollPane actionsScroll = new ScrollPane();
|
||||||
private final VBox actionsSection;
|
private final VBox actionsSection;
|
||||||
@ -534,6 +537,10 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
|||||||
PackerAssetDetailsDTO details,
|
PackerAssetDetailsDTO details,
|
||||||
java.util.List<PackerDiagnosticDTO> diagnostics,
|
java.util.List<PackerDiagnosticDTO> diagnostics,
|
||||||
java.util.List<PackerAssetActionAvailabilityDTO> actions) {
|
java.util.List<PackerAssetActionAvailabilityDTO> actions) {
|
||||||
|
final var baseSummary = AssetListPackerMappings.mapSummary(details.summary());
|
||||||
|
final AssetStudioMetadataSnapshot studioMetadata = studioMetadataService.read(
|
||||||
|
baseSummary.assetRoot(),
|
||||||
|
baseSummary.assetFamily());
|
||||||
final java.util.List<PackerDiagnosticDTO> mergedDiagnostics = new java.util.ArrayList<>(details.diagnostics());
|
final java.util.List<PackerDiagnosticDTO> mergedDiagnostics = new java.util.ArrayList<>(details.diagnostics());
|
||||||
for (PackerDiagnosticDTO diagnostic : diagnostics) {
|
for (PackerDiagnosticDTO diagnostic : diagnostics) {
|
||||||
if (!mergedDiagnostics.contains(diagnostic)) {
|
if (!mergedDiagnostics.contains(diagnostic)) {
|
||||||
@ -541,7 +548,17 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new AssetWorkspaceAssetDetails(
|
return new AssetWorkspaceAssetDetails(
|
||||||
AssetListPackerMappings.mapSummary(details.summary()),
|
new p.studio.workspaces.assets.messages.AssetWorkspaceAssetSummary(
|
||||||
|
baseSummary.assetReference(),
|
||||||
|
baseSummary.assetName(),
|
||||||
|
baseSummary.state(),
|
||||||
|
baseSummary.buildParticipation(),
|
||||||
|
baseSummary.assetId(),
|
||||||
|
baseSummary.assetFamily(),
|
||||||
|
studioMetadata.glyphSpecialization(),
|
||||||
|
baseSummary.assetRoot(),
|
||||||
|
baseSummary.preload(),
|
||||||
|
baseSummary.hasDiagnostics()),
|
||||||
actions.stream()
|
actions.stream()
|
||||||
.map(action -> new AssetWorkspaceAssetAction(
|
.map(action -> new AssetWorkspaceAssetAction(
|
||||||
action.action(),
|
action.action(),
|
||||||
@ -557,6 +574,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
|||||||
details.outputPipeline(),
|
details.outputPipeline(),
|
||||||
mapBankComposition(details.bankComposition()),
|
mapBankComposition(details.bankComposition()),
|
||||||
details.pipelinePalettes(),
|
details.pipelinePalettes(),
|
||||||
|
studioMetadata.sceneBankMetadata(),
|
||||||
mergedDiagnostics);
|
mergedDiagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import p.studio.controls.forms.StudioFormSection;
|
|||||||
import p.studio.controls.forms.StudioSection;
|
import p.studio.controls.forms.StudioSection;
|
||||||
import p.studio.projects.ProjectReference;
|
import p.studio.projects.ProjectReference;
|
||||||
import p.studio.utilities.i18n.I18n;
|
import p.studio.utilities.i18n.I18n;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioGlyphSpecialization;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetState;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetState;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceBuildParticipation;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceBuildParticipation;
|
||||||
|
|
||||||
@ -114,6 +115,7 @@ public final class AssetDetailsUiSupport {
|
|||||||
public static String typeLabel(AssetFamilyCatalog assetFamily) {
|
public static String typeLabel(AssetFamilyCatalog assetFamily) {
|
||||||
return switch (assetFamily) {
|
return switch (assetFamily) {
|
||||||
case GLYPH_BANK -> Container.i18n().text(I18n.ASSETS_TYPE_GLYPH_BANK);
|
case GLYPH_BANK -> Container.i18n().text(I18n.ASSETS_TYPE_GLYPH_BANK);
|
||||||
|
case SCENE_BANK -> Container.i18n().text(I18n.ASSETS_TYPE_SCENE_BANK);
|
||||||
case SOUND_BANK -> Container.i18n().text(I18n.ASSETS_TYPE_SOUND_BANK);
|
case SOUND_BANK -> Container.i18n().text(I18n.ASSETS_TYPE_SOUND_BANK);
|
||||||
case UNKNOWN -> Container.i18n().text(I18n.ASSETS_TYPE_UNKNOWN);
|
case UNKNOWN -> Container.i18n().text(I18n.ASSETS_TYPE_UNKNOWN);
|
||||||
};
|
};
|
||||||
@ -122,11 +124,21 @@ public final class AssetDetailsUiSupport {
|
|||||||
public static String typeChipTone(AssetFamilyCatalog assetFamily) {
|
public static String typeChipTone(AssetFamilyCatalog assetFamily) {
|
||||||
return switch (assetFamily) {
|
return switch (assetFamily) {
|
||||||
case GLYPH_BANK -> "assets-details-chip-image";
|
case GLYPH_BANK -> "assets-details-chip-image";
|
||||||
|
case SCENE_BANK -> "assets-details-chip-generic";
|
||||||
case SOUND_BANK -> "assets-details-chip-audio";
|
case SOUND_BANK -> "assets-details-chip-audio";
|
||||||
case UNKNOWN -> "assets-details-chip-generic";
|
case UNKNOWN -> "assets-details-chip-generic";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String specializationLabel(AssetStudioGlyphSpecialization specialization) {
|
||||||
|
return switch (specialization) {
|
||||||
|
case NONE -> Container.i18n().text(I18n.ASSETS_SPECIALIZATION_NONE);
|
||||||
|
case TILESET -> Container.i18n().text(I18n.ASSETS_SPECIALIZATION_TILESET);
|
||||||
|
case SPRITES -> Container.i18n().text(I18n.ASSETS_SPECIALIZATION_SPRITES);
|
||||||
|
case UI -> Container.i18n().text(I18n.ASSETS_SPECIALIZATION_UI);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static String actionLabel(AssetAction action) {
|
public static String actionLabel(AssetAction action) {
|
||||||
return switch (action) {
|
return switch (action) {
|
||||||
case REGISTER -> Container.i18n().text(I18n.ASSETS_ACTION_REGISTER);
|
case REGISTER -> Container.i18n().text(I18n.ASSETS_ACTION_REGISTER);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package p.studio.workspaces.assets.details;
|
package p.studio.workspaces.assets.details;
|
||||||
|
|
||||||
import p.packer.dtos.PackerAssetSummaryDTO;
|
import p.packer.dtos.PackerAssetSummaryDTO;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioGlyphSpecialization;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetState;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetState;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetSummary;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetSummary;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceBuildParticipation;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceBuildParticipation;
|
||||||
@ -25,6 +26,7 @@ public final class AssetListPackerMappings {
|
|||||||
buildParticipation,
|
buildParticipation,
|
||||||
summary.identity().assetId(),
|
summary.identity().assetId(),
|
||||||
summary.assetFamily(),
|
summary.assetFamily(),
|
||||||
|
AssetStudioGlyphSpecialization.NONE,
|
||||||
summary.identity().assetRoot(),
|
summary.identity().assetRoot(),
|
||||||
summary.preloadEnabled(),
|
summary.preloadEnabled(),
|
||||||
summary.hasDiagnostics());
|
summary.hasDiagnostics());
|
||||||
|
|||||||
@ -21,6 +21,12 @@ public final class AssetBankCapacityService {
|
|||||||
final Map<String, Object> safePipeline = Map.copyOf(Objects.requireNonNull(outputPipeline, "outputPipeline"));
|
final Map<String, Object> safePipeline = Map.copyOf(Objects.requireNonNull(outputPipeline, "outputPipeline"));
|
||||||
return switch (safeFamily) {
|
return switch (safeFamily) {
|
||||||
case GLYPH_BANK -> evaluateGlyphBank(artifactCount, safeMetadata);
|
case GLYPH_BANK -> evaluateGlyphBank(artifactCount, safeMetadata);
|
||||||
|
case SCENE_BANK -> new AssetDetailsBankCompositionCapacityState(
|
||||||
|
0.0d,
|
||||||
|
StudioAssetCapacitySeverity.GREEN,
|
||||||
|
false,
|
||||||
|
artifactCount + " support files",
|
||||||
|
"");
|
||||||
case SOUND_BANK -> evaluateSoundBank(resolveSoundBankUsedBytes(safePipeline, usedBytes));
|
case SOUND_BANK -> evaluateSoundBank(resolveSoundBankUsedBytes(safePipeline, usedBytes));
|
||||||
case UNKNOWN -> new AssetDetailsBankCompositionCapacityState(
|
case UNKNOWN -> new AssetDetailsBankCompositionCapacityState(
|
||||||
0.0d,
|
0.0d,
|
||||||
|
|||||||
@ -8,6 +8,9 @@ import p.studio.lsp.events.StudioWorkspaceEventBus;
|
|||||||
import p.studio.projects.ProjectReference;
|
import p.studio.projects.ProjectReference;
|
||||||
import p.studio.utilities.i18n.I18n;
|
import p.studio.utilities.i18n.I18n;
|
||||||
import p.studio.workspaces.assets.details.AssetDetailsUiSupport;
|
import p.studio.workspaces.assets.details.AssetDetailsUiSupport;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioGlyphSpecialization;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioSceneBankMetadata;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioSceneLayerBinding;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetSummary;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetSummary;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsViewState;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsViewState;
|
||||||
import p.studio.workspaces.assets.messages.events.StudioAssetsDetailsViewStateChangedEvent;
|
import p.studio.workspaces.assets.messages.events.StudioAssetsDetailsViewStateChangedEvent;
|
||||||
@ -53,14 +56,40 @@ public final class AssetDetailsSummaryControl extends VBox implements StudioCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
final VBox content = new VBox(8);
|
final VBox content = new VBox(8);
|
||||||
|
final AssetStudioSceneBankMetadata sceneBankMetadata = viewState.selectedAssetDetails().sceneBankMetadata();
|
||||||
content.getChildren().setAll(
|
content.getChildren().setAll(
|
||||||
AssetDetailsUiSupport.createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_ASSET_ID), summary.assetId() == null ? "—" : String.valueOf(summary.assetId())),
|
AssetDetailsUiSupport.createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_ASSET_ID), summary.assetId() == null ? "—" : String.valueOf(summary.assetId())),
|
||||||
AssetDetailsUiSupport.createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), AssetDetailsUiSupport.projectRelativePath(projectReference, summary.assetRoot())));
|
AssetDetailsUiSupport.createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), AssetDetailsUiSupport.projectRelativePath(projectReference, summary.assetRoot())));
|
||||||
|
final VBox typeBox = new VBox(6);
|
||||||
|
typeBox.getChildren().add(AssetDetailsUiSupport.createChip(
|
||||||
|
AssetDetailsUiSupport.typeChipTone(summary.assetFamily()),
|
||||||
|
AssetDetailsUiSupport.typeLabel(summary.assetFamily())));
|
||||||
|
if (summary.glyphSpecialization() != AssetStudioGlyphSpecialization.NONE) {
|
||||||
|
typeBox.getChildren().add(AssetDetailsUiSupport.createChip(
|
||||||
|
"assets-details-chip-generic",
|
||||||
|
AssetDetailsUiSupport.specializationLabel(summary.glyphSpecialization())));
|
||||||
|
}
|
||||||
content.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
|
content.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
|
||||||
Container.i18n().text(I18n.ASSETS_LABEL_TYPE),
|
Container.i18n().text(I18n.ASSETS_LABEL_TYPE),
|
||||||
AssetDetailsUiSupport.createChip(
|
typeBox));
|
||||||
AssetDetailsUiSupport.typeChipTone(summary.assetFamily()),
|
if (summary.glyphSpecialization() != AssetStudioGlyphSpecialization.NONE) {
|
||||||
AssetDetailsUiSupport.typeLabel(summary.assetFamily()))));
|
content.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
|
||||||
|
Container.i18n().text(I18n.ASSETS_LABEL_STUDIO_ROLE),
|
||||||
|
AssetDetailsUiSupport.specializationLabel(summary.glyphSpecialization())));
|
||||||
|
}
|
||||||
|
if (sceneBankMetadata != null) {
|
||||||
|
content.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
|
||||||
|
Container.i18n().text(I18n.ASSETS_LABEL_SCENE_LAYERS),
|
||||||
|
String.valueOf(sceneBankMetadata.layerCount())));
|
||||||
|
content.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
|
||||||
|
Container.i18n().text(I18n.ASSETS_LABEL_TILEMAPS),
|
||||||
|
sceneBankMetadata.layerBindings().stream()
|
||||||
|
.map(AssetStudioSceneLayerBinding::tilemap)
|
||||||
|
.collect(java.util.stream.Collectors.joining(", "))));
|
||||||
|
content.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
|
||||||
|
Container.i18n().text(I18n.ASSETS_LABEL_SUPPORT_FILE),
|
||||||
|
AssetDetailsUiSupport.projectRelativePath(projectReference, sceneBankMetadata.supportFile())));
|
||||||
|
}
|
||||||
content.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
|
content.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
|
||||||
Container.i18n().text(I18n.ASSETS_LABEL_REGISTRATION),
|
Container.i18n().text(I18n.ASSETS_LABEL_REGISTRATION),
|
||||||
AssetDetailsUiSupport.createChip(
|
AssetDetailsUiSupport.createChip(
|
||||||
|
|||||||
@ -113,6 +113,7 @@ public final class AssetListItemControl extends VBox {
|
|||||||
private String assetRowToneClass(AssetFamilyCatalog assetFamily) {
|
private String assetRowToneClass(AssetFamilyCatalog assetFamily) {
|
||||||
return switch (assetFamily) {
|
return switch (assetFamily) {
|
||||||
case GLYPH_BANK -> "assets-workspace-asset-row-tone-image";
|
case GLYPH_BANK -> "assets-workspace-asset-row-tone-image";
|
||||||
|
case SCENE_BANK -> "assets-workspace-asset-row-tone-generic";
|
||||||
case SOUND_BANK -> "assets-workspace-asset-row-tone-audio";
|
case SOUND_BANK -> "assets-workspace-asset-row-tone-audio";
|
||||||
default -> "assets-workspace-asset-row-tone-generic";
|
default -> "assets-workspace-asset-row-tone-generic";
|
||||||
};
|
};
|
||||||
@ -121,6 +122,7 @@ public final class AssetListItemControl extends VBox {
|
|||||||
private String assetNameToneClass(AssetFamilyCatalog assetFamily) {
|
private String assetNameToneClass(AssetFamilyCatalog assetFamily) {
|
||||||
return switch (assetFamily) {
|
return switch (assetFamily) {
|
||||||
case GLYPH_BANK -> "assets-workspace-asset-name-tone-image";
|
case GLYPH_BANK -> "assets-workspace-asset-name-tone-image";
|
||||||
|
case SCENE_BANK -> "assets-workspace-asset-name-tone-generic";
|
||||||
case SOUND_BANK -> "assets-workspace-asset-name-tone-audio";
|
case SOUND_BANK -> "assets-workspace-asset-name-tone-audio";
|
||||||
default -> "assets-workspace-asset-name-tone-generic";
|
default -> "assets-workspace-asset-name-tone-generic";
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import p.packer.dtos.PackerCodecConfigurationFieldDTO;
|
|||||||
import p.packer.dtos.PackerDiagnosticDTO;
|
import p.packer.dtos.PackerDiagnosticDTO;
|
||||||
import p.packer.messages.assets.OutputCodecCatalog;
|
import p.packer.messages.assets.OutputCodecCatalog;
|
||||||
import p.packer.messages.assets.OutputFormatCatalog;
|
import p.packer.messages.assets.OutputFormatCatalog;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioSceneBankMetadata;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -20,6 +21,7 @@ public record AssetWorkspaceAssetDetails(
|
|||||||
Map<String, Object> outputPipeline,
|
Map<String, Object> outputPipeline,
|
||||||
AssetWorkspaceBankCompositionDetails bankComposition,
|
AssetWorkspaceBankCompositionDetails bankComposition,
|
||||||
List<Map<String, Object>> pipelinePalettes,
|
List<Map<String, Object>> pipelinePalettes,
|
||||||
|
AssetStudioSceneBankMetadata sceneBankMetadata,
|
||||||
List<PackerDiagnosticDTO> diagnostics) {
|
List<PackerDiagnosticDTO> diagnostics) {
|
||||||
|
|
||||||
public AssetWorkspaceAssetDetails {
|
public AssetWorkspaceAssetDetails {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package p.studio.workspaces.assets.messages;
|
|||||||
|
|
||||||
import p.packer.messages.AssetReference;
|
import p.packer.messages.AssetReference;
|
||||||
import p.packer.messages.assets.AssetFamilyCatalog;
|
import p.packer.messages.assets.AssetFamilyCatalog;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioGlyphSpecialization;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -13,6 +14,7 @@ public record AssetWorkspaceAssetSummary(
|
|||||||
AssetWorkspaceBuildParticipation buildParticipation,
|
AssetWorkspaceBuildParticipation buildParticipation,
|
||||||
Integer assetId,
|
Integer assetId,
|
||||||
AssetFamilyCatalog assetFamily,
|
AssetFamilyCatalog assetFamily,
|
||||||
|
AssetStudioGlyphSpecialization glyphSpecialization,
|
||||||
Path assetRoot,
|
Path assetRoot,
|
||||||
boolean preload,
|
boolean preload,
|
||||||
boolean hasDiagnostics) {
|
boolean hasDiagnostics) {
|
||||||
@ -23,6 +25,7 @@ public record AssetWorkspaceAssetSummary(
|
|||||||
Objects.requireNonNull(state, "state");
|
Objects.requireNonNull(state, "state");
|
||||||
Objects.requireNonNull(buildParticipation, "buildParticipation");
|
Objects.requireNonNull(buildParticipation, "buildParticipation");
|
||||||
assetFamily = Objects.requireNonNullElse(assetFamily, AssetFamilyCatalog.UNKNOWN);
|
assetFamily = Objects.requireNonNullElse(assetFamily, AssetFamilyCatalog.UNKNOWN);
|
||||||
|
glyphSpecialization = Objects.requireNonNullElse(glyphSpecialization, AssetStudioGlyphSpecialization.NONE);
|
||||||
assetRoot = Objects.requireNonNull(assetRoot, "assetRoot").toAbsolutePath().normalize();
|
assetRoot = Objects.requireNonNull(assetRoot, "assetRoot").toAbsolutePath().normalize();
|
||||||
if (assetName.isBlank()) {
|
if (assetName.isBlank()) {
|
||||||
throw new IllegalArgumentException("assetName must not be blank");
|
throw new IllegalArgumentException("assetName must not be blank");
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
package p.studio.workspaces.assets.metadata;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public enum AssetStudioGlyphSpecialization {
|
||||||
|
NONE("none"),
|
||||||
|
TILESET("tileset"),
|
||||||
|
SPRITES("sprites"),
|
||||||
|
UI("ui");
|
||||||
|
|
||||||
|
private final String manifestValue;
|
||||||
|
|
||||||
|
AssetStudioGlyphSpecialization(String manifestValue) {
|
||||||
|
this.manifestValue = manifestValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String manifestValue() {
|
||||||
|
return manifestValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AssetStudioGlyphSpecialization fromManifestValue(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
final String normalized = value.trim().toLowerCase(Locale.ROOT);
|
||||||
|
for (AssetStudioGlyphSpecialization candidate : values()) {
|
||||||
|
if (candidate.manifestValue.equals(normalized)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
package p.studio.workspaces.assets.metadata;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import p.packer.messages.assets.AssetFamilyCatalog;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class AssetStudioMetadataService {
|
||||||
|
public static final String STUDIO_ASSET_METADATA_FILE = "studio.asset.json";
|
||||||
|
public static final String SCENE_BANK_SUPPORT_FILE = "scene-bank.studio.json";
|
||||||
|
|
||||||
|
private final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
public AssetStudioMetadataSnapshot read(Path assetRoot, AssetFamilyCatalog assetFamily) {
|
||||||
|
final Path normalizedAssetRoot = Objects.requireNonNull(assetRoot, "assetRoot").toAbsolutePath().normalize();
|
||||||
|
return new AssetStudioMetadataSnapshot(
|
||||||
|
readGlyphSpecialization(normalizedAssetRoot, assetFamily),
|
||||||
|
readSceneBankMetadata(normalizedAssetRoot, assetFamily));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeGlyphSpecialization(Path assetRoot, AssetStudioGlyphSpecialization specialization) throws IOException {
|
||||||
|
final Path metadataPath = Objects.requireNonNull(assetRoot, "assetRoot")
|
||||||
|
.toAbsolutePath()
|
||||||
|
.normalize()
|
||||||
|
.resolve(STUDIO_ASSET_METADATA_FILE);
|
||||||
|
final AssetStudioGlyphSpecialization normalized = Objects.requireNonNullElse(
|
||||||
|
specialization,
|
||||||
|
AssetStudioGlyphSpecialization.NONE);
|
||||||
|
if (normalized == AssetStudioGlyphSpecialization.NONE) {
|
||||||
|
Files.deleteIfExists(metadataPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final var root = mapper.createObjectNode();
|
||||||
|
root.put("schema_version", 1);
|
||||||
|
root.put("glyph_bank_specialization", normalized.manifestValue());
|
||||||
|
mapper.writerWithDefaultPrettyPrinter().writeValue(metadataPath.toFile(), root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AssetStudioGlyphSpecialization readGlyphSpecialization(Path assetRoot, AssetFamilyCatalog assetFamily) {
|
||||||
|
if (assetFamily != AssetFamilyCatalog.GLYPH_BANK) {
|
||||||
|
return AssetStudioGlyphSpecialization.NONE;
|
||||||
|
}
|
||||||
|
final Path metadataPath = assetRoot.resolve(STUDIO_ASSET_METADATA_FILE);
|
||||||
|
if (!Files.isRegularFile(metadataPath)) {
|
||||||
|
return AssetStudioGlyphSpecialization.NONE;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final JsonNode root = mapper.readTree(metadataPath.toFile());
|
||||||
|
if (root == null || !root.isObject()) {
|
||||||
|
return AssetStudioGlyphSpecialization.NONE;
|
||||||
|
}
|
||||||
|
return AssetStudioGlyphSpecialization.fromManifestValue(root.path("glyph_bank_specialization").asText(null));
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
return AssetStudioGlyphSpecialization.NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AssetStudioSceneBankMetadata readSceneBankMetadata(Path assetRoot, AssetFamilyCatalog assetFamily) {
|
||||||
|
if (assetFamily != AssetFamilyCatalog.SCENE_BANK) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Path supportFile = assetRoot.resolve(SCENE_BANK_SUPPORT_FILE);
|
||||||
|
if (!Files.isRegularFile(supportFile)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final JsonNode root = mapper.readTree(supportFile.toFile());
|
||||||
|
if (root == null || !root.isObject()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final int layerCount = root.path("layer_count").asInt(0);
|
||||||
|
if (layerCount < 1 || layerCount > 4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!(root.path("layers") instanceof ArrayNode layersNode) || layersNode.size() != layerCount) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final List<AssetStudioSceneLayerBinding> bindings = new ArrayList<>();
|
||||||
|
final Set<Integer> indexes = new HashSet<>();
|
||||||
|
for (JsonNode layerNode : layersNode) {
|
||||||
|
final int index = layerNode.path("index").asInt(0);
|
||||||
|
final String tilemap = layerNode.path("tilemap").asText("").trim();
|
||||||
|
if (index < 1 || index > layerCount || tilemap.isBlank() || !indexes.add(index)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
bindings.add(new AssetStudioSceneLayerBinding(index, tilemap));
|
||||||
|
}
|
||||||
|
bindings.sort(Comparator.comparingInt(AssetStudioSceneLayerBinding::index));
|
||||||
|
return new AssetStudioSceneBankMetadata(layerCount, bindings, supportFile);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package p.studio.workspaces.assets.metadata;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record AssetStudioMetadataSnapshot(
|
||||||
|
AssetStudioGlyphSpecialization glyphSpecialization,
|
||||||
|
AssetStudioSceneBankMetadata sceneBankMetadata) {
|
||||||
|
|
||||||
|
public AssetStudioMetadataSnapshot {
|
||||||
|
glyphSpecialization = Objects.requireNonNullElse(glyphSpecialization, AssetStudioGlyphSpecialization.NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package p.studio.workspaces.assets.metadata;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record AssetStudioSceneBankMetadata(
|
||||||
|
int layerCount,
|
||||||
|
List<AssetStudioSceneLayerBinding> layerBindings,
|
||||||
|
Path supportFile) {
|
||||||
|
|
||||||
|
public AssetStudioSceneBankMetadata {
|
||||||
|
layerBindings = List.copyOf(Objects.requireNonNull(layerBindings, "layerBindings"));
|
||||||
|
supportFile = Objects.requireNonNull(supportFile, "supportFile").toAbsolutePath().normalize();
|
||||||
|
if (layerCount < 1 || layerCount > 4) {
|
||||||
|
throw new IllegalArgumentException("layerCount must stay between 1 and 4");
|
||||||
|
}
|
||||||
|
if (layerBindings.size() != layerCount) {
|
||||||
|
throw new IllegalArgumentException("layerBindings must match layerCount");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package p.studio.workspaces.assets.metadata;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record AssetStudioSceneLayerBinding(int index, String tilemap) {
|
||||||
|
public AssetStudioSceneLayerBinding {
|
||||||
|
tilemap = Objects.requireNonNull(tilemap, "tilemap").trim();
|
||||||
|
if (index <= 0) {
|
||||||
|
throw new IllegalArgumentException("index must be positive");
|
||||||
|
}
|
||||||
|
if (tilemap.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("tilemap must not be blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,6 +24,8 @@ import p.studio.Container;
|
|||||||
import p.studio.projects.ProjectReference;
|
import p.studio.projects.ProjectReference;
|
||||||
import p.studio.utilities.i18n.I18n;
|
import p.studio.utilities.i18n.I18n;
|
||||||
import p.studio.workspaces.assets.details.AssetDetailsUiSupport;
|
import p.studio.workspaces.assets.details.AssetDetailsUiSupport;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioGlyphSpecialization;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioMetadataService;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -47,9 +49,11 @@ public final class AddAssetWizard {
|
|||||||
private final TextField assetRootField = new TextField();
|
private final TextField assetRootField = new TextField();
|
||||||
private final TextField assetNameField = new TextField();
|
private final TextField assetNameField = new TextField();
|
||||||
private final ComboBox<AssetFamilyCatalog> assetFamilyCombo = new ComboBox<>();
|
private final ComboBox<AssetFamilyCatalog> assetFamilyCombo = new ComboBox<>();
|
||||||
|
private final ComboBox<AssetStudioGlyphSpecialization> glyphSpecializationCombo = new ComboBox<>();
|
||||||
private final ComboBox<OutputFormatCatalog> outputFormatCombo = new ComboBox<>();
|
private final ComboBox<OutputFormatCatalog> outputFormatCombo = new ComboBox<>();
|
||||||
private final ComboBox<OutputCodecCatalog> outputCodecCombo = new ComboBox<>();
|
private final ComboBox<OutputCodecCatalog> outputCodecCombo = new ComboBox<>();
|
||||||
private final CheckBox preloadCheckBox = new CheckBox();
|
private final CheckBox preloadCheckBox = new CheckBox();
|
||||||
|
private final AssetStudioMetadataService studioMetadataService = new AssetStudioMetadataService();
|
||||||
|
|
||||||
private int stepIndex;
|
private int stepIndex;
|
||||||
private boolean creating;
|
private boolean creating;
|
||||||
@ -65,6 +69,7 @@ public final class AddAssetWizard {
|
|||||||
|
|
||||||
preloadCheckBox.setSelected(false);
|
preloadCheckBox.setSelected(false);
|
||||||
configureAssetFamilyCombo();
|
configureAssetFamilyCombo();
|
||||||
|
configureGlyphSpecializationCombo();
|
||||||
configureOutputFormatCombo();
|
configureOutputFormatCombo();
|
||||||
configureOutputCodecCombo();
|
configureOutputCodecCombo();
|
||||||
renderStep();
|
renderStep();
|
||||||
@ -131,7 +136,34 @@ public final class AddAssetWizard {
|
|||||||
});
|
});
|
||||||
assetFamilyCombo.valueProperty().addListener((ignored, oldValue, newValue) -> {
|
assetFamilyCombo.valueProperty().addListener((ignored, oldValue, newValue) -> {
|
||||||
if (!Objects.equals(oldValue, newValue)) {
|
if (!Objects.equals(oldValue, newValue)) {
|
||||||
|
if (newValue != AssetFamilyCatalog.GLYPH_BANK) {
|
||||||
|
glyphSpecializationCombo.getSelectionModel().select(AssetStudioGlyphSpecialization.NONE);
|
||||||
|
}
|
||||||
refreshOutputFormats();
|
refreshOutputFormats();
|
||||||
|
if (stepIndex == 1) {
|
||||||
|
renderStep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureGlyphSpecializationCombo() {
|
||||||
|
glyphSpecializationCombo.setItems(FXCollections.observableArrayList(AssetStudioGlyphSpecialization.values()));
|
||||||
|
glyphSpecializationCombo.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
glyphSpecializationCombo.setPromptText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_PROMPT_SPECIALIZATION));
|
||||||
|
glyphSpecializationCombo.getSelectionModel().select(AssetStudioGlyphSpecialization.NONE);
|
||||||
|
glyphSpecializationCombo.setCellFactory(ignored -> new javafx.scene.control.ListCell<>() {
|
||||||
|
@Override
|
||||||
|
protected void updateItem(AssetStudioGlyphSpecialization item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
setText(empty || item == null ? null : AssetDetailsUiSupport.specializationLabel(item));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
glyphSpecializationCombo.setButtonCell(new javafx.scene.control.ListCell<>() {
|
||||||
|
@Override
|
||||||
|
protected void updateItem(AssetStudioGlyphSpecialization item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
setText(empty || item == null ? null : AssetDetailsUiSupport.specializationLabel(item));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -224,17 +256,23 @@ public final class AddAssetWizard {
|
|||||||
|
|
||||||
final Label nameLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_NAME));
|
final Label nameLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_NAME));
|
||||||
final Label typeLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_TYPE));
|
final Label typeLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_TYPE));
|
||||||
|
final Label specializationLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_SPECIALIZATION));
|
||||||
final Label formatLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_FORMAT));
|
final Label formatLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_FORMAT));
|
||||||
final Label codecLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_CODEC));
|
final Label codecLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_CODEC));
|
||||||
final Label preloadLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_PRELOAD));
|
final Label preloadLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_PRELOAD));
|
||||||
final Label noteLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_NOTE));
|
final Label noteLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_NOTE));
|
||||||
noteLabel.setWrapText(true);
|
noteLabel.setWrapText(true);
|
||||||
noteLabel.getStyleClass().add("studio-launcher-subtitle");
|
noteLabel.getStyleClass().add("studio-launcher-subtitle");
|
||||||
|
final VBox specializationBox = new VBox(6, specializationLabel, glyphSpecializationCombo);
|
||||||
|
final boolean specializationVisible = selectedFamily() == AssetFamilyCatalog.GLYPH_BANK;
|
||||||
|
specializationBox.setVisible(specializationVisible);
|
||||||
|
specializationBox.setManaged(specializationVisible);
|
||||||
|
|
||||||
preloadCheckBox.setText("");
|
preloadCheckBox.setText("");
|
||||||
stepBody.getChildren().setAll(
|
stepBody.getChildren().setAll(
|
||||||
new VBox(6, nameLabel, assetNameField),
|
new VBox(6, nameLabel, assetNameField),
|
||||||
new VBox(6, typeLabel, assetFamilyCombo),
|
new VBox(6, typeLabel, assetFamilyCombo),
|
||||||
|
specializationBox,
|
||||||
new VBox(6, formatLabel, outputFormatCombo),
|
new VBox(6, formatLabel, outputFormatCombo),
|
||||||
new VBox(6, codecLabel, outputCodecCombo),
|
new VBox(6, codecLabel, outputCodecCombo),
|
||||||
new VBox(6, preloadLabel, preloadCheckBox),
|
new VBox(6, preloadLabel, preloadCheckBox),
|
||||||
@ -360,6 +398,15 @@ public final class AddAssetWizard {
|
|||||||
private void applyCreateResult(CreateAssetResult createResult) {
|
private void applyCreateResult(CreateAssetResult createResult) {
|
||||||
creating = false;
|
creating = false;
|
||||||
if (createResult.status() == PackerOperationStatus.SUCCESS && createResult.assetReference() != null) {
|
if (createResult.status() == PackerOperationStatus.SUCCESS && createResult.assetReference() != null) {
|
||||||
|
try {
|
||||||
|
persistStudioMetadata(createResult.assetRoot());
|
||||||
|
} catch (IOException exception) {
|
||||||
|
feedbackLabel.setText(exception.getMessage() == null || exception.getMessage().isBlank()
|
||||||
|
? "Unable to persist Studio metadata."
|
||||||
|
: exception.getMessage());
|
||||||
|
renderStep();
|
||||||
|
return;
|
||||||
|
}
|
||||||
result.set(createResult.assetReference());
|
result.set(createResult.assetReference());
|
||||||
stage.close();
|
stage.close();
|
||||||
return;
|
return;
|
||||||
@ -409,6 +456,17 @@ public final class AddAssetWizard {
|
|||||||
return outputCodecCombo.getValue();
|
return outputCodecCombo.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AssetStudioGlyphSpecialization selectedGlyphSpecialization() {
|
||||||
|
return glyphSpecializationCombo.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistStudioMetadata(Path assetRoot) throws IOException {
|
||||||
|
if (selectedFamily() != AssetFamilyCatalog.GLYPH_BANK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
studioMetadataService.writeGlyphSpecialization(assetRoot, selectedGlyphSpecialization());
|
||||||
|
}
|
||||||
|
|
||||||
private String normalizedRelativeRoot(String candidate) {
|
private String normalizedRelativeRoot(String candidate) {
|
||||||
final String raw = Objects.requireNonNullElse(candidate, "").trim().replace('\\', '/');
|
final String raw = Objects.requireNonNullElse(candidate, "").trim().replace('\\', '/');
|
||||||
if (raw.isBlank()) {
|
if (raw.isBlank()) {
|
||||||
|
|||||||
@ -181,10 +181,19 @@ assets.label.registration=Registration
|
|||||||
assets.label.buildParticipation=Build Participation
|
assets.label.buildParticipation=Build Participation
|
||||||
assets.label.assetId=Asset ID
|
assets.label.assetId=Asset ID
|
||||||
assets.label.type=Type
|
assets.label.type=Type
|
||||||
|
assets.label.studioRole=Studio Role
|
||||||
|
assets.label.sceneLayers=Scene Layers
|
||||||
|
assets.label.tilemaps=Tilemaps
|
||||||
|
assets.label.supportFile=Support File
|
||||||
assets.type.glyphBank=Glyph Bank
|
assets.type.glyphBank=Glyph Bank
|
||||||
|
assets.type.sceneBank=Scene Bank
|
||||||
assets.type.paletteBank=Palette Bank
|
assets.type.paletteBank=Palette Bank
|
||||||
assets.type.soundBank=Sound Bank
|
assets.type.soundBank=Sound Bank
|
||||||
assets.type.unknown=Unknown
|
assets.type.unknown=Unknown
|
||||||
|
assets.specialization.none=None
|
||||||
|
assets.specialization.tileset=Tileset
|
||||||
|
assets.specialization.sprites=Sprites
|
||||||
|
assets.specialization.ui=UI
|
||||||
assets.label.location=Location
|
assets.label.location=Location
|
||||||
assets.label.bank=Bank
|
assets.label.bank=Bank
|
||||||
assets.label.targetLocation=Target Location
|
assets.label.targetLocation=Target Location
|
||||||
@ -245,10 +254,12 @@ assets.addWizard.step.summary.description=Confirm the registered asset you are a
|
|||||||
assets.addWizard.label.name=Asset Name
|
assets.addWizard.label.name=Asset Name
|
||||||
assets.addWizard.label.root=Asset Root
|
assets.addWizard.label.root=Asset Root
|
||||||
assets.addWizard.label.type=Asset Type
|
assets.addWizard.label.type=Asset Type
|
||||||
|
assets.addWizard.label.specialization=Studio Specialization
|
||||||
assets.addWizard.label.format=Output Format
|
assets.addWizard.label.format=Output Format
|
||||||
assets.addWizard.label.codec=Output Codec
|
assets.addWizard.label.codec=Output Codec
|
||||||
assets.addWizard.label.preload=Preload on startup
|
assets.addWizard.label.preload=Preload on startup
|
||||||
assets.addWizard.prompt.type=Choose asset type
|
assets.addWizard.prompt.type=Choose asset type
|
||||||
|
assets.addWizard.prompt.specialization=Choose Studio specialization
|
||||||
assets.addWizard.prompt.format=Choose output format
|
assets.addWizard.prompt.format=Choose output format
|
||||||
assets.addWizard.prompt.codec=Choose output codec
|
assets.addWizard.prompt.codec=Choose output codec
|
||||||
assets.addWizard.assetsRootHint=Assets root: {0}
|
assets.addWizard.assetsRootHint=Assets root: {0}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import p.packer.messages.assets.AssetFamilyCatalog;
|
|||||||
import p.packer.messages.assets.OutputCodecCatalog;
|
import p.packer.messages.assets.OutputCodecCatalog;
|
||||||
import p.packer.messages.assets.OutputFormatCatalog;
|
import p.packer.messages.assets.OutputFormatCatalog;
|
||||||
import p.packer.messages.assets.PackerCodecConfigurationFieldType;
|
import p.packer.messages.assets.PackerCodecConfigurationFieldType;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioGlyphSpecialization;
|
||||||
import p.studio.workspaces.assets.messages.*;
|
import p.studio.workspaces.assets.messages.*;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -114,6 +115,7 @@ final class AssetDetailsBankCompositionCoordinatorTest {
|
|||||||
List.of(),
|
List.of(),
|
||||||
0L),
|
0L),
|
||||||
List.of(),
|
List.of(),
|
||||||
|
null,
|
||||||
List.of());
|
List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +138,7 @@ final class AssetDetailsBankCompositionCoordinatorTest {
|
|||||||
List.of(),
|
List.of(),
|
||||||
0L),
|
0L),
|
||||||
List.of(),
|
List.of(),
|
||||||
|
null,
|
||||||
List.of());
|
List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +162,7 @@ final class AssetDetailsBankCompositionCoordinatorTest {
|
|||||||
AssetWorkspaceBuildParticipation.INCLUDED,
|
AssetWorkspaceBuildParticipation.INCLUDED,
|
||||||
1,
|
1,
|
||||||
family,
|
family,
|
||||||
|
AssetStudioGlyphSpecialization.NONE,
|
||||||
Path.of("/tmp/bank"),
|
Path.of("/tmp/bank"),
|
||||||
false,
|
false,
|
||||||
false);
|
false);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import p.packer.messages.AssetReference;
|
|||||||
import p.packer.messages.assets.AssetFamilyCatalog;
|
import p.packer.messages.assets.AssetFamilyCatalog;
|
||||||
import p.packer.messages.assets.OutputCodecCatalog;
|
import p.packer.messages.assets.OutputCodecCatalog;
|
||||||
import p.packer.messages.assets.OutputFormatCatalog;
|
import p.packer.messages.assets.OutputFormatCatalog;
|
||||||
|
import p.studio.workspaces.assets.metadata.AssetStudioGlyphSpecialization;
|
||||||
import p.studio.workspaces.assets.messages.*;
|
import p.studio.workspaces.assets.messages.*;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -81,6 +82,7 @@ final class AssetDetailsPaletteOverhaulingCoordinatorTest {
|
|||||||
AssetWorkspaceBuildParticipation.INCLUDED,
|
AssetWorkspaceBuildParticipation.INCLUDED,
|
||||||
1,
|
1,
|
||||||
AssetFamilyCatalog.GLYPH_BANK,
|
AssetFamilyCatalog.GLYPH_BANK,
|
||||||
|
AssetStudioGlyphSpecialization.NONE,
|
||||||
Path.of("/tmp/bank"),
|
Path.of("/tmp/bank"),
|
||||||
false,
|
false,
|
||||||
false),
|
false),
|
||||||
@ -93,6 +95,7 @@ final class AssetDetailsPaletteOverhaulingCoordinatorTest {
|
|||||||
Map.of(),
|
Map.of(),
|
||||||
new AssetWorkspaceBankCompositionDetails(availableFiles, selectedFiles, 0L),
|
new AssetWorkspaceBankCompositionDetails(availableFiles, selectedFiles, 0L),
|
||||||
selectedFiles.stream().map(file -> (Map<String, Object>) file.metadata().get("palette")).toList(),
|
selectedFiles.stream().map(file -> (Map<String, Object>) file.metadata().get("palette")).toList(),
|
||||||
|
null,
|
||||||
List.of());
|
List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,76 @@
|
|||||||
|
package p.studio.workspaces.assets.metadata;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import p.packer.messages.assets.AssetFamilyCatalog;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
final class AssetStudioMetadataServiceTest {
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
private final AssetStudioMetadataService service = new AssetStudioMetadataService();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readsGlyphSpecializationFromStudioMetadataFile() throws Exception {
|
||||||
|
final Path assetRoot = tempDir.resolve("tileset");
|
||||||
|
Files.createDirectories(assetRoot);
|
||||||
|
service.writeGlyphSpecialization(assetRoot, AssetStudioGlyphSpecialization.TILESET);
|
||||||
|
|
||||||
|
final AssetStudioMetadataSnapshot snapshot = service.read(assetRoot, AssetFamilyCatalog.GLYPH_BANK);
|
||||||
|
|
||||||
|
assertEquals(AssetStudioGlyphSpecialization.TILESET, snapshot.glyphSpecialization());
|
||||||
|
assertNull(snapshot.sceneBankMetadata());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readsSceneBankSupportMetadataWhenContractIsValid() throws Exception {
|
||||||
|
final Path assetRoot = tempDir.resolve("scene");
|
||||||
|
Files.createDirectories(assetRoot);
|
||||||
|
Files.writeString(assetRoot.resolve(AssetStudioMetadataService.SCENE_BANK_SUPPORT_FILE), """
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"layer_count": 2,
|
||||||
|
"layers": [
|
||||||
|
{ "index": 1, "tilemap": "ground.tmx" },
|
||||||
|
{ "index": 2, "tilemap": "collision.tmx" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
final AssetStudioMetadataSnapshot snapshot = service.read(assetRoot, AssetFamilyCatalog.SCENE_BANK);
|
||||||
|
|
||||||
|
assertNotNull(snapshot.sceneBankMetadata());
|
||||||
|
assertEquals(2, snapshot.sceneBankMetadata().layerCount());
|
||||||
|
assertEquals(
|
||||||
|
java.util.List.of("ground.tmx", "collision.tmx"),
|
||||||
|
snapshot.sceneBankMetadata().layerBindings().stream().map(AssetStudioSceneLayerBinding::tilemap).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsSceneBankSupportMetadataBeyondWaveOneLayerLimit() throws Exception {
|
||||||
|
final Path assetRoot = tempDir.resolve("scene-invalid");
|
||||||
|
Files.createDirectories(assetRoot);
|
||||||
|
Files.writeString(assetRoot.resolve(AssetStudioMetadataService.SCENE_BANK_SUPPORT_FILE), """
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"layer_count": 5,
|
||||||
|
"layers": [
|
||||||
|
{ "index": 1, "tilemap": "a.tmx" },
|
||||||
|
{ "index": 2, "tilemap": "b.tmx" },
|
||||||
|
{ "index": 3, "tilemap": "c.tmx" },
|
||||||
|
{ "index": 4, "tilemap": "d.tmx" },
|
||||||
|
{ "index": 5, "tilemap": "e.tmx" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
final AssetStudioMetadataSnapshot snapshot = service.read(assetRoot, AssetFamilyCatalog.SCENE_BANK);
|
||||||
|
|
||||||
|
assertNull(snapshot.sceneBankMetadata());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user