From ed3af9f50b1a61db4cfec28cfbefb63156575d91 Mon Sep 17 00:00:00 2001 From: Andoni del Olmo Date: Tue, 9 Apr 2024 21:34:06 +0200 Subject: [PATCH] Show modal with error if the quest contains objects for Icon Packs not yet installed --- .../org/lightless/heroscribe/gui/Board.java | 26 ++---- .../org/lightless/heroscribe/gui/Gui.java | 61 +++++++++++--- .../heroscribe/gui/PropertiesModal.java | 4 +- .../heroscribe/gui/SquareDisplayer.java | 4 +- .../heroscribe/iconpack/IconPackService.java | 1 + .../org/lightless/heroscribe/xml/Kind.java | 34 ++++++-- .../lightless/heroscribe/xml/ObjectList.java | 11 ++- .../org/lightless/heroscribe/xml/Quest.java | 84 ++++++++++++++++--- .../lightless/heroscribe/xml/QuestParser.java | 39 ++++++++- 9 files changed, 207 insertions(+), 57 deletions(-) diff --git a/src/main/java/org/lightless/heroscribe/gui/Board.java b/src/main/java/org/lightless/heroscribe/gui/Board.java index 69dae6a..e84eec2 100644 --- a/src/main/java/org/lightless/heroscribe/gui/Board.java +++ b/src/main/java/org/lightless/heroscribe/gui/Board.java @@ -72,7 +72,7 @@ private static void removeFromBoardObjectsNotPresentInObjectList(Gui gui) { final List objectsInBoard = board.getObjects(); Arrays.stream(objectsInBoard.toArray(Quest.Board.Object[]::new)) .filter(o -> gui.getObjectList().getOptionalObject(o.getId()).isEmpty()) - .forEach(objectsInBoard::remove); + .forEach(board::removeObject); }); } @@ -93,26 +93,15 @@ public void paintComponent(Graphics g) { } private Quest.Board.Object getNewObject(boolean floating) { - Quest.Board.Object newObject; final String id = gui.tools.selectorPanel.getSelectedObject(); - - if (floating) { - newObject = new Quest.Board.Object(); - newObject.setId(id); - newObject.setZorder(Integer.MAX_VALUE); - } else { - newObject = new Quest.Board.Object(); - newObject.setId(gui.tools.selectorPanel.getSelectedObject()); - - final ObjectList.Object obj = gui.getObjectList().getObjectById(id); - - newObject.setZorder(obj.getZorder()); - } - + final ObjectList.Object obj = gui.getObjectList().getObjectById(id); + final Quest.Board.Object newObject = new Quest.Board.Object(); + newObject.setId(id); + newObject.setZorder(floating ? Integer.MAX_VALUE : obj.getZorder()); newObject.setRotation(Rotation.fromNumber(rotation)); newObject.setLeft(lastLeft); newObject.setTop(lastTop); - + newObject.setKind(gui.getObjectList().getKind(obj.getKind())); return newObject; } @@ -263,8 +252,9 @@ public void mousePressed(MouseEvent e) { Quest.Board.Object obj = getNewObject(false); if (isWellPositioned(obj)) - if (gui.getQuest().getBoard(lastColumn, lastRow).addObject(obj)) { + if (gui.getQuest().getBoard(lastColumn, lastRow).addObjectIfNotPresentInCell(obj)) { hasAdded = true; +// gui.getQuest().getKinds().add(gui.getObjectList().getKind(obj.getId())); gui.getQuest().setModified(true); } } diff --git a/src/main/java/org/lightless/heroscribe/gui/Gui.java b/src/main/java/org/lightless/heroscribe/gui/Gui.java index eb017f7..11a36bd 100644 --- a/src/main/java/org/lightless/heroscribe/gui/Gui.java +++ b/src/main/java/org/lightless/heroscribe/gui/Gui.java @@ -23,23 +23,33 @@ HeroScribe Enhanced (changes are prefixed with HSE in comments) package org.lightless.heroscribe.gui; -import org.lightless.heroscribe.*; -import org.lightless.heroscribe.export.*; -import org.lightless.heroscribe.helper.*; -import org.lightless.heroscribe.iconpack.*; +import org.lightless.heroscribe.Constants; +import org.lightless.heroscribe.Preferences; +import org.lightless.heroscribe.export.ExportEPS; +import org.lightless.heroscribe.export.ExportIPDF; +import org.lightless.heroscribe.export.ExportPDF; +import org.lightless.heroscribe.export.ExportRaster; +import org.lightless.heroscribe.helper.BoardPainter; +import org.lightless.heroscribe.iconpack.IconPackService; import org.lightless.heroscribe.utils.OS; -import org.lightless.heroscribe.xml.*; -import org.slf4j.*; +import org.lightless.heroscribe.xml.Kind; +import org.lightless.heroscribe.xml.ObjectList; +import org.lightless.heroscribe.xml.Quest; +import org.lightless.heroscribe.xml.QuestParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.swing.*; import javax.swing.filechooser.FileFilter; import java.awt.*; import java.awt.event.*; -import java.io.*; -import java.nio.file.*; +import java.io.File; +import java.nio.file.Path; +import java.util.List; import java.util.*; +import java.util.stream.Collectors; -import static org.lightless.heroscribe.export.ExportRaster.ImageFormat.*; +import static org.lightless.heroscribe.export.ExportRaster.ImageFormat.PNG; public class Gui extends JFrame implements WindowListener, ItemListener, ActionListener { @@ -504,6 +514,20 @@ public void actionPerformed(ActionEvent e) { objectList.getBoard().getHeight()); tools.none.doClick(); + + final Set unsupportedKinds = findUnsupportedKinds(newXmlQuest, objectList.getKinds()); + if(!unsupportedKinds.isEmpty()) { + JOptionPane.showMessageDialog(this, + "The Quest contains objects not supported.\n\n" + + "Please import the following Icon Pack(s) and try again:\n" + + unsupportedKinds.stream() + .map(Kind::getName) + .collect(Collectors.joining("\n", "• ", "")), + "Error", + JOptionPane.ERROR_MESSAGE); + return; + } + quest = newXmlQuest; setMenuRegion(); @@ -534,7 +558,7 @@ public void actionPerformed(ActionEvent e) { quest.setFile(file); } - questParser.saveToDisk(quest, quest.getFile()); + questParser.saveToDisk(objectList, quest, quest.getFile()); updateTitle(); } catch (Exception ex) { JOptionPane.showMessageDialog(this, @@ -549,9 +573,7 @@ public void actionPerformed(ActionEvent e) { if ((file = askPath("xml")) != null) { try { - quest.setFile(file); - questParser.saveToDisk(quest, file); - + questParser.saveToDisk(objectList, quest, file); updateTitle(); } catch (Exception ex) { JOptionPane.showMessageDialog(this, @@ -725,6 +747,19 @@ public void actionPerformed(ActionEvent e) { } + private Set findUnsupportedKinds(Quest newXmlQuest, List kinds) { + final Set unsupportedKinds = new HashSet<>(); + final List supportedKindIds = kinds.stream() + .map(Kind::getId) + .collect(Collectors.toList()); + for (Kind questKind : newXmlQuest.getKinds()) { + if (!supportedKindIds.contains(questKind.getId())) { + unsupportedKinds.add(questKind); + } + } + return unsupportedKinds; + } + private File askPath(String extension) { fileChooser.resetChoosableFileFilters(); diff --git a/src/main/java/org/lightless/heroscribe/gui/PropertiesModal.java b/src/main/java/org/lightless/heroscribe/gui/PropertiesModal.java index f0c2a9d..9a2b7f4 100644 --- a/src/main/java/org/lightless/heroscribe/gui/PropertiesModal.java +++ b/src/main/java/org/lightless/heroscribe/gui/PropertiesModal.java @@ -101,11 +101,11 @@ public void showDialog() { final ObjectList.Object selectedItem = (ObjectList.Object) wandering.getSelectedItem(); if (!quest.hasWanderingMonster()) { - quest.setWandering(selectedItem.getName(), selectedItem.getId()); + quest.setWandering(selectedItem.getId()); quest.setModified(true); } if (!selectedItem.getId().equals(quest.getWanderingId())) { - quest.setWandering(selectedItem.getName(), selectedItem.getId()); + quest.setWandering(selectedItem.getId()); quest.setModified(true); } diff --git a/src/main/java/org/lightless/heroscribe/gui/SquareDisplayer.java b/src/main/java/org/lightless/heroscribe/gui/SquareDisplayer.java index d26b549..73526b6 100644 --- a/src/main/java/org/lightless/heroscribe/gui/SquareDisplayer.java +++ b/src/main/java/org/lightless/heroscribe/gui/SquareDisplayer.java @@ -165,7 +165,7 @@ public void actionPerformed(ActionEvent e) { JButton button = (JButton) e.getSource(); if (obj != null) { - gui.getQuest().getBoard(lastColumn, lastRow).getObjects().remove(obj); + gui.getQuest().getBoard(lastColumn, lastRow).removeObject(obj); selected.remove(obj); } @@ -180,7 +180,7 @@ public void actionPerformed(ActionEvent e) { } if (obj != null) { - gui.getQuest().getBoard(lastColumn, lastRow).getObjects().add(obj); + gui.getQuest().getBoard(lastColumn, lastRow).addObject(obj); selected.add(obj); } diff --git a/src/main/java/org/lightless/heroscribe/iconpack/IconPackService.java b/src/main/java/org/lightless/heroscribe/iconpack/IconPackService.java index 8238b59..67f9a46 100644 --- a/src/main/java/org/lightless/heroscribe/iconpack/IconPackService.java +++ b/src/main/java/org/lightless/heroscribe/iconpack/IconPackService.java @@ -177,6 +177,7 @@ private List getNewKindsFromIconPack(final ObjectList referenceObjectList, final ObjectList iconPackObjectList) { return iconPackObjectList.getKinds().stream() .filter(kind -> !referenceObjectList.getKindIds().contains(kind.getId())) + .peek(kind -> kind.setOriginal(false)) .collect(Collectors.toList()); } diff --git a/src/main/java/org/lightless/heroscribe/xml/Kind.java b/src/main/java/org/lightless/heroscribe/xml/Kind.java index e441f68..01be50d 100644 --- a/src/main/java/org/lightless/heroscribe/xml/Kind.java +++ b/src/main/java/org/lightless/heroscribe/xml/Kind.java @@ -1,19 +1,35 @@ +/* + HeroScribe Enhanced Skull + Copyright (C) 2022 Andoni del Olmo + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 (not + later versions) as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + package org.lightless.heroscribe.xml; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; -/** - * @author Andoni del Olmo - * @since 7/4/24 - */ public class Kind { @JacksonXmlProperty(isAttribute = true) private String id; @JacksonXmlProperty(isAttribute = true) private String name; + @JacksonXmlProperty(isAttribute = true) + private boolean original = true; public String getId() { return id; @@ -31,6 +47,14 @@ public void setName(String name) { this.name = name; } + public boolean isOriginal() { + return original; + } + + public void setOriginal(boolean original) { + this.original = original; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -44,7 +68,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return new HashCodeBuilder(17, 37).append(id).append(name).toHashCode(); + return new HashCodeBuilder(17, 37).append(id).toHashCode(); } @Override diff --git a/src/main/java/org/lightless/heroscribe/xml/ObjectList.java b/src/main/java/org/lightless/heroscribe/xml/ObjectList.java index d1472c2..e52d82c 100644 --- a/src/main/java/org/lightless/heroscribe/xml/ObjectList.java +++ b/src/main/java/org/lightless/heroscribe/xml/ObjectList.java @@ -154,10 +154,6 @@ public ObjectList.Object getObjectById(String id) { .orElseThrow(() -> new IllegalStateException(format("Unknown object '%s'", id))); } - public boolean containsObjectById(String id) { - return getOptionalObject(id).isPresent(); - } - public Optional getOptionalObject(String id) { return objects.stream() .filter(object -> object.getId().equals(id)) @@ -200,6 +196,13 @@ public void setBasePath(Path basePath) { this.basePath = basePath; } + public Kind getKind(String id) { + return kinds.stream() + .filter(kind -> id.equals(kind.getId())) + .findFirst() + .orElseThrow(() -> new IllegalStateException(format("Unknown kind '%s'", id))); + } + public static class Board { @JacksonXmlProperty(isAttribute = true) diff --git a/src/main/java/org/lightless/heroscribe/xml/Quest.java b/src/main/java/org/lightless/heroscribe/xml/Quest.java index 5bd1e34..dde8243 100644 --- a/src/main/java/org/lightless/heroscribe/xml/Quest.java +++ b/src/main/java/org/lightless/heroscribe/xml/Quest.java @@ -18,12 +18,26 @@ package org.lightless.heroscribe.xml; -import com.fasterxml.jackson.annotation.*; -import com.fasterxml.jackson.dataformat.xml.annotation.*; - -import java.io.*; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.node.TextNode; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +import java.io.File; +import java.io.IOException; import java.util.*; -import java.util.stream.*; +import java.util.stream.Collectors; @JsonRootName("quest") public class Quest { @@ -52,6 +66,10 @@ public class Quest { @JacksonXmlElementWrapper(useWrapping = false) private List notes = new ArrayList<>(); + @JsonProperty("kind") + @JacksonXmlElementWrapper(useWrapping = false) + private Set kinds = new HashSet<>(); + @JsonIgnore private File file; @JsonIgnore @@ -164,6 +182,14 @@ public void setSpeech(String speech) { this.speech = speech; } + public Set getKinds() { + return kinds; + } + + public void setKinds(Set kinds) { + this.kinds = kinds; + } + @JsonIgnore public List getNotesForUI() { return notes.stream() @@ -191,7 +217,7 @@ public void addNote(String text) { } public void setNote(int index, String text) { - notes.set(index,text); + notes.set(index, text); /*int wanderingMonsterNoteIndex = -1; for (int i = 0; i < notes.size(); i++) { final String note = notes.get(i); @@ -255,7 +281,7 @@ public String getWanderingId() { .orElse(DEFAULT_WANDERING_MONSTER); } - public void setWandering(String name, String id) { + public void setWandering(String id) { final Optional wanderingMonsterNote = findWanderingMonsterNote(); if (wanderingMonsterNote.isEmpty()) { notes.add(WANDERING_MONSTER_NOTE_MESSAGE + id); @@ -302,7 +328,7 @@ public void setDarks(List darks) { } public List getObjects() { - return objects; + return Collections.unmodifiableList(objects); } public void setObjects(List objects) { @@ -347,7 +373,7 @@ private Optional findDark(int left, int top) { return Optional.empty(); } - public boolean addObject(Quest.Board.Object newObj) { + public boolean addObjectIfNotPresentInCell(Quest.Board.Object newObj) { for (Object obj : objects) { if (obj.left == newObj.left && obj.top == newObj.top @@ -356,11 +382,15 @@ public boolean addObject(Quest.Board.Object newObj) { return false; } - objects.add(newObj); + addObject(newObj); return true; } + public void addObject(Object object) { + objects.add(object); + } + public int getWidth() { return width; } @@ -377,6 +407,10 @@ public void setHeight(int height) { this.height = height; } + public void removeObject(Object object) { + objects.remove(object); + } + private static class Dark { @JacksonXmlProperty(isAttribute = true) private int left; @@ -446,6 +480,11 @@ public static class Object implements Comparable { @JacksonXmlProperty(isAttribute = true) private float zorder; + @JacksonXmlProperty(isAttribute = true) + @JsonSerialize(using = KindToIdString.class) + @JsonDeserialize(using = KindIdToObject.class) + private Kind kind; + @JsonIgnore private int order; @JsonIgnore @@ -499,6 +538,14 @@ public void setZorder(float zorder) { this.zorder = zorder; } + public Kind getKind() { + return kind; + } + + public void setKind(Kind kind) { + this.kind = kind; + } + @Override public int compareTo(Object o) { if (this.zorder < o.zorder) @@ -517,6 +564,23 @@ else if (this.order > o.order) public String toString() { return id; } + + public static class KindToIdString extends JsonSerializer { + + @Override + public void serialize(Kind value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString(value.getId()); + } + } + + public static class KindIdToObject extends JsonDeserializer { + @Override + public Kind deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + return new Kind() {{ + setId(((TextNode) p.readValueAsTree()).asText()); + }}; + } + } } } diff --git a/src/main/java/org/lightless/heroscribe/xml/QuestParser.java b/src/main/java/org/lightless/heroscribe/xml/QuestParser.java index 13f42a6..2011ea9 100644 --- a/src/main/java/org/lightless/heroscribe/xml/QuestParser.java +++ b/src/main/java/org/lightless/heroscribe/xml/QuestParser.java @@ -18,9 +18,13 @@ package org.lightless.heroscribe.xml; -import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.*; +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; public class QuestParser { private final ObjectMapper xmlMapper; @@ -36,8 +40,37 @@ public Quest parse(File file, int boardWidth, int boardHeight) throws IOExceptio return quest; } - public void saveToDisk(Quest quest, File outputFile) throws IOException { + public void saveToDisk(ObjectList objectList, Quest quest, File outputFile) throws IOException { + final List originalKinds = objectList.getKinds().stream() + .filter(Kind::isOriginal) + .collect(Collectors.toList()); + quest.setKinds(new HashSet<>(originalKinds)); + + if (questContainsKindsFromIconPacks(quest.getBoards(), objectList.getKinds())) { + for (Quest.Board board : quest.getBoards()) { + for (Quest.Board.Object object : board.getObjects()) { + if (object.getKind() != null && !object.getKind().isOriginal()) { + quest.getKinds().add(object.getKind()); + } + } + } + } + + quest.setFile(outputFile); xmlMapper.writeValue(outputFile, quest); quest.setModified(false); } + + private boolean questContainsKindsFromIconPacks(List questBoards, List kinds) { + for (Quest.Board questBoard : questBoards) { + for (Quest.Board.Object object : questBoard.getObjects()) { + if (object.getKind() != null) { + if (!object.getKind().isOriginal()) { + return true; + } + } + } + } + return false; + } } \ No newline at end of file