implements PLN-0055
This commit is contained in:
parent
831e419c80
commit
b77a24c57b
@ -156,6 +156,8 @@ public enum I18n {
|
||||
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_INCLUDE_IN_BUILD("assets.action.includeInBuild"),
|
||||
ASSETS_ACTION_EXCLUDE_FROM_BUILD("assets.action.excludeFromBuild"),
|
||||
@ -193,6 +195,7 @@ public enum I18n {
|
||||
ASSETS_LABEL_ASSET_ID("assets.label.assetId"),
|
||||
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"),
|
||||
@ -205,6 +208,10 @@ public enum I18n {
|
||||
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_BANK("assets.label.bank"),
|
||||
ASSETS_LABEL_TARGET_LOCATION("assets.label.targetLocation"),
|
||||
|
||||
@ -30,12 +30,16 @@ import p.studio.workspaces.assets.messages.AssetWorkspaceAssetAction;
|
||||
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionDetails;
|
||||
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
|
||||
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.AssetWorkspaceDetailsViewState;
|
||||
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.StudioAssetsRefreshRequestedEvent;
|
||||
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;
|
||||
@ -43,6 +47,7 @@ import p.studio.workspaces.assets.wizards.MoveAssetWizard;
|
||||
import p.studio.workspaces.framework.StudioEventAware;
|
||||
import p.studio.workspaces.framework.StudioEventBindings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@ -60,6 +65,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
||||
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 ScrollPane actionsScroll = new ScrollPane();
|
||||
private final VBox actionsSection;
|
||||
@ -343,6 +349,17 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
||||
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());
|
||||
AssetDetailsUiSupport.applyActionTone(
|
||||
@ -479,6 +496,11 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
||||
&& 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;
|
||||
@ -510,6 +532,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
||||
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()));
|
||||
@ -517,6 +540,45 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
actionRunning = false;
|
||||
if (result.status() == p.packer.messages.PackerOperationStatus.SUCCESS && result.assetReference() != null) {
|
||||
@ -606,13 +668,14 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
||||
final AssetStudioMetadataSnapshot studioMetadata = studioMetadataService.read(
|
||||
baseSummary.assetRoot(),
|
||||
baseSummary.assetFamily());
|
||||
final java.util.List<PackerDiagnosticDTO> mergedDiagnostics = new java.util.ArrayList<>(details.diagnostics());
|
||||
for (PackerDiagnosticDTO diagnostic : diagnostics) {
|
||||
if (!mergedDiagnostics.contains(diagnostic)) {
|
||||
mergedDiagnostics.add(diagnostic);
|
||||
}
|
||||
}
|
||||
return new AssetWorkspaceAssetDetails(
|
||||
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(),
|
||||
@ -623,23 +686,47 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
|
||||
studioMetadata.glyphSpecialization(),
|
||||
baseSummary.assetRoot(),
|
||||
baseSummary.preload(),
|
||||
baseSummary.hasDiagnostics()),
|
||||
actions.stream()
|
||||
.map(action -> new AssetWorkspaceAssetAction(
|
||||
action.action(),
|
||||
action.enabled(),
|
||||
action.visible(),
|
||||
action.reason()))
|
||||
.toList(),
|
||||
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(),
|
||||
mapBankComposition(details.bankComposition()),
|
||||
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());
|
||||
for (PackerDiagnosticDTO diagnostic : diagnostics) {
|
||||
if (!mergedDiagnostics.contains(diagnostic)) {
|
||||
mergedDiagnostics.add(diagnostic);
|
||||
}
|
||||
}
|
||||
for (PackerDiagnosticDTO diagnostic : sceneBankWorkflow.diagnostics()) {
|
||||
if (!mergedDiagnostics.contains(diagnostic)) {
|
||||
mergedDiagnostics.add(diagnostic);
|
||||
}
|
||||
}
|
||||
return new AssetWorkspaceAssetDetails(
|
||||
mappedSummary,
|
||||
mappedActions,
|
||||
details.outputFormat(),
|
||||
details.outputCodec(),
|
||||
details.availableOutputCodecs(),
|
||||
details.codecConfigurationFieldsByCodec(),
|
||||
details.metadataFields(),
|
||||
details.outputPipeline(),
|
||||
bankComposition,
|
||||
details.pipelinePalettes(),
|
||||
studioMetadata.sceneBankMetadata(),
|
||||
sceneBankWorkflow.validation(),
|
||||
mergedDiagnostics);
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ import p.studio.controls.forms.StudioSection;
|
||||
import p.studio.projects.ProjectReference;
|
||||
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.AssetWorkspaceBuildParticipation;
|
||||
|
||||
@ -139,6 +140,16 @@ public final class AssetDetailsUiSupport {
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
return switch (action) {
|
||||
case REGISTER -> Container.i18n().text(I18n.ASSETS_ACTION_REGISTER);
|
||||
|
||||
@ -78,6 +78,9 @@ public final class AssetDetailsSummaryControl extends VBox implements StudioCont
|
||||
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())));
|
||||
|
||||
@ -22,6 +22,7 @@ public record AssetWorkspaceAssetDetails(
|
||||
AssetWorkspaceBankCompositionDetails bankComposition,
|
||||
List<Map<String, Object>> pipelinePalettes,
|
||||
AssetStudioSceneBankMetadata sceneBankMetadata,
|
||||
AssetWorkspaceSceneBankValidation sceneBankValidation,
|
||||
List<PackerDiagnosticDTO> diagnostics) {
|
||||
|
||||
public AssetWorkspaceAssetDetails {
|
||||
@ -35,6 +36,7 @@ public record AssetWorkspaceAssetDetails(
|
||||
outputPipeline = Map.copyOf(Objects.requireNonNull(outputPipeline, "outputPipeline"));
|
||||
bankComposition = Objects.requireNonNull(bankComposition, "bankComposition");
|
||||
pipelinePalettes = List.copyOf(Objects.requireNonNull(pipelinePalettes, "pipelinePalettes"));
|
||||
sceneBankValidation = Objects.requireNonNullElse(sceneBankValidation, AssetWorkspaceSceneBankValidation.NOT_APPLICABLE);
|
||||
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
package p.studio.workspaces.assets.messages;
|
||||
|
||||
public enum AssetWorkspaceSceneBankStatus {
|
||||
NOT_APPLICABLE,
|
||||
PENDING_VALIDATION,
|
||||
VALIDATED_PENDING_ACCEPTANCE,
|
||||
READY,
|
||||
VALIDATION_FAILED
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
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, "");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
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()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,279 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -146,6 +146,8 @@ assets.action.register=Register
|
||||
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.deleteDialog.title=Delete Asset
|
||||
assets.deleteDialog.description=Type the asset name below to confirm deletion of {0}.
|
||||
@ -184,6 +186,7 @@ assets.label.buildParticipation=Build Participation
|
||||
assets.label.assetId=Asset ID
|
||||
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
|
||||
@ -196,6 +199,10 @@ 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.bank=Bank
|
||||
assets.label.targetLocation=Target Location
|
||||
|
||||
@ -116,6 +116,7 @@ final class AssetDetailsBankCompositionCoordinatorTest {
|
||||
0L),
|
||||
List.of(),
|
||||
null,
|
||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
||||
List.of());
|
||||
}
|
||||
|
||||
@ -139,6 +140,7 @@ final class AssetDetailsBankCompositionCoordinatorTest {
|
||||
0L),
|
||||
List.of(),
|
||||
null,
|
||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
||||
List.of());
|
||||
}
|
||||
|
||||
|
||||
@ -96,6 +96,7 @@ final class AssetDetailsPaletteOverhaulingCoordinatorTest {
|
||||
new AssetWorkspaceBankCompositionDetails(availableFiles, selectedFiles, 0L),
|
||||
selectedFiles.stream().map(file -> (Map<String, Object>) file.metadata().get("palette")).toList(),
|
||||
null,
|
||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
||||
List.of());
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,193 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@ -105,6 +105,7 @@ final class TiledAssetGenerationServiceTest {
|
||||
0L),
|
||||
List.of(),
|
||||
null,
|
||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
||||
List.of());
|
||||
}
|
||||
|
||||
@ -136,6 +137,7 @@ final class TiledAssetGenerationServiceTest {
|
||||
1,
|
||||
List.of(new AssetStudioSceneLayerBinding(1, "Ground", "ground.tmx", "tilesets/overworld")),
|
||||
assetRoot.resolve("scene-bank.studio.json")),
|
||||
AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
|
||||
List.of());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user