Compare commits
No commits in common. "a6b0760657420dde44616e48aa746d3723570a2b" and "ea8c81368b858db14b2a38e71171079df34c9e9f" have entirely different histories.
a6b0760657
...
ea8c81368b
@ -4,7 +4,6 @@ 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,7 +5,6 @@ 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,9 +100,6 @@ 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,7 +38,6 @@ 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;
|
||||||
@ -288,7 +287,6 @@ 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(
|
||||||
@ -669,25 +667,6 @@ 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("map_width", 16);
|
|
||||||
supportFile.put("map_height", 16);
|
|
||||||
supportFile.put("layer_count", 1);
|
|
||||||
supportFile.put("layers", List.of(Map.of(
|
|
||||||
"index", 1,
|
|
||||||
"name", "Layer 1",
|
|
||||||
"tilemap", "layer-1.tmx",
|
|
||||||
"tileset_asset_root", "")));
|
|
||||||
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, scene_bank, sound_bank.",
|
"Field 'type' must be one of: glyph_bank, palette_bank, sound_bank.",
|
||||||
manifestPath,
|
manifestPath,
|
||||||
true));
|
true));
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -512,32 +512,6 @@ 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(16, supportFile.path("map_width").asInt());
|
|
||||||
assertEquals(16, supportFile.path("map_height").asInt());
|
|
||||||
assertEquals(1, supportFile.path("layer_count").asInt());
|
|
||||||
assertEquals("Layer 1", supportFile.path("layers").get(0).path("name").asText());
|
|
||||||
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,31 +67,6 @@ 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");
|
||||||
|
|||||||
@ -154,10 +154,6 @@ public enum I18n {
|
|||||||
ASSETS_ACTIONS_EMPTY("assets.actions.empty"),
|
ASSETS_ACTIONS_EMPTY("assets.actions.empty"),
|
||||||
ASSETS_ACTION_REGISTER("assets.action.register"),
|
ASSETS_ACTION_REGISTER("assets.action.register"),
|
||||||
ASSETS_ACTION_ANALYSE("assets.action.analyse"),
|
ASSETS_ACTION_ANALYSE("assets.action.analyse"),
|
||||||
ASSETS_ACTION_GENERATE_TSX("assets.action.generateTsx"),
|
|
||||||
ASSETS_ACTION_GENERATE_TMX("assets.action.generateTmx"),
|
|
||||||
ASSETS_ACTION_VALIDATE_SCENE_BANK("assets.action.validateSceneBank"),
|
|
||||||
ASSETS_ACTION_ACCEPT_SCENE_BANK("assets.action.acceptSceneBank"),
|
|
||||||
ASSETS_ACTION_DELETE("assets.action.delete"),
|
ASSETS_ACTION_DELETE("assets.action.delete"),
|
||||||
ASSETS_ACTION_INCLUDE_IN_BUILD("assets.action.includeInBuild"),
|
ASSETS_ACTION_INCLUDE_IN_BUILD("assets.action.includeInBuild"),
|
||||||
ASSETS_ACTION_EXCLUDE_FROM_BUILD("assets.action.excludeFromBuild"),
|
ASSETS_ACTION_EXCLUDE_FROM_BUILD("assets.action.excludeFromBuild"),
|
||||||
@ -194,24 +190,10 @@ 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_STATUS("assets.label.sceneStatus"),
|
|
||||||
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_SCENE_STATUS_PENDING_VALIDATION("assets.sceneStatus.pendingValidation"),
|
|
||||||
ASSETS_SCENE_STATUS_VALIDATED_PENDING_ACCEPTANCE("assets.sceneStatus.validatedPendingAcceptance"),
|
|
||||||
ASSETS_SCENE_STATUS_READY("assets.sceneStatus.ready"),
|
|
||||||
ASSETS_SCENE_STATUS_VALIDATION_FAILED("assets.sceneStatus.validationFailed"),
|
|
||||||
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"),
|
||||||
@ -272,12 +254,10 @@ 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,31 +23,21 @@ 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.metadata.AssetStudioGlyphSpecialization;
|
|
||||||
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;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetDetails;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetDetails;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceSceneBankValidation;
|
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsStatus;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsStatus;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsViewState;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsViewState;
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceBuildParticipation;
|
import p.studio.workspaces.assets.messages.AssetWorkspaceBuildParticipation;
|
||||||
import p.studio.workspaces.assets.messages.events.StudioAssetLogEvent;
|
|
||||||
import p.studio.workspaces.assets.messages.events.StudioAssetsDetailsViewStateChangedEvent;
|
import p.studio.workspaces.assets.messages.events.StudioAssetsDetailsViewStateChangedEvent;
|
||||||
import p.studio.workspaces.assets.messages.events.StudioAssetsRefreshRequestedEvent;
|
import p.studio.workspaces.assets.messages.events.StudioAssetsRefreshRequestedEvent;
|
||||||
import p.studio.workspaces.assets.messages.events.StudioAssetsWorkspaceSelectionRequestedEvent;
|
import p.studio.workspaces.assets.messages.events.StudioAssetsWorkspaceSelectionRequestedEvent;
|
||||||
import p.studio.workspaces.assets.scene.SceneBankWorkflowResult;
|
|
||||||
import p.studio.workspaces.assets.scene.SceneBankWorkflowService;
|
|
||||||
import p.studio.workspaces.assets.tiled.TiledAssetGenerationResult;
|
|
||||||
import p.studio.workspaces.assets.tiled.TiledAssetGenerationService;
|
|
||||||
import p.studio.workspaces.assets.wizards.DeleteAssetDialog;
|
import p.studio.workspaces.assets.wizards.DeleteAssetDialog;
|
||||||
import p.studio.workspaces.assets.wizards.MoveAssetWizard;
|
import p.studio.workspaces.assets.wizards.MoveAssetWizard;
|
||||||
import p.studio.workspaces.framework.StudioEventAware;
|
import p.studio.workspaces.framework.StudioEventAware;
|
||||||
import p.studio.workspaces.framework.StudioEventBindings;
|
import p.studio.workspaces.framework.StudioEventBindings;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
@ -63,9 +53,6 @@ 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 TiledAssetGenerationService tiledGenerationService = new TiledAssetGenerationService();
|
|
||||||
private final SceneBankWorkflowService sceneBankWorkflowService = new SceneBankWorkflowService();
|
|
||||||
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;
|
||||||
@ -337,29 +324,6 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
|||||||
analyseButton.setDisable(actionRunning || viewState.selectedAssetDetails().diagnostics().isEmpty());
|
analyseButton.setDisable(actionRunning || viewState.selectedAssetDetails().diagnostics().isEmpty());
|
||||||
analyseButton.setOnAction(ignored -> openDiagnosticsDialog());
|
analyseButton.setOnAction(ignored -> openDiagnosticsDialog());
|
||||||
nodes.add(analyseButton);
|
nodes.add(analyseButton);
|
||||||
if (canGenerateTsx()) {
|
|
||||||
final Button generateTsxButton = AssetDetailsUiSupport.createActionButton(Container.i18n().text(I18n.ASSETS_ACTION_GENERATE_TSX));
|
|
||||||
generateTsxButton.setDisable(actionRunning);
|
|
||||||
generateTsxButton.setOnAction(ignored -> generateTsx());
|
|
||||||
nodes.add(generateTsxButton);
|
|
||||||
}
|
|
||||||
if (canGenerateTmx()) {
|
|
||||||
final Button generateTmxButton = AssetDetailsUiSupport.createActionButton(Container.i18n().text(I18n.ASSETS_ACTION_GENERATE_TMX));
|
|
||||||
generateTmxButton.setDisable(actionRunning);
|
|
||||||
generateTmxButton.setOnAction(ignored -> generateTmx());
|
|
||||||
nodes.add(generateTmxButton);
|
|
||||||
}
|
|
||||||
if (canValidateSceneBank()) {
|
|
||||||
final Button validateSceneButton = AssetDetailsUiSupport.createActionButton(Container.i18n().text(I18n.ASSETS_ACTION_VALIDATE_SCENE_BANK));
|
|
||||||
validateSceneButton.setDisable(actionRunning);
|
|
||||||
validateSceneButton.setOnAction(ignored -> validateSceneBank());
|
|
||||||
nodes.add(validateSceneButton);
|
|
||||||
|
|
||||||
final Button acceptSceneButton = AssetDetailsUiSupport.createActionButton(Container.i18n().text(I18n.ASSETS_ACTION_ACCEPT_SCENE_BANK));
|
|
||||||
acceptSceneButton.setDisable(actionRunning || !viewState.selectedAssetDetails().sceneBankValidation().canAccept());
|
|
||||||
acceptSceneButton.setOnAction(ignored -> acceptSceneBank());
|
|
||||||
nodes.add(acceptSceneButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Button buildParticipationButton = AssetDetailsUiSupport.createActionButton(buildParticipationActionLabel());
|
final Button buildParticipationButton = AssetDetailsUiSupport.createActionButton(buildParticipationActionLabel());
|
||||||
AssetDetailsUiSupport.applyActionTone(
|
AssetDetailsUiSupport.applyActionTone(
|
||||||
@ -485,100 +449,6 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canGenerateTsx() {
|
|
||||||
return viewState.selectedAssetDetails() != null
|
|
||||||
&& viewState.selectedAssetDetails().summary().assetFamily() == p.packer.messages.assets.AssetFamilyCatalog.GLYPH_BANK
|
|
||||||
&& viewState.selectedAssetDetails().summary().glyphSpecialization() == AssetStudioGlyphSpecialization.TILESET;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean canGenerateTmx() {
|
|
||||||
return viewState.selectedAssetDetails() != null
|
|
||||||
&& viewState.selectedAssetDetails().summary().assetFamily() == p.packer.messages.assets.AssetFamilyCatalog.SCENE_BANK;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean canValidateSceneBank() {
|
|
||||||
return viewState.selectedAssetDetails() != null
|
|
||||||
&& viewState.selectedAssetDetails().summary().assetFamily() == p.packer.messages.assets.AssetFamilyCatalog.SCENE_BANK;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateTsx() {
|
|
||||||
if (actionRunning || viewState.selectedAssetDetails() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
actionRunning = true;
|
|
||||||
actionFeedbackMessage = null;
|
|
||||||
renderActions();
|
|
||||||
final AssetWorkspaceAssetDetails details = viewState.selectedAssetDetails();
|
|
||||||
Container.backgroundTasks().submit(() -> {
|
|
||||||
final TiledAssetGenerationResult result = tiledGenerationService.generateTilesetTsx(details);
|
|
||||||
Platform.runLater(() -> applyTiledGenerationResult(result));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateTmx() {
|
|
||||||
if (actionRunning || viewState.selectedAssetDetails() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
actionRunning = true;
|
|
||||||
actionFeedbackMessage = null;
|
|
||||||
renderActions();
|
|
||||||
final AssetWorkspaceAssetDetails details = viewState.selectedAssetDetails();
|
|
||||||
Container.backgroundTasks().submit(() -> {
|
|
||||||
final TiledAssetGenerationResult result = tiledGenerationService.generateSceneBankTilemaps(projectReference, details);
|
|
||||||
Platform.runLater(() -> applyTiledGenerationResult(result));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyTiledGenerationResult(TiledAssetGenerationResult result) {
|
|
||||||
actionRunning = false;
|
|
||||||
actionFeedbackMessage = result.message();
|
|
||||||
workspaceBus.publish(new StudioAssetLogEvent("scene-bank", result.message()));
|
|
||||||
renderActions();
|
|
||||||
if (result.success() && viewState.selectedAssetReference() != null) {
|
|
||||||
workspaceBus.publish(new StudioAssetsRefreshRequestedEvent(viewState.selectedAssetReference()));
|
|
||||||
workspaceBus.publish(new StudioAssetsWorkspaceSelectionRequestedEvent(viewState.selectedAssetReference(), true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateSceneBank() {
|
|
||||||
if (actionRunning || viewState.selectedAssetDetails() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
actionRunning = true;
|
|
||||||
actionFeedbackMessage = null;
|
|
||||||
renderActions();
|
|
||||||
final AssetWorkspaceAssetDetails details = viewState.selectedAssetDetails();
|
|
||||||
Container.backgroundTasks().submit(() -> {
|
|
||||||
final SceneBankWorkflowResult result = sceneBankWorkflowService.validate(projectReference, details);
|
|
||||||
Platform.runLater(() -> applySceneBankWorkflowResult("scene-bank-validate", result));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void acceptSceneBank() {
|
|
||||||
if (actionRunning || viewState.selectedAssetDetails() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
actionRunning = true;
|
|
||||||
actionFeedbackMessage = null;
|
|
||||||
renderActions();
|
|
||||||
final AssetWorkspaceAssetDetails details = viewState.selectedAssetDetails();
|
|
||||||
Container.backgroundTasks().submit(() -> {
|
|
||||||
final SceneBankWorkflowResult result = sceneBankWorkflowService.accept(projectReference, details);
|
|
||||||
Platform.runLater(() -> applySceneBankWorkflowResult("scene-bank-accept", result));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applySceneBankWorkflowResult(String source, SceneBankWorkflowResult result) {
|
|
||||||
actionRunning = false;
|
|
||||||
actionFeedbackMessage = result.message();
|
|
||||||
workspaceBus.publish(new StudioAssetLogEvent(source, result.message()));
|
|
||||||
renderActions();
|
|
||||||
if (viewState.selectedAssetReference() != null) {
|
|
||||||
workspaceBus.publish(new StudioAssetsRefreshRequestedEvent(viewState.selectedAssetReference()));
|
|
||||||
workspaceBus.publish(new StudioAssetsWorkspaceSelectionRequestedEvent(viewState.selectedAssetReference(), true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyBuildParticipationResult(UpdateAssetBuildParticipationResult result) {
|
private void applyBuildParticipationResult(UpdateAssetBuildParticipationResult result) {
|
||||||
actionRunning = false;
|
actionRunning = false;
|
||||||
if (result.status() == p.packer.messages.PackerOperationStatus.SUCCESS && result.assetReference() != null) {
|
if (result.status() == p.packer.messages.PackerOperationStatus.SUCCESS && result.assetReference() != null) {
|
||||||
@ -664,69 +534,29 @@ 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<AssetWorkspaceAssetAction> mappedActions = actions.stream()
|
|
||||||
.map(action -> new AssetWorkspaceAssetAction(
|
|
||||||
action.action(),
|
|
||||||
action.enabled(),
|
|
||||||
action.visible(),
|
|
||||||
action.reason()))
|
|
||||||
.toList();
|
|
||||||
final p.studio.workspaces.assets.messages.AssetWorkspaceAssetSummary mappedSummary =
|
|
||||||
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());
|
|
||||||
final AssetWorkspaceBankCompositionDetails bankComposition = mapBankComposition(details.bankComposition());
|
|
||||||
final AssetWorkspaceAssetDetails draftDetails = new AssetWorkspaceAssetDetails(
|
|
||||||
mappedSummary,
|
|
||||||
mappedActions,
|
|
||||||
details.outputFormat(),
|
|
||||||
details.outputCodec(),
|
|
||||||
details.availableOutputCodecs(),
|
|
||||||
details.codecConfigurationFieldsByCodec(),
|
|
||||||
details.metadataFields(),
|
|
||||||
details.outputPipeline(),
|
|
||||||
bankComposition,
|
|
||||||
details.pipelinePalettes(),
|
|
||||||
studioMetadata.sceneBankMetadata(),
|
|
||||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
|
||||||
List.of());
|
|
||||||
final SceneBankWorkflowResult sceneBankWorkflow = sceneBankWorkflowService.inspect(projectReference, draftDetails);
|
|
||||||
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)) {
|
||||||
mergedDiagnostics.add(diagnostic);
|
mergedDiagnostics.add(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (PackerDiagnosticDTO diagnostic : sceneBankWorkflow.diagnostics()) {
|
|
||||||
if (!mergedDiagnostics.contains(diagnostic)) {
|
|
||||||
mergedDiagnostics.add(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new AssetWorkspaceAssetDetails(
|
return new AssetWorkspaceAssetDetails(
|
||||||
mappedSummary,
|
AssetListPackerMappings.mapSummary(details.summary()),
|
||||||
mappedActions,
|
actions.stream()
|
||||||
|
.map(action -> new AssetWorkspaceAssetAction(
|
||||||
|
action.action(),
|
||||||
|
action.enabled(),
|
||||||
|
action.visible(),
|
||||||
|
action.reason()))
|
||||||
|
.toList(),
|
||||||
details.outputFormat(),
|
details.outputFormat(),
|
||||||
details.outputCodec(),
|
details.outputCodec(),
|
||||||
details.availableOutputCodecs(),
|
details.availableOutputCodecs(),
|
||||||
details.codecConfigurationFieldsByCodec(),
|
details.codecConfigurationFieldsByCodec(),
|
||||||
details.metadataFields(),
|
details.metadataFields(),
|
||||||
details.outputPipeline(),
|
details.outputPipeline(),
|
||||||
bankComposition,
|
mapBankComposition(details.bankComposition()),
|
||||||
details.pipelinePalettes(),
|
details.pipelinePalettes(),
|
||||||
studioMetadata.sceneBankMetadata(),
|
|
||||||
sceneBankWorkflow.validation(),
|
|
||||||
mergedDiagnostics);
|
mergedDiagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,8 +16,6 @@ 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.AssetWorkspaceSceneBankStatus;
|
|
||||||
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;
|
||||||
|
|
||||||
@ -116,7 +114,6 @@ 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);
|
||||||
};
|
};
|
||||||
@ -125,31 +122,11 @@ 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 sceneBankStatusLabel(AssetWorkspaceSceneBankStatus status) {
|
|
||||||
return switch (status) {
|
|
||||||
case NOT_APPLICABLE -> "";
|
|
||||||
case PENDING_VALIDATION -> Container.i18n().text(I18n.ASSETS_SCENE_STATUS_PENDING_VALIDATION);
|
|
||||||
case VALIDATED_PENDING_ACCEPTANCE -> Container.i18n().text(I18n.ASSETS_SCENE_STATUS_VALIDATED_PENDING_ACCEPTANCE);
|
|
||||||
case READY -> Container.i18n().text(I18n.ASSETS_SCENE_STATUS_READY);
|
|
||||||
case VALIDATION_FAILED -> Container.i18n().text(I18n.ASSETS_SCENE_STATUS_VALIDATION_FAILED);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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,7 +1,6 @@
|
|||||||
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;
|
||||||
@ -26,7 +25,6 @@ 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,12 +21,6 @@ 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,9 +8,6 @@ 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;
|
||||||
@ -56,43 +53,14 @@ 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),
|
||||||
typeBox));
|
AssetDetailsUiSupport.createChip(
|
||||||
if (summary.glyphSpecialization() != AssetStudioGlyphSpecialization.NONE) {
|
AssetDetailsUiSupport.typeChipTone(summary.assetFamily()),
|
||||||
content.getChildren().add(AssetDetailsUiSupport.createKeyValueRow(
|
AssetDetailsUiSupport.typeLabel(summary.assetFamily()))));
|
||||||
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_STATUS),
|
|
||||||
AssetDetailsUiSupport.sceneBankStatusLabel(viewState.selectedAssetDetails().sceneBankValidation().status())));
|
|
||||||
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,7 +113,6 @@ 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";
|
||||||
};
|
};
|
||||||
@ -122,7 +121,6 @@ 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,7 +4,6 @@ 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;
|
||||||
@ -21,8 +20,6 @@ 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,
|
|
||||||
AssetWorkspaceSceneBankValidation sceneBankValidation,
|
|
||||||
List<PackerDiagnosticDTO> diagnostics) {
|
List<PackerDiagnosticDTO> diagnostics) {
|
||||||
|
|
||||||
public AssetWorkspaceAssetDetails {
|
public AssetWorkspaceAssetDetails {
|
||||||
@ -36,7 +33,6 @@ public record AssetWorkspaceAssetDetails(
|
|||||||
outputPipeline = Map.copyOf(Objects.requireNonNull(outputPipeline, "outputPipeline"));
|
outputPipeline = Map.copyOf(Objects.requireNonNull(outputPipeline, "outputPipeline"));
|
||||||
bankComposition = Objects.requireNonNull(bankComposition, "bankComposition");
|
bankComposition = Objects.requireNonNull(bankComposition, "bankComposition");
|
||||||
pipelinePalettes = List.copyOf(Objects.requireNonNull(pipelinePalettes, "pipelinePalettes"));
|
pipelinePalettes = List.copyOf(Objects.requireNonNull(pipelinePalettes, "pipelinePalettes"));
|
||||||
sceneBankValidation = Objects.requireNonNullElse(sceneBankValidation, AssetWorkspaceSceneBankValidation.NOT_APPLICABLE);
|
|
||||||
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ 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;
|
||||||
@ -14,7 +13,6 @@ 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) {
|
||||||
@ -25,7 +23,6 @@ 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");
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.messages;
|
|
||||||
|
|
||||||
public enum AssetWorkspaceSceneBankStatus {
|
|
||||||
NOT_APPLICABLE,
|
|
||||||
PENDING_VALIDATION,
|
|
||||||
VALIDATED_PENDING_ACCEPTANCE,
|
|
||||||
READY,
|
|
||||||
VALIDATION_FAILED
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.messages;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record AssetWorkspaceSceneBankValidation(
|
|
||||||
AssetWorkspaceSceneBankStatus status,
|
|
||||||
boolean pendingExternalChanges,
|
|
||||||
boolean canAccept,
|
|
||||||
String currentFingerprint,
|
|
||||||
String validatedFingerprint,
|
|
||||||
String acceptedFingerprint) {
|
|
||||||
|
|
||||||
public static final AssetWorkspaceSceneBankValidation NOT_APPLICABLE = new AssetWorkspaceSceneBankValidation(
|
|
||||||
AssetWorkspaceSceneBankStatus.NOT_APPLICABLE,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
|
|
||||||
public AssetWorkspaceSceneBankValidation {
|
|
||||||
status = Objects.requireNonNullElse(status, AssetWorkspaceSceneBankStatus.NOT_APPLICABLE);
|
|
||||||
currentFingerprint = Objects.requireNonNullElse(currentFingerprint, "");
|
|
||||||
validatedFingerprint = Objects.requireNonNullElse(validatedFingerprint, "");
|
|
||||||
acceptedFingerprint = Objects.requireNonNullElse(acceptedFingerprint, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,108 +0,0 @@
|
|||||||
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 mapWidth = root.path("map_width").asInt(0);
|
|
||||||
final int mapHeight = root.path("map_height").asInt(0);
|
|
||||||
final int layerCount = root.path("layer_count").asInt(0);
|
|
||||||
if (mapWidth <= 0 || mapHeight <= 0 || 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 layerName = layerNode.path("name").asText("").trim();
|
|
||||||
final String tilemap = layerNode.path("tilemap").asText("").trim();
|
|
||||||
final String tilesetAssetRoot = layerNode.path("tileset_asset_root").asText("").trim();
|
|
||||||
if (index < 1 || index > layerCount || layerName.isBlank() || tilemap.isBlank() || !indexes.add(index)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
bindings.add(new AssetStudioSceneLayerBinding(index, layerName, tilemap, tilesetAssetRoot));
|
|
||||||
}
|
|
||||||
bindings.sort(Comparator.comparingInt(AssetStudioSceneLayerBinding::index));
|
|
||||||
return new AssetStudioSceneBankMetadata(mapWidth, mapHeight, layerCount, bindings, supportFile);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.metadata;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record AssetStudioSceneBankMetadata(
|
|
||||||
int mapWidth,
|
|
||||||
int mapHeight,
|
|
||||||
int layerCount,
|
|
||||||
List<AssetStudioSceneLayerBinding> layerBindings,
|
|
||||||
Path supportFile) {
|
|
||||||
|
|
||||||
public AssetStudioSceneBankMetadata {
|
|
||||||
layerBindings = List.copyOf(Objects.requireNonNull(layerBindings, "layerBindings"));
|
|
||||||
supportFile = Objects.requireNonNull(supportFile, "supportFile").toAbsolutePath().normalize();
|
|
||||||
if (mapWidth <= 0 || mapHeight <= 0) {
|
|
||||||
throw new IllegalArgumentException("map dimensions must be positive");
|
|
||||||
}
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.metadata;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record AssetStudioSceneLayerBinding(
|
|
||||||
int index,
|
|
||||||
String layerName,
|
|
||||||
String tilemap,
|
|
||||||
String tilesetAssetRoot) {
|
|
||||||
public AssetStudioSceneLayerBinding {
|
|
||||||
layerName = Objects.requireNonNull(layerName, "layerName").trim();
|
|
||||||
tilemap = Objects.requireNonNull(tilemap, "tilemap").trim();
|
|
||||||
tilesetAssetRoot = Objects.requireNonNull(tilesetAssetRoot, "tilesetAssetRoot").trim();
|
|
||||||
if (index <= 0) {
|
|
||||||
throw new IllegalArgumentException("index must be positive");
|
|
||||||
}
|
|
||||||
if (layerName.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("layerName must not be blank");
|
|
||||||
}
|
|
||||||
if (tilemap.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("tilemap must not be blank");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.scene;
|
|
||||||
|
|
||||||
import p.packer.dtos.PackerDiagnosticDTO;
|
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceSceneBankValidation;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record SceneBankWorkflowResult(
|
|
||||||
boolean success,
|
|
||||||
String message,
|
|
||||||
AssetWorkspaceSceneBankValidation validation,
|
|
||||||
List<PackerDiagnosticDTO> diagnostics) {
|
|
||||||
|
|
||||||
public SceneBankWorkflowResult {
|
|
||||||
message = Objects.requireNonNullElse(message, "");
|
|
||||||
validation = Objects.requireNonNullElse(validation, AssetWorkspaceSceneBankValidation.NOT_APPLICABLE);
|
|
||||||
diagnostics = List.copyOf(Objects.requireNonNullElse(diagnostics, List.of()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,279 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.scene;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import p.packer.dtos.PackerDiagnosticDTO;
|
|
||||||
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
|
||||||
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
|
||||||
import p.studio.projects.ProjectReference;
|
|
||||||
import p.studio.workspaces.assets.metadata.AssetStudioSceneBankMetadata;
|
|
||||||
import p.studio.workspaces.assets.metadata.AssetStudioSceneLayerBinding;
|
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetDetails;
|
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceSceneBankStatus;
|
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceSceneBankValidation;
|
|
||||||
import p.studio.workspaces.assets.tiled.TiledMapDocument;
|
|
||||||
import p.studio.workspaces.assets.tiled.TiledTilesetDocument;
|
|
||||||
import p.studio.workspaces.assets.tiled.TiledUnsupportedFeatureException;
|
|
||||||
import p.studio.workspaces.assets.tiled.TiledXmlCodec;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HexFormat;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public final class SceneBankWorkflowService {
|
|
||||||
private static final String VALIDATION_FILE = "scene-bank.validation.json";
|
|
||||||
private static final String ACCEPTANCE_FILE = "scene-bank.acceptance.json";
|
|
||||||
|
|
||||||
private final ObjectMapper mapper = new ObjectMapper();
|
|
||||||
private final TiledXmlCodec codec = new TiledXmlCodec();
|
|
||||||
|
|
||||||
public SceneBankWorkflowResult inspect(ProjectReference projectReference, AssetWorkspaceAssetDetails details) {
|
|
||||||
if (details == null || details.summary().assetFamily() != p.packer.messages.assets.AssetFamilyCatalog.SCENE_BANK) {
|
|
||||||
return new SceneBankWorkflowResult(true, "", AssetWorkspaceSceneBankValidation.NOT_APPLICABLE, List.of());
|
|
||||||
}
|
|
||||||
final List<PackerDiagnosticDTO> diagnostics = validateDiagnostics(projectReference, details);
|
|
||||||
final String currentFingerprint = diagnostics.stream().anyMatch(PackerDiagnosticDTO::blocking)
|
|
||||||
? ""
|
|
||||||
: computeFingerprint(projectReference, details);
|
|
||||||
final String validatedFingerprint = readFingerprint(details.summary().assetRoot().resolve(VALIDATION_FILE));
|
|
||||||
final String acceptedFingerprint = readFingerprint(details.summary().assetRoot().resolve(ACCEPTANCE_FILE));
|
|
||||||
final AssetWorkspaceSceneBankValidation validation = validationState(
|
|
||||||
diagnostics,
|
|
||||||
currentFingerprint,
|
|
||||||
validatedFingerprint,
|
|
||||||
acceptedFingerprint);
|
|
||||||
return new SceneBankWorkflowResult(
|
|
||||||
diagnostics.stream().noneMatch(PackerDiagnosticDTO::blocking),
|
|
||||||
messageFor(validation),
|
|
||||||
validation,
|
|
||||||
diagnostics);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SceneBankWorkflowResult validate(ProjectReference projectReference, AssetWorkspaceAssetDetails details) {
|
|
||||||
final SceneBankWorkflowResult inspection = inspect(projectReference, details);
|
|
||||||
final Path validationFile = details.summary().assetRoot().resolve(VALIDATION_FILE);
|
|
||||||
try {
|
|
||||||
if (inspection.success() && !inspection.validation().currentFingerprint().isBlank()) {
|
|
||||||
writeFingerprint(validationFile, inspection.validation().currentFingerprint());
|
|
||||||
} else {
|
|
||||||
Files.deleteIfExists(validationFile);
|
|
||||||
}
|
|
||||||
} catch (IOException exception) {
|
|
||||||
return new SceneBankWorkflowResult(
|
|
||||||
false,
|
|
||||||
"Unable to persist validation state: " + exception.getMessage(),
|
|
||||||
inspection.validation(),
|
|
||||||
inspection.diagnostics());
|
|
||||||
}
|
|
||||||
return inspect(projectReference, details);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SceneBankWorkflowResult accept(ProjectReference projectReference, AssetWorkspaceAssetDetails details) {
|
|
||||||
final SceneBankWorkflowResult inspection = inspect(projectReference, details);
|
|
||||||
if (!inspection.validation().canAccept()) {
|
|
||||||
return new SceneBankWorkflowResult(
|
|
||||||
false,
|
|
||||||
"Scene Bank must validate successfully before acceptance.",
|
|
||||||
inspection.validation(),
|
|
||||||
inspection.diagnostics());
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
writeFingerprint(details.summary().assetRoot().resolve(ACCEPTANCE_FILE), inspection.validation().currentFingerprint());
|
|
||||||
writeFingerprint(details.summary().assetRoot().resolve(VALIDATION_FILE), inspection.validation().currentFingerprint());
|
|
||||||
} catch (IOException exception) {
|
|
||||||
return new SceneBankWorkflowResult(
|
|
||||||
false,
|
|
||||||
"Unable to persist acceptance state: " + exception.getMessage(),
|
|
||||||
inspection.validation(),
|
|
||||||
inspection.diagnostics());
|
|
||||||
}
|
|
||||||
return inspect(projectReference, details);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PackerDiagnosticDTO> validateDiagnostics(ProjectReference projectReference, AssetWorkspaceAssetDetails details) {
|
|
||||||
final List<PackerDiagnosticDTO> diagnostics = new ArrayList<>();
|
|
||||||
final AssetStudioSceneBankMetadata metadata = details.sceneBankMetadata();
|
|
||||||
if (metadata == null) {
|
|
||||||
diagnostics.add(error("Scene Bank support metadata is missing or invalid.", details.summary().assetRoot().resolve("scene-bank.studio.json")));
|
|
||||||
return diagnostics;
|
|
||||||
}
|
|
||||||
for (AssetStudioSceneLayerBinding binding : metadata.layerBindings()) {
|
|
||||||
if (binding.tilesetAssetRoot().isBlank()) {
|
|
||||||
diagnostics.add(error("Layer " + binding.index() + " is missing tileset_asset_root.", metadata.supportFile()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final Path tmxPath = details.summary().assetRoot().resolve(binding.tilemap()).toAbsolutePath().normalize();
|
|
||||||
final Path tsxPath = projectReference.rootPath()
|
|
||||||
.resolve("assets")
|
|
||||||
.resolve(binding.tilesetAssetRoot())
|
|
||||||
.resolve("tileset.tsx")
|
|
||||||
.toAbsolutePath()
|
|
||||||
.normalize();
|
|
||||||
if (!Files.isRegularFile(tmxPath)) {
|
|
||||||
diagnostics.add(error("TMX file is missing for layer " + binding.index() + ": " + binding.tilemap(), tmxPath));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!Files.isRegularFile(tsxPath)) {
|
|
||||||
diagnostics.add(error("Referenced TSX file is missing for layer " + binding.index() + ": " + binding.tilesetAssetRoot(), tsxPath));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final TiledTilesetDocument tileset = codec.readTileset(tsxPath);
|
|
||||||
final TiledMapDocument map = codec.readMap(tmxPath);
|
|
||||||
final String expectedSource = details.summary().assetRoot()
|
|
||||||
.toAbsolutePath()
|
|
||||||
.normalize()
|
|
||||||
.relativize(tsxPath)
|
|
||||||
.toString()
|
|
||||||
.replace('\\', '/');
|
|
||||||
if (map.tilesets().size() != 1) {
|
|
||||||
diagnostics.add(error("TMX must reference exactly one TSX in wave 1: " + binding.tilemap(), tmxPath));
|
|
||||||
} else if (!expectedSource.equals(map.tilesets().getFirst().source())) {
|
|
||||||
diagnostics.add(error("TMX tileset reference does not match support metadata: " + binding.tilemap(), tmxPath));
|
|
||||||
}
|
|
||||||
if (map.width() != metadata.mapWidth() || map.height() != metadata.mapHeight()) {
|
|
||||||
diagnostics.add(error("TMX dimensions do not match scene-bank support metadata: " + binding.tilemap(), tmxPath));
|
|
||||||
}
|
|
||||||
if (map.tileWidth() != tileset.tileWidth() || map.tileHeight() != tileset.tileHeight()) {
|
|
||||||
diagnostics.add(error("TMX tile size does not match referenced TSX: " + binding.tilemap(), tmxPath));
|
|
||||||
}
|
|
||||||
if (map.tileLayers().size() != 1 || !binding.layerName().equals(map.tileLayers().getFirst().name())) {
|
|
||||||
diagnostics.add(error("TMX layer mapping does not match scene-bank support metadata: " + binding.tilemap(), tmxPath));
|
|
||||||
}
|
|
||||||
} catch (TiledUnsupportedFeatureException exception) {
|
|
||||||
diagnostics.add(error(exception.getMessage(), tmxPath));
|
|
||||||
} catch (IOException exception) {
|
|
||||||
diagnostics.add(error("Unable to validate Scene Bank XML: " + exception.getMessage(), tmxPath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return List.copyOf(diagnostics);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String computeFingerprint(ProjectReference projectReference, AssetWorkspaceAssetDetails details) {
|
|
||||||
final AssetStudioSceneBankMetadata metadata = details.sceneBankMetadata();
|
|
||||||
if (metadata == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
|
||||||
final List<Path> files = new ArrayList<>();
|
|
||||||
files.add(metadata.supportFile());
|
|
||||||
for (AssetStudioSceneLayerBinding binding : metadata.layerBindings()) {
|
|
||||||
files.add(details.summary().assetRoot().resolve(binding.tilemap()).toAbsolutePath().normalize());
|
|
||||||
if (!binding.tilesetAssetRoot().isBlank()) {
|
|
||||||
files.add(projectReference.rootPath().resolve("assets").resolve(binding.tilesetAssetRoot()).resolve("tileset.tsx").toAbsolutePath().normalize());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
files.stream()
|
|
||||||
.distinct()
|
|
||||||
.sorted(Comparator.comparing(Path::toString))
|
|
||||||
.forEach(path -> updateDigest(digest, path));
|
|
||||||
return HexFormat.of().formatHex(digest.digest());
|
|
||||||
} catch (Exception exception) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDigest(MessageDigest digest, Path path) {
|
|
||||||
try {
|
|
||||||
if (!Files.isRegularFile(path)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
digest.update(path.toString().getBytes(StandardCharsets.UTF_8));
|
|
||||||
digest.update((byte) 0);
|
|
||||||
digest.update(Files.readAllBytes(path));
|
|
||||||
digest.update((byte) 0);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AssetWorkspaceSceneBankValidation validationState(
|
|
||||||
List<PackerDiagnosticDTO> diagnostics,
|
|
||||||
String currentFingerprint,
|
|
||||||
String validatedFingerprint,
|
|
||||||
String acceptedFingerprint) {
|
|
||||||
if (diagnostics.stream().anyMatch(PackerDiagnosticDTO::blocking)) {
|
|
||||||
return new AssetWorkspaceSceneBankValidation(
|
|
||||||
AssetWorkspaceSceneBankStatus.VALIDATION_FAILED,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
currentFingerprint,
|
|
||||||
validatedFingerprint,
|
|
||||||
acceptedFingerprint);
|
|
||||||
}
|
|
||||||
final boolean validated = !currentFingerprint.isBlank() && currentFingerprint.equals(validatedFingerprint);
|
|
||||||
final boolean accepted = validated && currentFingerprint.equals(acceptedFingerprint);
|
|
||||||
if (accepted) {
|
|
||||||
return new AssetWorkspaceSceneBankValidation(
|
|
||||||
AssetWorkspaceSceneBankStatus.READY,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
currentFingerprint,
|
|
||||||
validatedFingerprint,
|
|
||||||
acceptedFingerprint);
|
|
||||||
}
|
|
||||||
if (validated) {
|
|
||||||
return new AssetWorkspaceSceneBankValidation(
|
|
||||||
AssetWorkspaceSceneBankStatus.VALIDATED_PENDING_ACCEPTANCE,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
currentFingerprint,
|
|
||||||
validatedFingerprint,
|
|
||||||
acceptedFingerprint);
|
|
||||||
}
|
|
||||||
return new AssetWorkspaceSceneBankValidation(
|
|
||||||
AssetWorkspaceSceneBankStatus.PENDING_VALIDATION,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
currentFingerprint,
|
|
||||||
validatedFingerprint,
|
|
||||||
acceptedFingerprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String messageFor(AssetWorkspaceSceneBankValidation validation) {
|
|
||||||
return switch (validation.status()) {
|
|
||||||
case NOT_APPLICABLE -> "";
|
|
||||||
case PENDING_VALIDATION -> "Scene Bank has pending external changes and requires validation.";
|
|
||||||
case VALIDATED_PENDING_ACCEPTANCE -> "Scene Bank validation succeeded and is waiting for explicit acceptance.";
|
|
||||||
case READY -> "Scene Bank is ready.";
|
|
||||||
case VALIDATION_FAILED -> "Scene Bank validation failed.";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String readFingerprint(Path path) {
|
|
||||||
if (!Files.isRegularFile(path)) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final JsonNode root = mapper.readTree(path.toFile());
|
|
||||||
return root.path("fingerprint").asText("").trim();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeFingerprint(Path path, String fingerprint) throws IOException {
|
|
||||||
final var root = mapper.createObjectNode();
|
|
||||||
root.put("schema_version", 1);
|
|
||||||
root.put("fingerprint", Objects.requireNonNullElse(fingerprint, ""));
|
|
||||||
root.put("recorded_at", Instant.now().toString());
|
|
||||||
mapper.writerWithDefaultPrettyPrinter().writeValue(path.toFile(), root);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PackerDiagnosticDTO error(String message, Path path) {
|
|
||||||
return new PackerDiagnosticDTO(
|
|
||||||
PackerDiagnosticSeverity.ERROR,
|
|
||||||
PackerDiagnosticCategory.STRUCTURAL,
|
|
||||||
message,
|
|
||||||
path,
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record TiledAssetGenerationResult(
|
|
||||||
boolean success,
|
|
||||||
String message,
|
|
||||||
List<Path> writtenFiles) {
|
|
||||||
|
|
||||||
public TiledAssetGenerationResult {
|
|
||||||
message = Objects.requireNonNullElse(message, "");
|
|
||||||
writtenFiles = List.copyOf(Objects.requireNonNullElse(writtenFiles, List.of()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import p.packer.dtos.PackerCodecConfigurationFieldDTO;
|
|
||||||
import p.packer.messages.assets.AssetFamilyCatalog;
|
|
||||||
import p.studio.projects.ProjectReference;
|
|
||||||
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.AssetWorkspaceAssetDetails;
|
|
||||||
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public final class TiledAssetGenerationService {
|
|
||||||
public static final String GENERATED_TSX_FILE = "tileset.tsx";
|
|
||||||
|
|
||||||
private final TiledXmlCodec codec = new TiledXmlCodec();
|
|
||||||
|
|
||||||
public TiledAssetGenerationResult generateTilesetTsx(AssetWorkspaceAssetDetails details) {
|
|
||||||
if (details == null
|
|
||||||
|| details.summary().assetFamily() != AssetFamilyCatalog.GLYPH_BANK
|
|
||||||
|| details.summary().glyphSpecialization() != AssetStudioGlyphSpecialization.TILESET) {
|
|
||||||
return new TiledAssetGenerationResult(false, "TSX generation is available only for Tileset-specialized glyph banks.", List.of());
|
|
||||||
}
|
|
||||||
final List<AssetWorkspaceBankCompositionFile> selectedFiles = details.bankComposition().selectedFiles();
|
|
||||||
if (selectedFiles.isEmpty()) {
|
|
||||||
return new TiledAssetGenerationResult(false, "Select at least one glyph artifact before generating TSX.", List.of());
|
|
||||||
}
|
|
||||||
final int tileSize = parseTileSize(details.metadataFields());
|
|
||||||
final List<TiledTilesetTile> tiles = new ArrayList<>();
|
|
||||||
for (int index = 0; index < selectedFiles.size(); index += 1) {
|
|
||||||
final AssetWorkspaceBankCompositionFile file = selectedFiles.get(index);
|
|
||||||
tiles.add(new TiledTilesetTile(
|
|
||||||
index,
|
|
||||||
file.path(),
|
|
||||||
tileSize,
|
|
||||||
tileSize,
|
|
||||||
List.of(new TiledProperty("glyph_id", "int", Integer.toString(index))),
|
|
||||||
null));
|
|
||||||
}
|
|
||||||
final TiledTilesetDocument document = new TiledTilesetDocument(
|
|
||||||
"1.10",
|
|
||||||
"1.12.1",
|
|
||||||
details.summary().assetName(),
|
|
||||||
tileSize,
|
|
||||||
tileSize,
|
|
||||||
tiles.size(),
|
|
||||||
0,
|
|
||||||
List.of(),
|
|
||||||
tiles);
|
|
||||||
final Path outputPath = details.summary().assetRoot().resolve(GENERATED_TSX_FILE);
|
|
||||||
try {
|
|
||||||
codec.writeTileset(outputPath, document);
|
|
||||||
return new TiledAssetGenerationResult(true, "Generated TSX successfully.", List.of(outputPath));
|
|
||||||
} catch (IOException exception) {
|
|
||||||
return new TiledAssetGenerationResult(false, "Unable to generate TSX: " + exception.getMessage(), List.of());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TiledAssetGenerationResult generateSceneBankTilemaps(ProjectReference projectReference, AssetWorkspaceAssetDetails details) {
|
|
||||||
if (details == null || details.summary().assetFamily() != AssetFamilyCatalog.SCENE_BANK) {
|
|
||||||
return new TiledAssetGenerationResult(false, "TMX generation is available only for Scene Bank assets.", List.of());
|
|
||||||
}
|
|
||||||
final AssetStudioSceneBankMetadata metadata = details.sceneBankMetadata();
|
|
||||||
if (metadata == null) {
|
|
||||||
return new TiledAssetGenerationResult(false, "Scene Bank support metadata is missing or invalid.", List.of());
|
|
||||||
}
|
|
||||||
final List<Path> writtenFiles = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
for (AssetStudioSceneLayerBinding binding : metadata.layerBindings()) {
|
|
||||||
final String tilesetAssetRoot = Objects.requireNonNullElse(binding.tilesetAssetRoot(), "").trim();
|
|
||||||
if (tilesetAssetRoot.isBlank()) {
|
|
||||||
return new TiledAssetGenerationResult(
|
|
||||||
false,
|
|
||||||
"Layer " + binding.index() + " must declare a non-blank tileset_asset_root before TMX generation.",
|
|
||||||
List.of());
|
|
||||||
}
|
|
||||||
final Path tilesetPath = projectReference.rootPath()
|
|
||||||
.resolve("assets")
|
|
||||||
.resolve(tilesetAssetRoot)
|
|
||||||
.resolve(GENERATED_TSX_FILE)
|
|
||||||
.toAbsolutePath()
|
|
||||||
.normalize();
|
|
||||||
if (!java.nio.file.Files.isRegularFile(tilesetPath)) {
|
|
||||||
return new TiledAssetGenerationResult(
|
|
||||||
false,
|
|
||||||
"Referenced TSX was not found for layer " + binding.index() + ": " + tilesetAssetRoot,
|
|
||||||
List.of());
|
|
||||||
}
|
|
||||||
final TiledTilesetDocument tileset = codec.readTileset(tilesetPath);
|
|
||||||
final String relativeTilesetPath = details.summary().assetRoot()
|
|
||||||
.toAbsolutePath()
|
|
||||||
.normalize()
|
|
||||||
.relativize(tilesetPath)
|
|
||||||
.toString()
|
|
||||||
.replace('\\', '/');
|
|
||||||
final int tileCount = metadata.mapWidth() * metadata.mapHeight();
|
|
||||||
final List<Long> gids = new ArrayList<>(tileCount);
|
|
||||||
for (int index = 0; index < tileCount; index += 1) {
|
|
||||||
gids.add(0L);
|
|
||||||
}
|
|
||||||
final TiledMapDocument document = new TiledMapDocument(
|
|
||||||
"1.10",
|
|
||||||
"1.12.1",
|
|
||||||
"orthogonal",
|
|
||||||
"right-down",
|
|
||||||
metadata.mapWidth(),
|
|
||||||
metadata.mapHeight(),
|
|
||||||
tileset.tileWidth(),
|
|
||||||
tileset.tileHeight(),
|
|
||||||
2,
|
|
||||||
1,
|
|
||||||
List.of(),
|
|
||||||
List.of(new TiledTilesetReference(1, relativeTilesetPath)),
|
|
||||||
List.of(new TiledTileLayer(
|
|
||||||
1,
|
|
||||||
binding.layerName(),
|
|
||||||
metadata.mapWidth(),
|
|
||||||
metadata.mapHeight(),
|
|
||||||
gids,
|
|
||||||
List.of())),
|
|
||||||
List.of());
|
|
||||||
final Path outputPath = details.summary().assetRoot().resolve(binding.tilemap());
|
|
||||||
codec.writeMap(outputPath, document);
|
|
||||||
writtenFiles.add(outputPath);
|
|
||||||
}
|
|
||||||
return new TiledAssetGenerationResult(true, "Generated TMX successfully.", writtenFiles);
|
|
||||||
} catch (TiledUnsupportedFeatureException | IOException exception) {
|
|
||||||
return new TiledAssetGenerationResult(false, "Unable to generate TMX: " + exception.getMessage(), List.of());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int parseTileSize(List<PackerCodecConfigurationFieldDTO> metadataFields) {
|
|
||||||
final String value = metadataFields.stream()
|
|
||||||
.filter(field -> "tile_size".equals(field.key()))
|
|
||||||
.map(PackerCodecConfigurationFieldDTO::value)
|
|
||||||
.findFirst()
|
|
||||||
.orElse("16x16");
|
|
||||||
final String normalized = value.trim().toLowerCase();
|
|
||||||
if (normalized.startsWith("8x8")) {
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
if (normalized.startsWith("32x32")) {
|
|
||||||
return 32;
|
|
||||||
}
|
|
||||||
return 16;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record TiledMapDocument(
|
|
||||||
String version,
|
|
||||||
String tiledVersion,
|
|
||||||
String orientation,
|
|
||||||
String renderOrder,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int tileWidth,
|
|
||||||
int tileHeight,
|
|
||||||
int nextLayerId,
|
|
||||||
int nextObjectId,
|
|
||||||
List<TiledProperty> properties,
|
|
||||||
List<TiledTilesetReference> tilesets,
|
|
||||||
List<TiledTileLayer> tileLayers,
|
|
||||||
List<TiledObjectLayer> objectLayers) {
|
|
||||||
|
|
||||||
public TiledMapDocument {
|
|
||||||
version = Objects.requireNonNullElse(version, "1.10");
|
|
||||||
tiledVersion = Objects.requireNonNullElse(tiledVersion, "1.12.1");
|
|
||||||
orientation = Objects.requireNonNullElse(orientation, "orthogonal");
|
|
||||||
renderOrder = Objects.requireNonNullElse(renderOrder, "right-down");
|
|
||||||
properties = List.copyOf(Objects.requireNonNull(properties, "properties"));
|
|
||||||
tilesets = List.copyOf(Objects.requireNonNull(tilesets, "tilesets"));
|
|
||||||
tileLayers = List.copyOf(Objects.requireNonNull(tileLayers, "tileLayers"));
|
|
||||||
objectLayers = List.copyOf(Objects.requireNonNull(objectLayers, "objectLayers"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record TiledObjectData(
|
|
||||||
int id,
|
|
||||||
String name,
|
|
||||||
double x,
|
|
||||||
double y,
|
|
||||||
double width,
|
|
||||||
double height,
|
|
||||||
List<TiledPoint> polygonPoints,
|
|
||||||
List<TiledProperty> properties) {
|
|
||||||
|
|
||||||
public TiledObjectData {
|
|
||||||
name = Objects.requireNonNullElse(name, "").trim();
|
|
||||||
polygonPoints = List.copyOf(Objects.requireNonNull(polygonPoints, "polygonPoints"));
|
|
||||||
properties = List.copyOf(Objects.requireNonNull(properties, "properties"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record TiledObjectLayer(
|
|
||||||
int id,
|
|
||||||
String name,
|
|
||||||
List<TiledObjectData> objects,
|
|
||||||
List<TiledProperty> properties) {
|
|
||||||
|
|
||||||
public TiledObjectLayer {
|
|
||||||
name = Objects.requireNonNullElse(name, "").trim();
|
|
||||||
objects = List.copyOf(Objects.requireNonNull(objects, "objects"));
|
|
||||||
properties = List.copyOf(Objects.requireNonNull(properties, "properties"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
public record TiledPoint(double x, double y) {
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record TiledProperty(String name, String type, String value) {
|
|
||||||
public TiledProperty {
|
|
||||||
name = Objects.requireNonNull(name, "name").trim();
|
|
||||||
type = Objects.requireNonNullElse(type, "string").trim();
|
|
||||||
value = Objects.requireNonNullElse(value, "").trim();
|
|
||||||
if (name.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("name must not be blank");
|
|
||||||
}
|
|
||||||
if (type.isBlank()) {
|
|
||||||
type = "string";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record TiledTileLayer(
|
|
||||||
int id,
|
|
||||||
String name,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
List<Long> gids,
|
|
||||||
List<TiledProperty> properties) {
|
|
||||||
|
|
||||||
public TiledTileLayer {
|
|
||||||
name = Objects.requireNonNullElse(name, "").trim();
|
|
||||||
gids = List.copyOf(Objects.requireNonNull(gids, "gids"));
|
|
||||||
properties = List.copyOf(Objects.requireNonNull(properties, "properties"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record TiledTilesetDocument(
|
|
||||||
String version,
|
|
||||||
String tiledVersion,
|
|
||||||
String name,
|
|
||||||
int tileWidth,
|
|
||||||
int tileHeight,
|
|
||||||
int tileCount,
|
|
||||||
int columns,
|
|
||||||
List<TiledProperty> properties,
|
|
||||||
List<TiledTilesetTile> tiles) {
|
|
||||||
|
|
||||||
public TiledTilesetDocument {
|
|
||||||
version = Objects.requireNonNullElse(version, "1.10");
|
|
||||||
tiledVersion = Objects.requireNonNullElse(tiledVersion, "1.12.1");
|
|
||||||
name = Objects.requireNonNull(name, "name").trim();
|
|
||||||
properties = List.copyOf(Objects.requireNonNull(properties, "properties"));
|
|
||||||
tiles = List.copyOf(Objects.requireNonNull(tiles, "tiles"));
|
|
||||||
if (name.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("name must not be blank");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record TiledTilesetReference(int firstGid, String source) {
|
|
||||||
public TiledTilesetReference {
|
|
||||||
source = Objects.requireNonNull(source, "source").trim();
|
|
||||||
if (firstGid <= 0) {
|
|
||||||
throw new IllegalArgumentException("firstGid must be positive");
|
|
||||||
}
|
|
||||||
if (source.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("source must not be blank");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record TiledTilesetTile(
|
|
||||||
int id,
|
|
||||||
String imageSource,
|
|
||||||
int imageWidth,
|
|
||||||
int imageHeight,
|
|
||||||
List<TiledProperty> properties,
|
|
||||||
TiledObjectLayer collisionLayer) {
|
|
||||||
|
|
||||||
public TiledTilesetTile {
|
|
||||||
imageSource = Objects.requireNonNull(imageSource, "imageSource").trim();
|
|
||||||
properties = List.copyOf(Objects.requireNonNull(properties, "properties"));
|
|
||||||
if (imageSource.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("imageSource must not be blank");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
public final class TiledUnsupportedFeatureException extends RuntimeException {
|
|
||||||
public TiledUnsupportedFeatureException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,460 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.transform.OutputKeys;
|
|
||||||
import javax.xml.transform.TransformerFactory;
|
|
||||||
import javax.xml.transform.dom.DOMSource;
|
|
||||||
import javax.xml.transform.stream.StreamResult;
|
|
||||||
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.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public final class TiledXmlCodec {
|
|
||||||
public TiledMapDocument readMap(Path path) throws IOException {
|
|
||||||
final Document document = parseDocument(path);
|
|
||||||
final Element root = document.getDocumentElement();
|
|
||||||
if (!"map".equals(root.getTagName())) {
|
|
||||||
throw new IOException("TMX root element must be <map>.");
|
|
||||||
}
|
|
||||||
if (!"0".equals(root.getAttribute("infinite")) && !root.getAttribute("infinite").isBlank()) {
|
|
||||||
throw new TiledUnsupportedFeatureException("Infinite maps are not supported in wave 1.");
|
|
||||||
}
|
|
||||||
rejectMapUnsupportedChildren(root);
|
|
||||||
return new TiledMapDocument(
|
|
||||||
root.getAttribute("version"),
|
|
||||||
root.getAttribute("tiledversion"),
|
|
||||||
root.getAttribute("orientation"),
|
|
||||||
root.getAttribute("renderorder"),
|
|
||||||
intAttribute(root, "width", 0),
|
|
||||||
intAttribute(root, "height", 0),
|
|
||||||
intAttribute(root, "tilewidth", 0),
|
|
||||||
intAttribute(root, "tileheight", 0),
|
|
||||||
intAttribute(root, "nextlayerid", 0),
|
|
||||||
intAttribute(root, "nextobjectid", 0),
|
|
||||||
readProperties(child(root, "properties")),
|
|
||||||
readTilesets(root),
|
|
||||||
readTileLayers(root),
|
|
||||||
readObjectLayers(root));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeMap(Path path, TiledMapDocument map) throws IOException {
|
|
||||||
final Document document = newDocument();
|
|
||||||
final Element root = document.createElement("map");
|
|
||||||
document.appendChild(root);
|
|
||||||
root.setAttribute("version", map.version());
|
|
||||||
root.setAttribute("tiledversion", map.tiledVersion());
|
|
||||||
root.setAttribute("orientation", map.orientation());
|
|
||||||
root.setAttribute("renderorder", map.renderOrder());
|
|
||||||
root.setAttribute("width", Integer.toString(map.width()));
|
|
||||||
root.setAttribute("height", Integer.toString(map.height()));
|
|
||||||
root.setAttribute("tilewidth", Integer.toString(map.tileWidth()));
|
|
||||||
root.setAttribute("tileheight", Integer.toString(map.tileHeight()));
|
|
||||||
root.setAttribute("infinite", "0");
|
|
||||||
root.setAttribute("nextlayerid", Integer.toString(map.nextLayerId()));
|
|
||||||
root.setAttribute("nextobjectid", Integer.toString(map.nextObjectId()));
|
|
||||||
appendProperties(document, root, map.properties());
|
|
||||||
for (TiledTilesetReference tileset : map.tilesets()) {
|
|
||||||
final Element tilesetElement = document.createElement("tileset");
|
|
||||||
tilesetElement.setAttribute("firstgid", Integer.toString(tileset.firstGid()));
|
|
||||||
tilesetElement.setAttribute("source", tileset.source());
|
|
||||||
root.appendChild(tilesetElement);
|
|
||||||
}
|
|
||||||
for (TiledTileLayer layer : map.tileLayers()) {
|
|
||||||
final Element layerElement = document.createElement("layer");
|
|
||||||
layerElement.setAttribute("id", Integer.toString(layer.id()));
|
|
||||||
layerElement.setAttribute("name", layer.name());
|
|
||||||
layerElement.setAttribute("width", Integer.toString(layer.width()));
|
|
||||||
layerElement.setAttribute("height", Integer.toString(layer.height()));
|
|
||||||
appendProperties(document, layerElement, layer.properties());
|
|
||||||
final Element dataElement = document.createElement("data");
|
|
||||||
dataElement.setAttribute("encoding", "csv");
|
|
||||||
dataElement.setTextContent(csv(layer.gids(), layer.width()));
|
|
||||||
layerElement.appendChild(dataElement);
|
|
||||||
root.appendChild(layerElement);
|
|
||||||
}
|
|
||||||
for (TiledObjectLayer layer : map.objectLayers()) {
|
|
||||||
root.appendChild(writeObjectLayer(document, layer, false));
|
|
||||||
}
|
|
||||||
writeDocument(path, document);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TiledTilesetDocument readTileset(Path path) throws IOException {
|
|
||||||
final Document document = parseDocument(path);
|
|
||||||
final Element root = document.getDocumentElement();
|
|
||||||
if (!"tileset".equals(root.getTagName())) {
|
|
||||||
throw new IOException("TSX root element must be <tileset>.");
|
|
||||||
}
|
|
||||||
rejectTilesetUnsupportedChildren(root);
|
|
||||||
final List<TiledTilesetTile> tiles = new ArrayList<>();
|
|
||||||
for (Element tileElement : children(root, "tile")) {
|
|
||||||
if (child(tileElement, "animation") != null) {
|
|
||||||
throw new TiledUnsupportedFeatureException("Tiled animations are not supported in wave 1.");
|
|
||||||
}
|
|
||||||
final Element imageElement = child(tileElement, "image");
|
|
||||||
if (imageElement == null) {
|
|
||||||
throw new IOException("TSX tile must contain an <image> element.");
|
|
||||||
}
|
|
||||||
tiles.add(new TiledTilesetTile(
|
|
||||||
intAttribute(tileElement, "id", 0),
|
|
||||||
imageElement.getAttribute("source"),
|
|
||||||
intAttribute(imageElement, "width", 0),
|
|
||||||
intAttribute(imageElement, "height", 0),
|
|
||||||
readProperties(child(tileElement, "properties")),
|
|
||||||
readEmbeddedObjectLayer(tileElement)));
|
|
||||||
}
|
|
||||||
tiles.sort(Comparator.comparingInt(TiledTilesetTile::id));
|
|
||||||
return new TiledTilesetDocument(
|
|
||||||
root.getAttribute("version"),
|
|
||||||
root.getAttribute("tiledversion"),
|
|
||||||
root.getAttribute("name"),
|
|
||||||
intAttribute(root, "tilewidth", 0),
|
|
||||||
intAttribute(root, "tileheight", 0),
|
|
||||||
intAttribute(root, "tilecount", tiles.size()),
|
|
||||||
intAttribute(root, "columns", 0),
|
|
||||||
readProperties(child(root, "properties")),
|
|
||||||
tiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeTileset(Path path, TiledTilesetDocument tileset) throws IOException {
|
|
||||||
final Document document = newDocument();
|
|
||||||
final Element root = document.createElement("tileset");
|
|
||||||
document.appendChild(root);
|
|
||||||
root.setAttribute("version", tileset.version());
|
|
||||||
root.setAttribute("tiledversion", tileset.tiledVersion());
|
|
||||||
root.setAttribute("name", tileset.name());
|
|
||||||
root.setAttribute("tilewidth", Integer.toString(tileset.tileWidth()));
|
|
||||||
root.setAttribute("tileheight", Integer.toString(tileset.tileHeight()));
|
|
||||||
root.setAttribute("tilecount", Integer.toString(tileset.tileCount()));
|
|
||||||
root.setAttribute("columns", Integer.toString(tileset.columns()));
|
|
||||||
final Element grid = document.createElement("grid");
|
|
||||||
grid.setAttribute("orientation", "orthogonal");
|
|
||||||
grid.setAttribute("width", "1");
|
|
||||||
grid.setAttribute("height", "1");
|
|
||||||
root.appendChild(grid);
|
|
||||||
appendProperties(document, root, tileset.properties());
|
|
||||||
for (TiledTilesetTile tile : tileset.tiles()) {
|
|
||||||
final Element tileElement = document.createElement("tile");
|
|
||||||
tileElement.setAttribute("id", Integer.toString(tile.id()));
|
|
||||||
appendProperties(document, tileElement, tile.properties());
|
|
||||||
final Element imageElement = document.createElement("image");
|
|
||||||
imageElement.setAttribute("source", tile.imageSource());
|
|
||||||
imageElement.setAttribute("width", Integer.toString(tile.imageWidth()));
|
|
||||||
imageElement.setAttribute("height", Integer.toString(tile.imageHeight()));
|
|
||||||
tileElement.appendChild(imageElement);
|
|
||||||
if (tile.collisionLayer() != null) {
|
|
||||||
tileElement.appendChild(writeObjectLayer(document, tile.collisionLayer(), true));
|
|
||||||
}
|
|
||||||
root.appendChild(tileElement);
|
|
||||||
}
|
|
||||||
writeDocument(path, document);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Document parseDocument(Path path) throws IOException {
|
|
||||||
try {
|
|
||||||
final var factory = DocumentBuilderFactory.newInstance();
|
|
||||||
factory.setNamespaceAware(false);
|
|
||||||
return factory.newDocumentBuilder().parse(path.toFile());
|
|
||||||
} catch (TiledUnsupportedFeatureException exception) {
|
|
||||||
throw exception;
|
|
||||||
} catch (Exception exception) {
|
|
||||||
throw new IOException("Unable to parse Tiled XML: " + exception.getMessage(), exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Document newDocument() throws IOException {
|
|
||||||
try {
|
|
||||||
return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
|
|
||||||
} catch (Exception exception) {
|
|
||||||
throw new IOException("Unable to create XML document: " + exception.getMessage(), exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeDocument(Path path, Document document) throws IOException {
|
|
||||||
try {
|
|
||||||
Files.createDirectories(Objects.requireNonNull(path, "path").toAbsolutePath().normalize().getParent());
|
|
||||||
final var transformer = TransformerFactory.newInstance().newTransformer();
|
|
||||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
|
||||||
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
|
|
||||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
|
||||||
transformer.transform(new DOMSource(document), new StreamResult(path.toFile()));
|
|
||||||
} catch (Exception exception) {
|
|
||||||
throw new IOException("Unable to write Tiled XML: " + exception.getMessage(), exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rejectMapUnsupportedChildren(Element root) {
|
|
||||||
for (Element child : childElements(root)) {
|
|
||||||
final String tag = child.getTagName();
|
|
||||||
if ("imagelayer".equals(tag) || "group".equals(tag) || "template".equals(tag)) {
|
|
||||||
throw new TiledUnsupportedFeatureException("Unsupported TMX feature in wave 1: " + tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rejectTilesetUnsupportedChildren(Element root) {
|
|
||||||
for (Element child : childElements(root)) {
|
|
||||||
final String tag = child.getTagName();
|
|
||||||
if ("wangsets".equals(tag) || "tileoffset".equals(tag) || "transformations".equals(tag)) {
|
|
||||||
throw new TiledUnsupportedFeatureException("Unsupported TSX feature in wave 1: " + tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TiledTilesetReference> readTilesets(Element root) {
|
|
||||||
final List<TiledTilesetReference> tilesets = new ArrayList<>();
|
|
||||||
for (Element tilesetElement : children(root, "tileset")) {
|
|
||||||
if (!tilesetElement.hasAttribute("source")) {
|
|
||||||
throw new TiledUnsupportedFeatureException("Inline tilesets are not supported in wave 1.");
|
|
||||||
}
|
|
||||||
tilesets.add(new TiledTilesetReference(
|
|
||||||
intAttribute(tilesetElement, "firstgid", 1),
|
|
||||||
tilesetElement.getAttribute("source")));
|
|
||||||
}
|
|
||||||
return List.copyOf(tilesets);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TiledTileLayer> readTileLayers(Element root) {
|
|
||||||
final List<TiledTileLayer> layers = new ArrayList<>();
|
|
||||||
for (Element layerElement : children(root, "layer")) {
|
|
||||||
final Element dataElement = child(layerElement, "data");
|
|
||||||
if (dataElement == null || !"csv".equalsIgnoreCase(dataElement.getAttribute("encoding"))) {
|
|
||||||
throw new TiledUnsupportedFeatureException("Wave 1 supports only CSV tile layer encoding.");
|
|
||||||
}
|
|
||||||
layers.add(new TiledTileLayer(
|
|
||||||
intAttribute(layerElement, "id", 0),
|
|
||||||
layerElement.getAttribute("name"),
|
|
||||||
intAttribute(layerElement, "width", 0),
|
|
||||||
intAttribute(layerElement, "height", 0),
|
|
||||||
parseCsvData(dataElement.getTextContent()),
|
|
||||||
readProperties(child(layerElement, "properties"))));
|
|
||||||
}
|
|
||||||
return List.copyOf(layers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TiledObjectLayer> readObjectLayers(Element root) {
|
|
||||||
final List<TiledObjectLayer> layers = new ArrayList<>();
|
|
||||||
for (Element objectGroup : children(root, "objectgroup")) {
|
|
||||||
layers.add(readObjectLayer(objectGroup));
|
|
||||||
}
|
|
||||||
return List.copyOf(layers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TiledObjectLayer readEmbeddedObjectLayer(Element parent) {
|
|
||||||
final Element objectGroup = child(parent, "objectgroup");
|
|
||||||
return objectGroup == null ? null : readObjectLayer(objectGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TiledObjectLayer readObjectLayer(Element objectGroup) {
|
|
||||||
final List<TiledObjectData> objects = new ArrayList<>();
|
|
||||||
for (Element objectElement : children(objectGroup, "object")) {
|
|
||||||
final Element polygon = child(objectElement, "polygon");
|
|
||||||
if (child(objectElement, "ellipse") != null || child(objectElement, "point") != null || child(objectElement, "polyline") != null) {
|
|
||||||
throw new TiledUnsupportedFeatureException("Unsupported object-layer geometry in wave 1.");
|
|
||||||
}
|
|
||||||
objects.add(new TiledObjectData(
|
|
||||||
intAttribute(objectElement, "id", 0),
|
|
||||||
objectElement.getAttribute("name"),
|
|
||||||
doubleAttribute(objectElement, "x", 0.0d),
|
|
||||||
doubleAttribute(objectElement, "y", 0.0d),
|
|
||||||
doubleAttribute(objectElement, "width", 0.0d),
|
|
||||||
doubleAttribute(objectElement, "height", 0.0d),
|
|
||||||
polygon == null ? List.of() : parsePolygon(polygon.getAttribute("points")),
|
|
||||||
readProperties(child(objectElement, "properties"))));
|
|
||||||
}
|
|
||||||
return new TiledObjectLayer(
|
|
||||||
intAttribute(objectGroup, "id", 0),
|
|
||||||
objectGroup.getAttribute("name"),
|
|
||||||
objects,
|
|
||||||
readProperties(child(objectGroup, "properties")));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Element writeObjectLayer(Document document, TiledObjectLayer layer, boolean embedded) {
|
|
||||||
final Element objectGroup = document.createElement("objectgroup");
|
|
||||||
if (layer.id() > 0) {
|
|
||||||
objectGroup.setAttribute("id", Integer.toString(layer.id()));
|
|
||||||
}
|
|
||||||
if (!layer.name().isBlank()) {
|
|
||||||
objectGroup.setAttribute("name", layer.name());
|
|
||||||
}
|
|
||||||
if (embedded) {
|
|
||||||
objectGroup.setAttribute("draworder", "index");
|
|
||||||
}
|
|
||||||
appendProperties(document, objectGroup, layer.properties());
|
|
||||||
for (TiledObjectData object : layer.objects()) {
|
|
||||||
final Element objectElement = document.createElement("object");
|
|
||||||
if (object.id() > 0) {
|
|
||||||
objectElement.setAttribute("id", Integer.toString(object.id()));
|
|
||||||
}
|
|
||||||
if (!object.name().isBlank()) {
|
|
||||||
objectElement.setAttribute("name", object.name());
|
|
||||||
}
|
|
||||||
objectElement.setAttribute("x", formatDecimal(object.x()));
|
|
||||||
objectElement.setAttribute("y", formatDecimal(object.y()));
|
|
||||||
if (object.width() > 0.0d) {
|
|
||||||
objectElement.setAttribute("width", formatDecimal(object.width()));
|
|
||||||
}
|
|
||||||
if (object.height() > 0.0d) {
|
|
||||||
objectElement.setAttribute("height", formatDecimal(object.height()));
|
|
||||||
}
|
|
||||||
appendProperties(document, objectElement, object.properties());
|
|
||||||
if (!object.polygonPoints().isEmpty()) {
|
|
||||||
final Element polygon = document.createElement("polygon");
|
|
||||||
polygon.setAttribute("points", polygonPoints(object.polygonPoints()));
|
|
||||||
objectElement.appendChild(polygon);
|
|
||||||
}
|
|
||||||
objectGroup.appendChild(objectElement);
|
|
||||||
}
|
|
||||||
return objectGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendProperties(Document document, Element parent, List<TiledProperty> properties) {
|
|
||||||
if (properties.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final Element propertiesElement = document.createElement("properties");
|
|
||||||
for (TiledProperty property : properties) {
|
|
||||||
final Element propertyElement = document.createElement("property");
|
|
||||||
propertyElement.setAttribute("name", property.name());
|
|
||||||
if (!"string".equals(property.type())) {
|
|
||||||
propertyElement.setAttribute("type", property.type());
|
|
||||||
}
|
|
||||||
propertyElement.setAttribute("value", property.value());
|
|
||||||
propertiesElement.appendChild(propertyElement);
|
|
||||||
}
|
|
||||||
parent.appendChild(propertiesElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TiledProperty> readProperties(Element propertiesElement) {
|
|
||||||
if (propertiesElement == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final List<TiledProperty> properties = new ArrayList<>();
|
|
||||||
for (Element propertyElement : children(propertiesElement, "property")) {
|
|
||||||
properties.add(new TiledProperty(
|
|
||||||
propertyElement.getAttribute("name"),
|
|
||||||
propertyElement.getAttribute("type"),
|
|
||||||
propertyElement.hasAttribute("value")
|
|
||||||
? propertyElement.getAttribute("value")
|
|
||||||
: propertyElement.getTextContent()));
|
|
||||||
}
|
|
||||||
return List.copyOf(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Long> parseCsvData(String text) {
|
|
||||||
final List<Long> gids = new ArrayList<>();
|
|
||||||
for (String token : Objects.requireNonNullElse(text, "").split(",")) {
|
|
||||||
final String normalized = token.trim();
|
|
||||||
if (normalized.isBlank()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
gids.add(Long.parseLong(normalized));
|
|
||||||
}
|
|
||||||
return List.copyOf(gids);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TiledPoint> parsePolygon(String points) {
|
|
||||||
final List<TiledPoint> parsed = new ArrayList<>();
|
|
||||||
for (String segment : Objects.requireNonNullElse(points, "").trim().split(" ")) {
|
|
||||||
final String normalized = segment.trim();
|
|
||||||
if (normalized.isBlank()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final String[] pair = normalized.split(",");
|
|
||||||
if (pair.length != 2) {
|
|
||||||
throw new TiledUnsupportedFeatureException("Invalid polygon point format in Tiled XML.");
|
|
||||||
}
|
|
||||||
parsed.add(new TiledPoint(Double.parseDouble(pair[0]), Double.parseDouble(pair[1])));
|
|
||||||
}
|
|
||||||
return List.copyOf(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String csv(List<Long> gids, int width) {
|
|
||||||
final StringBuilder builder = new StringBuilder();
|
|
||||||
for (int index = 0; index < gids.size(); index += 1) {
|
|
||||||
if (index > 0) {
|
|
||||||
builder.append(index % Math.max(width, 1) == 0 ? ",\n" : ",");
|
|
||||||
}
|
|
||||||
builder.append(Long.toUnsignedString(gids.get(index)));
|
|
||||||
}
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String polygonPoints(List<TiledPoint> points) {
|
|
||||||
final StringBuilder builder = new StringBuilder();
|
|
||||||
for (int index = 0; index < points.size(); index += 1) {
|
|
||||||
if (index > 0) {
|
|
||||||
builder.append(' ');
|
|
||||||
}
|
|
||||||
builder.append(formatDecimal(points.get(index).x()))
|
|
||||||
.append(',')
|
|
||||||
.append(formatDecimal(points.get(index).y()));
|
|
||||||
}
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int intAttribute(Element element, String attribute, int fallback) {
|
|
||||||
final String value = element.getAttribute(attribute);
|
|
||||||
if (value == null || value.isBlank()) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
return Integer.parseInt(value.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
private double doubleAttribute(Element element, String attribute, double fallback) {
|
|
||||||
final String value = element.getAttribute(attribute);
|
|
||||||
if (value == null || value.isBlank()) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
return Double.parseDouble(value.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String formatDecimal(double value) {
|
|
||||||
if (Math.rint(value) == value) {
|
|
||||||
return Long.toString(Math.round(value));
|
|
||||||
}
|
|
||||||
return String.format(Locale.ROOT, "%.6f", value)
|
|
||||||
.replaceAll("0+$", "")
|
|
||||||
.replaceAll("\\.$", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Element child(Element parent, String name) {
|
|
||||||
for (Element child : childElements(parent)) {
|
|
||||||
if (name.equals(child.getTagName())) {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Element> children(Element parent, String name) {
|
|
||||||
final List<Element> elements = new ArrayList<>();
|
|
||||||
for (Element child : childElements(parent)) {
|
|
||||||
if (name.equals(child.getTagName())) {
|
|
||||||
elements.add(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return List.copyOf(elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Element> childElements(Element parent) {
|
|
||||||
final NodeList children = parent.getChildNodes();
|
|
||||||
final List<Element> elements = new ArrayList<>();
|
|
||||||
for (int index = 0; index < children.getLength(); index += 1) {
|
|
||||||
final Node node = children.item(index);
|
|
||||||
if (node instanceof Element element) {
|
|
||||||
elements.add(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return List.copyOf(elements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -24,8 +24,6 @@ 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;
|
||||||
@ -49,11 +47,9 @@ 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;
|
||||||
@ -69,7 +65,6 @@ public final class AddAssetWizard {
|
|||||||
|
|
||||||
preloadCheckBox.setSelected(false);
|
preloadCheckBox.setSelected(false);
|
||||||
configureAssetFamilyCombo();
|
configureAssetFamilyCombo();
|
||||||
configureGlyphSpecializationCombo();
|
|
||||||
configureOutputFormatCombo();
|
configureOutputFormatCombo();
|
||||||
configureOutputCodecCombo();
|
configureOutputCodecCombo();
|
||||||
renderStep();
|
renderStep();
|
||||||
@ -136,34 +131,7 @@ 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));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -256,23 +224,17 @@ 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),
|
||||||
@ -398,15 +360,6 @@ 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;
|
||||||
@ -456,17 +409,6 @@ 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()) {
|
||||||
|
|||||||
@ -144,10 +144,6 @@ assets.section.actions=Actions
|
|||||||
assets.actions.empty=No actions available for this asset.
|
assets.actions.empty=No actions available for this asset.
|
||||||
assets.action.register=Register
|
assets.action.register=Register
|
||||||
assets.action.analyse=Analyse
|
assets.action.analyse=Analyse
|
||||||
assets.action.generateTsx=Generate TSX
|
|
||||||
assets.action.generateTmx=Generate TMX
|
|
||||||
assets.action.validateSceneBank=Validate Scene Bank
|
|
||||||
assets.action.acceptSceneBank=Accept Scene Bank
|
|
||||||
assets.action.delete=Delete
|
assets.action.delete=Delete
|
||||||
assets.deleteDialog.title=Delete Asset
|
assets.deleteDialog.title=Delete Asset
|
||||||
assets.deleteDialog.description=Type the asset name below to confirm deletion of {0}.
|
assets.deleteDialog.description=Type the asset name below to confirm deletion of {0}.
|
||||||
@ -185,24 +181,10 @@ 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.sceneStatus=Scene Status
|
|
||||||
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.sceneStatus.pendingValidation=Pending Validation
|
|
||||||
assets.sceneStatus.validatedPendingAcceptance=Validated / Pending Acceptance
|
|
||||||
assets.sceneStatus.ready=Ready
|
|
||||||
assets.sceneStatus.validationFailed=Validation Failed
|
|
||||||
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
|
||||||
@ -263,12 +245,10 @@ 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,7 +7,6 @@ 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;
|
||||||
@ -115,8 +114,6 @@ final class AssetDetailsBankCompositionCoordinatorTest {
|
|||||||
List.of(),
|
List.of(),
|
||||||
0L),
|
0L),
|
||||||
List.of(),
|
List.of(),
|
||||||
null,
|
|
||||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
|
||||||
List.of());
|
List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,8 +136,6 @@ final class AssetDetailsBankCompositionCoordinatorTest {
|
|||||||
List.of(),
|
List.of(),
|
||||||
0L),
|
0L),
|
||||||
List.of(),
|
List.of(),
|
||||||
null,
|
|
||||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
|
||||||
List.of());
|
List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +159,6 @@ 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,7 +5,6 @@ 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;
|
||||||
@ -82,7 +81,6 @@ 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),
|
||||||
@ -95,8 +93,6 @@ 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,
|
|
||||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
|
||||||
List.of());
|
List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
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,
|
|
||||||
"map_width": 20,
|
|
||||||
"map_height": 12,
|
|
||||||
"layer_count": 2,
|
|
||||||
"layers": [
|
|
||||||
{ "index": 1, "name": "Ground", "tilemap": "ground.tmx", "tileset_asset_root": "tilesets/ground" },
|
|
||||||
{ "index": 2, "name": "Collision", "tilemap": "collision.tmx", "tileset_asset_root": "tilesets/ground" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""");
|
|
||||||
|
|
||||||
final AssetStudioMetadataSnapshot snapshot = service.read(assetRoot, AssetFamilyCatalog.SCENE_BANK);
|
|
||||||
|
|
||||||
assertNotNull(snapshot.sceneBankMetadata());
|
|
||||||
assertEquals(20, snapshot.sceneBankMetadata().mapWidth());
|
|
||||||
assertEquals(12, snapshot.sceneBankMetadata().mapHeight());
|
|
||||||
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,
|
|
||||||
"map_width": 16,
|
|
||||||
"map_height": 16,
|
|
||||||
"layer_count": 5,
|
|
||||||
"layers": [
|
|
||||||
{ "index": 1, "name": "A", "tilemap": "a.tmx", "tileset_asset_root": "" },
|
|
||||||
{ "index": 2, "name": "B", "tilemap": "b.tmx", "tileset_asset_root": "" },
|
|
||||||
{ "index": 3, "name": "C", "tilemap": "c.tmx", "tileset_asset_root": "" },
|
|
||||||
{ "index": 4, "name": "D", "tilemap": "d.tmx", "tileset_asset_root": "" },
|
|
||||||
{ "index": 5, "name": "E", "tilemap": "e.tmx", "tileset_asset_root": "" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""");
|
|
||||||
|
|
||||||
final AssetStudioMetadataSnapshot snapshot = service.read(assetRoot, AssetFamilyCatalog.SCENE_BANK);
|
|
||||||
|
|
||||||
assertNull(snapshot.sceneBankMetadata());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,193 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.scene;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
import p.packer.dtos.PackerCodecConfigurationFieldDTO;
|
|
||||||
import p.packer.messages.AssetReference;
|
|
||||||
import p.packer.messages.assets.AssetFamilyCatalog;
|
|
||||||
import p.packer.messages.assets.OutputCodecCatalog;
|
|
||||||
import p.packer.messages.assets.OutputFormatCatalog;
|
|
||||||
import p.packer.messages.assets.PackerCodecConfigurationFieldType;
|
|
||||||
import p.studio.projects.ProjectReference;
|
|
||||||
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.*;
|
|
||||||
import p.studio.workspaces.assets.tiled.TiledAssetGenerationService;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
final class SceneBankWorkflowServiceTest {
|
|
||||||
private final TiledAssetGenerationService generationService = new TiledAssetGenerationService();
|
|
||||||
private final SceneBankWorkflowService workflowService = new SceneBankWorkflowService();
|
|
||||||
|
|
||||||
@TempDir
|
|
||||||
Path tempDir;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void validateThenAcceptPromotesSceneBankToReadyUntilFilesChange() throws Exception {
|
|
||||||
final ProjectReference project = projectReference();
|
|
||||||
final AssetWorkspaceAssetDetails sceneDetails = createValidScene(project);
|
|
||||||
|
|
||||||
final SceneBankWorkflowResult initial = workflowService.inspect(project, sceneDetails);
|
|
||||||
assertEquals(AssetWorkspaceSceneBankStatus.PENDING_VALIDATION, initial.validation().status());
|
|
||||||
assertFalse(initial.validation().canAccept());
|
|
||||||
|
|
||||||
final SceneBankWorkflowResult validated = workflowService.validate(project, sceneDetails);
|
|
||||||
assertEquals(AssetWorkspaceSceneBankStatus.VALIDATED_PENDING_ACCEPTANCE, validated.validation().status());
|
|
||||||
assertTrue(validated.validation().canAccept());
|
|
||||||
|
|
||||||
final SceneBankWorkflowResult accepted = workflowService.accept(project, sceneDetails);
|
|
||||||
assertEquals(AssetWorkspaceSceneBankStatus.READY, accepted.validation().status());
|
|
||||||
assertFalse(accepted.validation().pendingExternalChanges());
|
|
||||||
|
|
||||||
final Path tmxPath = sceneDetails.summary().assetRoot().resolve("ground.tmx");
|
|
||||||
Files.writeString(tmxPath, Files.readString(tmxPath).replaceFirst("0,0,0", "1,0,0"));
|
|
||||||
|
|
||||||
final SceneBankWorkflowResult changed = workflowService.inspect(project, sceneDetails);
|
|
||||||
assertEquals(AssetWorkspaceSceneBankStatus.PENDING_VALIDATION, changed.validation().status());
|
|
||||||
assertTrue(changed.validation().pendingExternalChanges());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void validationFailsWhenReferencedTsxIsMissing() throws Exception {
|
|
||||||
final ProjectReference project = projectReference();
|
|
||||||
final Path sceneRoot = project.rootPath().resolve("assets/scenes/broken");
|
|
||||||
Files.createDirectories(sceneRoot);
|
|
||||||
final AssetWorkspaceAssetDetails brokenScene = new AssetWorkspaceAssetDetails(
|
|
||||||
new AssetWorkspaceAssetSummary(
|
|
||||||
AssetReference.forAssetId(2),
|
|
||||||
"broken_scene",
|
|
||||||
AssetWorkspaceAssetState.REGISTERED,
|
|
||||||
AssetWorkspaceBuildParticipation.EXCLUDED,
|
|
||||||
2,
|
|
||||||
AssetFamilyCatalog.SCENE_BANK,
|
|
||||||
AssetStudioGlyphSpecialization.NONE,
|
|
||||||
sceneRoot,
|
|
||||||
false,
|
|
||||||
false),
|
|
||||||
List.of(),
|
|
||||||
OutputFormatCatalog.SCENE_TILED_V1,
|
|
||||||
OutputCodecCatalog.NONE,
|
|
||||||
List.of(OutputCodecCatalog.NONE),
|
|
||||||
Map.of(OutputCodecCatalog.NONE, List.of()),
|
|
||||||
List.of(),
|
|
||||||
Map.of(),
|
|
||||||
new AssetWorkspaceBankCompositionDetails(List.of(), List.of(), 0L),
|
|
||||||
List.of(),
|
|
||||||
new AssetStudioSceneBankMetadata(
|
|
||||||
8,
|
|
||||||
8,
|
|
||||||
1,
|
|
||||||
List.of(new AssetStudioSceneLayerBinding(1, "Ground", "ground.tmx", "tilesets/missing")),
|
|
||||||
sceneRoot.resolve("scene-bank.studio.json")),
|
|
||||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
|
||||||
List.of());
|
|
||||||
|
|
||||||
Files.writeString(sceneRoot.resolve("ground.tmx"), """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<map version="1.10" tiledversion="1.12.1" orientation="orthogonal" renderorder="right-down"
|
|
||||||
width="8" height="8" tilewidth="16" tileheight="16" infinite="0" nextlayerid="2" nextobjectid="1">
|
|
||||||
<tileset firstgid="1" source="../../tilesets/missing/tileset.tsx"/>
|
|
||||||
<layer id="1" name="Ground" width="8" height="8">
|
|
||||||
<data encoding="csv">0</data>
|
|
||||||
</layer>
|
|
||||||
</map>
|
|
||||||
""");
|
|
||||||
|
|
||||||
final SceneBankWorkflowResult result = workflowService.inspect(project, brokenScene);
|
|
||||||
|
|
||||||
assertEquals(AssetWorkspaceSceneBankStatus.VALIDATION_FAILED, result.validation().status());
|
|
||||||
assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("Referenced TSX file is missing")));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ProjectReference projectReference() {
|
|
||||||
return new ProjectReference("main", "1", "pbs", 1, tempDir.resolve("project"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private AssetWorkspaceAssetDetails createValidScene(ProjectReference project) throws Exception {
|
|
||||||
final Path tilesetRoot = project.rootPath().resolve("assets/tilesets/overworld");
|
|
||||||
Files.createDirectories(tilesetRoot);
|
|
||||||
Files.writeString(tilesetRoot.resolve("a.png"), "fixture");
|
|
||||||
assertTrue(generationService.generateTilesetTsx(tilesetDetails(tilesetRoot)).success());
|
|
||||||
|
|
||||||
final Path sceneRoot = project.rootPath().resolve("assets/scenes/overworld");
|
|
||||||
Files.createDirectories(sceneRoot);
|
|
||||||
final AssetWorkspaceAssetDetails scene = sceneDetails(sceneRoot);
|
|
||||||
assertTrue(generationService.generateSceneBankTilemaps(project, scene).success());
|
|
||||||
return scene;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AssetWorkspaceAssetDetails tilesetDetails(Path assetRoot) {
|
|
||||||
return new AssetWorkspaceAssetDetails(
|
|
||||||
new AssetWorkspaceAssetSummary(
|
|
||||||
AssetReference.forAssetId(1),
|
|
||||||
"overworld_tileset",
|
|
||||||
AssetWorkspaceAssetState.REGISTERED,
|
|
||||||
AssetWorkspaceBuildParticipation.INCLUDED,
|
|
||||||
1,
|
|
||||||
AssetFamilyCatalog.GLYPH_BANK,
|
|
||||||
AssetStudioGlyphSpecialization.TILESET,
|
|
||||||
assetRoot,
|
|
||||||
false,
|
|
||||||
false),
|
|
||||||
List.of(),
|
|
||||||
OutputFormatCatalog.GLYPH_INDEXED_V1,
|
|
||||||
OutputCodecCatalog.NONE,
|
|
||||||
List.of(OutputCodecCatalog.NONE),
|
|
||||||
Map.of(OutputCodecCatalog.NONE, List.of()),
|
|
||||||
List.of(new PackerCodecConfigurationFieldDTO(
|
|
||||||
"tile_size",
|
|
||||||
"Tile Size",
|
|
||||||
PackerCodecConfigurationFieldType.ENUM,
|
|
||||||
"16x16",
|
|
||||||
true,
|
|
||||||
List.of("8x8", "16x16", "32x32"))),
|
|
||||||
Map.of(),
|
|
||||||
new AssetWorkspaceBankCompositionDetails(
|
|
||||||
List.of(),
|
|
||||||
List.of(new AssetWorkspaceBankCompositionFile("a.png", "a.png", 1L, 1L, null, Map.of())),
|
|
||||||
0L),
|
|
||||||
List.of(),
|
|
||||||
null,
|
|
||||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
|
||||||
List.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
private AssetWorkspaceAssetDetails sceneDetails(Path assetRoot) {
|
|
||||||
return new AssetWorkspaceAssetDetails(
|
|
||||||
new AssetWorkspaceAssetSummary(
|
|
||||||
AssetReference.forAssetId(2),
|
|
||||||
"overworld_scene",
|
|
||||||
AssetWorkspaceAssetState.REGISTERED,
|
|
||||||
AssetWorkspaceBuildParticipation.EXCLUDED,
|
|
||||||
2,
|
|
||||||
AssetFamilyCatalog.SCENE_BANK,
|
|
||||||
AssetStudioGlyphSpecialization.NONE,
|
|
||||||
assetRoot,
|
|
||||||
false,
|
|
||||||
false),
|
|
||||||
List.of(),
|
|
||||||
OutputFormatCatalog.SCENE_TILED_V1,
|
|
||||||
OutputCodecCatalog.NONE,
|
|
||||||
List.of(OutputCodecCatalog.NONE),
|
|
||||||
Map.of(OutputCodecCatalog.NONE, List.of()),
|
|
||||||
List.of(),
|
|
||||||
Map.of(),
|
|
||||||
new AssetWorkspaceBankCompositionDetails(List.of(), List.of(), 0L),
|
|
||||||
List.of(),
|
|
||||||
new AssetStudioSceneBankMetadata(
|
|
||||||
16,
|
|
||||||
12,
|
|
||||||
1,
|
|
||||||
List.of(new AssetStudioSceneLayerBinding(1, "Ground", "ground.tmx", "tilesets/overworld")),
|
|
||||||
assetRoot.resolve("scene-bank.studio.json")),
|
|
||||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
|
||||||
List.of());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
import p.packer.dtos.PackerCodecConfigurationFieldDTO;
|
|
||||||
import p.packer.messages.AssetReference;
|
|
||||||
import p.packer.messages.assets.AssetFamilyCatalog;
|
|
||||||
import p.packer.messages.assets.OutputCodecCatalog;
|
|
||||||
import p.packer.messages.assets.OutputFormatCatalog;
|
|
||||||
import p.packer.messages.assets.PackerCodecConfigurationFieldType;
|
|
||||||
import p.studio.projects.ProjectReference;
|
|
||||||
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.*;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
final class TiledAssetGenerationServiceTest {
|
|
||||||
private final TiledAssetGenerationService service = new TiledAssetGenerationService();
|
|
||||||
private final TiledXmlCodec codec = new TiledXmlCodec();
|
|
||||||
|
|
||||||
@TempDir
|
|
||||||
Path tempDir;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void generatesTsxForTilesetSpecializedGlyphBank() throws Exception {
|
|
||||||
final Path assetRoot = tempDir.resolve("assets/tilesets/overworld");
|
|
||||||
Files.createDirectories(assetRoot);
|
|
||||||
Files.writeString(assetRoot.resolve("a.png"), "fixture");
|
|
||||||
Files.writeString(assetRoot.resolve("b.png"), "fixture");
|
|
||||||
|
|
||||||
final TiledAssetGenerationResult result = service.generateTilesetTsx(tilesetDetails(assetRoot));
|
|
||||||
|
|
||||||
assertTrue(result.success());
|
|
||||||
final Path tsxPath = assetRoot.resolve(TiledAssetGenerationService.GENERATED_TSX_FILE);
|
|
||||||
assertTrue(Files.isRegularFile(tsxPath));
|
|
||||||
final TiledTilesetDocument tileset = codec.readTileset(tsxPath);
|
|
||||||
assertEquals(2, tileset.tiles().size());
|
|
||||||
assertEquals("a.png", tileset.tiles().get(0).imageSource());
|
|
||||||
assertEquals("glyph_id", tileset.tiles().get(0).properties().getFirst().name());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void generatesTmxFilesForSceneBankUsingReferencedTsx() throws Exception {
|
|
||||||
final Path projectRoot = tempDir.resolve("project");
|
|
||||||
final Path tilesetRoot = projectRoot.resolve("assets/tilesets/overworld");
|
|
||||||
Files.createDirectories(tilesetRoot);
|
|
||||||
Files.writeString(tilesetRoot.resolve("a.png"), "fixture");
|
|
||||||
assertTrue(service.generateTilesetTsx(tilesetDetails(tilesetRoot)).success());
|
|
||||||
|
|
||||||
final Path sceneRoot = projectRoot.resolve("assets/scenes/overworld");
|
|
||||||
Files.createDirectories(sceneRoot);
|
|
||||||
final AssetWorkspaceAssetDetails details = sceneDetails(sceneRoot);
|
|
||||||
final ProjectReference projectReference = new ProjectReference("main", "1", "pbs", 1, projectRoot);
|
|
||||||
|
|
||||||
final TiledAssetGenerationResult result = service.generateSceneBankTilemaps(projectReference, details);
|
|
||||||
|
|
||||||
assertTrue(result.success());
|
|
||||||
final Path tmxPath = sceneRoot.resolve("ground.tmx");
|
|
||||||
assertTrue(Files.isRegularFile(tmxPath));
|
|
||||||
final TiledMapDocument map = codec.readMap(tmxPath);
|
|
||||||
assertEquals("../../tilesets/overworld/tileset.tsx", map.tilesets().getFirst().source());
|
|
||||||
assertEquals(16, map.width());
|
|
||||||
assertEquals(12, map.height());
|
|
||||||
assertEquals("Ground", map.tileLayers().getFirst().name());
|
|
||||||
}
|
|
||||||
|
|
||||||
private AssetWorkspaceAssetDetails tilesetDetails(Path assetRoot) {
|
|
||||||
return new AssetWorkspaceAssetDetails(
|
|
||||||
new AssetWorkspaceAssetSummary(
|
|
||||||
AssetReference.forAssetId(1),
|
|
||||||
"overworld_tileset",
|
|
||||||
AssetWorkspaceAssetState.REGISTERED,
|
|
||||||
AssetWorkspaceBuildParticipation.INCLUDED,
|
|
||||||
1,
|
|
||||||
AssetFamilyCatalog.GLYPH_BANK,
|
|
||||||
AssetStudioGlyphSpecialization.TILESET,
|
|
||||||
assetRoot,
|
|
||||||
false,
|
|
||||||
false),
|
|
||||||
List.of(),
|
|
||||||
OutputFormatCatalog.GLYPH_INDEXED_V1,
|
|
||||||
OutputCodecCatalog.NONE,
|
|
||||||
List.of(OutputCodecCatalog.NONE),
|
|
||||||
Map.of(OutputCodecCatalog.NONE, List.of()),
|
|
||||||
List.of(new PackerCodecConfigurationFieldDTO(
|
|
||||||
"tile_size",
|
|
||||||
"Tile Size",
|
|
||||||
PackerCodecConfigurationFieldType.ENUM,
|
|
||||||
"16x16",
|
|
||||||
true,
|
|
||||||
List.of("8x8", "16x16", "32x32"))),
|
|
||||||
Map.of(),
|
|
||||||
new AssetWorkspaceBankCompositionDetails(
|
|
||||||
List.of(),
|
|
||||||
List.of(
|
|
||||||
new AssetWorkspaceBankCompositionFile("a.png", "a.png", 1L, 1L, null, Map.of()),
|
|
||||||
new AssetWorkspaceBankCompositionFile("b.png", "b.png", 1L, 1L, null, Map.of())),
|
|
||||||
0L),
|
|
||||||
List.of(),
|
|
||||||
null,
|
|
||||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
|
||||||
List.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
private AssetWorkspaceAssetDetails sceneDetails(Path assetRoot) {
|
|
||||||
return new AssetWorkspaceAssetDetails(
|
|
||||||
new AssetWorkspaceAssetSummary(
|
|
||||||
AssetReference.forAssetId(2),
|
|
||||||
"overworld_scene",
|
|
||||||
AssetWorkspaceAssetState.REGISTERED,
|
|
||||||
AssetWorkspaceBuildParticipation.EXCLUDED,
|
|
||||||
2,
|
|
||||||
AssetFamilyCatalog.SCENE_BANK,
|
|
||||||
AssetStudioGlyphSpecialization.NONE,
|
|
||||||
assetRoot,
|
|
||||||
false,
|
|
||||||
false),
|
|
||||||
List.of(),
|
|
||||||
OutputFormatCatalog.SCENE_TILED_V1,
|
|
||||||
OutputCodecCatalog.NONE,
|
|
||||||
List.of(OutputCodecCatalog.NONE),
|
|
||||||
Map.of(OutputCodecCatalog.NONE, List.of()),
|
|
||||||
List.of(),
|
|
||||||
Map.of(),
|
|
||||||
new AssetWorkspaceBankCompositionDetails(List.of(), List.of(), 0L),
|
|
||||||
List.of(),
|
|
||||||
new AssetStudioSceneBankMetadata(
|
|
||||||
16,
|
|
||||||
12,
|
|
||||||
1,
|
|
||||||
List.of(new AssetStudioSceneLayerBinding(1, "Ground", "ground.tmx", "tilesets/overworld")),
|
|
||||||
assetRoot.resolve("scene-bank.studio.json")),
|
|
||||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
|
||||||
List.of());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
package p.studio.workspaces.assets.tiled;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
final class TiledXmlCodecTest {
|
|
||||||
private final TiledXmlCodec codec = new TiledXmlCodec();
|
|
||||||
|
|
||||||
@TempDir
|
|
||||||
Path tempDir;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void readsFixtureTmxAndPreservesWaveOneSurfaces() throws Exception {
|
|
||||||
final TiledMapDocument map = codec.readMap(Path.of("..", "test-projects", "main", "assets", "scenes", "primeiro mapa.tmx").toAbsolutePath().normalize());
|
|
||||||
|
|
||||||
assertEquals(25, map.width());
|
|
||||||
assertEquals(25, map.height());
|
|
||||||
assertEquals(1, map.tilesets().size());
|
|
||||||
assertEquals("../Zelda3/primeiro tileset.tsx", map.tilesets().getFirst().source());
|
|
||||||
assertEquals(1, map.tileLayers().size());
|
|
||||||
assertEquals(625, map.tileLayers().getFirst().gids().size());
|
|
||||||
assertEquals(1, map.objectLayers().size());
|
|
||||||
assertEquals(9, map.objectLayers().getFirst().objects().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void readsFixtureTsxWithPropertiesAndCollisionObjects() throws Exception {
|
|
||||||
final TiledTilesetDocument tileset = codec.readTileset(Path.of("..", "test-projects", "main", "assets", "Zelda3", "primeiro tileset.tsx").toAbsolutePath().normalize());
|
|
||||||
|
|
||||||
assertEquals(32, tileset.tileWidth());
|
|
||||||
assertEquals(32, tileset.tileHeight());
|
|
||||||
assertEquals(13, tileset.tileCount());
|
|
||||||
assertEquals(13, tileset.tiles().size());
|
|
||||||
assertEquals("glyph_id", tileset.tiles().getFirst().properties().getFirst().name());
|
|
||||||
assertNotNull(tileset.tiles().getFirst().collisionLayer());
|
|
||||||
assertEquals(2, tileset.tiles().getFirst().collisionLayer().objects().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void rejectsUnsupportedInfiniteMaps() throws Exception {
|
|
||||||
final Path path = tempDir.resolve("infinite.tmx");
|
|
||||||
Files.writeString(path, """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<map version="1.10" tiledversion="1.12.1" orientation="orthogonal" renderorder="right-down"
|
|
||||||
width="10" height="10" tilewidth="16" tileheight="16" infinite="1"/>
|
|
||||||
""");
|
|
||||||
|
|
||||||
assertThrows(TiledUnsupportedFeatureException.class, () -> codec.readMap(path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schema_version" : 1,
|
"schema_version" : 1,
|
||||||
"next_asset_id" : 17,
|
"next_asset_id" : 16,
|
||||||
"assets" : [ {
|
"assets" : [ {
|
||||||
"asset_id" : 3,
|
"asset_id" : 3,
|
||||||
"asset_uuid" : "21953cb8-4101-4790-9e5e-d95f5fbc9b5a",
|
"asset_uuid" : "21953cb8-4101-4790-9e5e-d95f5fbc9b5a",
|
||||||
@ -41,10 +41,5 @@
|
|||||||
"asset_uuid" : "87396aab-337e-479e-b1f4-ec296678389e",
|
"asset_uuid" : "87396aab-337e-479e-b1f4-ec296678389e",
|
||||||
"root" : "Zelda3",
|
"root" : "Zelda3",
|
||||||
"included_in_build" : true
|
"included_in_build" : true
|
||||||
}, {
|
|
||||||
"asset_id" : 16,
|
|
||||||
"asset_uuid" : "6f05bee1-f974-4b65-a43f-eacd46b8ec96",
|
|
||||||
"root" : "tiled",
|
|
||||||
"included_in_build" : true
|
|
||||||
} ]
|
} ]
|
||||||
}
|
}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"schema_version" : 1,
|
|
||||||
"asset_uuid" : "6f05bee1-f974-4b65-a43f-eacd46b8ec96",
|
|
||||||
"name" : "Tiled Test",
|
|
||||||
"type" : "scene_bank",
|
|
||||||
"output" : {
|
|
||||||
"pipeline" : { },
|
|
||||||
"codec" : "NONE",
|
|
||||||
"format" : "SCENE/tiled_v1"
|
|
||||||
},
|
|
||||||
"preload" : {
|
|
||||||
"enabled" : false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"schema_version" : 1,
|
|
||||||
"map_width" : 16,
|
|
||||||
"map_height" : 16,
|
|
||||||
"layer_count" : 1,
|
|
||||||
"layers" : [ {
|
|
||||||
"index" : 1,
|
|
||||||
"tilemap" : "layer-1.tmx",
|
|
||||||
"tileset_asset_root" : "",
|
|
||||||
"name" : "Layer 1"
|
|
||||||
} ]
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user