From 2249ddbc6898a6a0d5862afcd9aedcdfe5ab5ffa Mon Sep 17 00:00:00 2001 From: Siphalor Date: Mon, 6 Jul 2020 19:42:48 +0200 Subject: [PATCH] A huge amount of work on GUI --- packs/test/lang/de_de.lang | 54 ++++++ src/main/java/de/siphalor/was/Start.java | 6 +- .../java/de/siphalor/was/WhatAStorage.java | 62 ++++++- .../de/siphalor/was/assets/AssetsManager.java | 26 +-- .../de/siphalor/was/content/lang/I18n.java | 36 +++- .../de/siphalor/was/content/lang/Lang.java | 8 + .../siphalor/was/content/product/Product.java | 28 +++ .../was/content/product/ProductManager.java | 2 +- .../was/content/product/ProductType.java | 11 +- .../product/dynamic/DynamicProduct.java | 5 + .../product/dynamic/DynamicProductType.java | 24 ++- .../de/siphalor/was/content/quest/Quest.java | 2 +- .../was/content/quest/QuestGenerator.java | 20 +-- .../content/quest/StaticQuestGenerator.java | 4 +- .../java/de/siphalor/was/game/Balance.java | 50 ++++++ .../java/de/siphalor/was/game/Storage.java | 29 ++- .../de/siphalor/was/game/StorageSlot.java | 73 ++++++++ .../de/siphalor/was/visual/CanvasVisual.java | 22 +++ .../de/siphalor/was/visual/JFXVisual.java | 132 +++++++++++++- .../java/de/siphalor/was/visual/Visual.java | 8 + .../siphalor/was/visual/jfx/BalanceEntry.java | 25 +++ .../was/visual/jfx/MainController.java | 43 +++++ .../was/visual/jfx/QuestController.java | 30 ++++ .../was/visual/layout/MainSwingUI.form | 68 ------- .../was/visual/layout/MainSwingUI.java | 10 -- src/main/resources/assets/jfx/main.css | 55 ++++++ src/main/resources/assets/jfx/main.fxml | 168 +++++++++++++++++- .../resources/assets/jfx/quest_widget.fxml | 62 +++++++ src/main/resources/content/lang/en_us.lang | 23 +++ .../content/products/paper.properties | 2 +- .../content/products/wood.properties | 1 + 31 files changed, 940 insertions(+), 149 deletions(-) create mode 100644 src/main/java/de/siphalor/was/game/Balance.java create mode 100644 src/main/java/de/siphalor/was/game/StorageSlot.java create mode 100644 src/main/java/de/siphalor/was/visual/jfx/BalanceEntry.java create mode 100644 src/main/java/de/siphalor/was/visual/jfx/MainController.java create mode 100644 src/main/java/de/siphalor/was/visual/jfx/QuestController.java delete mode 100644 src/main/java/de/siphalor/was/visual/layout/MainSwingUI.form delete mode 100644 src/main/java/de/siphalor/was/visual/layout/MainSwingUI.java create mode 100644 src/main/resources/assets/jfx/main.css create mode 100644 src/main/resources/assets/jfx/quest_widget.fxml diff --git a/packs/test/lang/de_de.lang b/packs/test/lang/de_de.lang index bcb4572..9a082b2 100644 --- a/packs/test/lang/de_de.lang +++ b/packs/test/lang/de_de.lang @@ -1 +1,55 @@ test.hello-world = Hallo Welt! + +game.budget = Budget: %d\u20ac +game.quit = Spiel beenden +game.quests = Auftr\u00e4ge +game.quests.next = N\u00e4chster Auftrag +game.quest.reward = %d\u20ac +game.trash = M\u00fclltonne +game.trash.hover = Zerst\u00f6ren +game.storage = Lager +game.balance.history = Buchungen +game.balance.history.change = Wert in \u20ac +game.balance.history.type = Beschreibung +game.balance.history.type.abandon = Auftrag abgelehnt +game.balance.history.type.destroy = Produkt zerst\u00f6rt +game.balance.history.type.move = Produkt bewegt +game.balance.history.type.noop = NOOP +game.balance.history.type.sell = Produkt ausgelagert +game.balance.history.type.store = Produkt eingelagert +game.balance.chart = Bilanz +game.balance.chart.line = Budget +game.balance.total-income = Gesamteinahmen: %d\u20ac +game.balance.total-loss = Gesamtverluste: %d\u20ac + +quests.normal = Normal + +products.paper = Papier +products.paper.color = Farbe +products.paper.color.white = wei\u00df +products.paper.color.green = gr\u00fcn +products.paper.color.blue = blau +products.paper.format = Format +products.paper.format.a3 = A3 +products.paper.format.a4 = A4 +products.paper.format.a5 = A5 + +products.wood = Holz +products.wood.type = Art +products.wood.type.beech = Buche +products.wood.type.oak = Eiche +products.wood.type.pine = Fichte +products.wood.form = Form +products.wood.form.pieces = Scheite +products.wood.form.boards = Bretter +products.wood.form.beams = Balken + +products.stone = Stein +products.stone.type = Art +products.stone.type.granite = Granit +products.stone.type.marble = Marmor +products.stone.type.sandstone = Sandstein +products.stone.weight = Gewicht +products.stone.weight.light = Leicht +products.stone.weight.medium = Mittel +products.stone.weight.heavy = Schwer diff --git a/src/main/java/de/siphalor/was/Start.java b/src/main/java/de/siphalor/was/Start.java index 918f45f..91264fc 100644 --- a/src/main/java/de/siphalor/was/Start.java +++ b/src/main/java/de/siphalor/was/Start.java @@ -6,10 +6,10 @@ public class Start { public static void main(String[] args) { WhatAStorage was = WhatAStorage.getInstance(); was.reload(); - was.start(); + was.setup(); - I18n.setLang("de_de", was.getContentManager()); - System.out.println(I18n.get("test.hello-world")); + I18n.getInstance().setLang("de_de", was.getContentManager()); + System.out.println(I18n.getInstance().getString("test.hello-world")); was.run(); } diff --git a/src/main/java/de/siphalor/was/WhatAStorage.java b/src/main/java/de/siphalor/was/WhatAStorage.java index 6724e9a..5b4b80e 100644 --- a/src/main/java/de/siphalor/was/WhatAStorage.java +++ b/src/main/java/de/siphalor/was/WhatAStorage.java @@ -5,14 +5,21 @@ import de.siphalor.was.content.lang.I18n; import de.siphalor.was.content.pack.FileContentPack; import de.siphalor.was.content.pack.JarContentPack; import de.siphalor.was.content.product.ProductManager; +import de.siphalor.was.content.quest.Quest; +import de.siphalor.was.content.quest.QuestGenerator; import de.siphalor.was.content.quest.QuestManager; -import de.siphalor.was.visual.CanvasVisual; +import de.siphalor.was.game.Balance; +import de.siphalor.was.visual.JFXVisual; import de.siphalor.was.visual.Visual; import java.io.File; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; public class WhatAStorage { + private static final int MAX_QUESTS = 3; + public static final String TITLE = "What a Storage"; private static final WhatAStorage INSTANCE = new WhatAStorage(); @@ -29,6 +36,10 @@ public class WhatAStorage { private boolean stopScheduled; + private Balance balance; + private QuestGenerator questGenerator; + private final List quests = new ArrayList<>(MAX_QUESTS); + private WhatAStorage() { contentManager = new ContentManager(); mainPack = new JarContentPack("", "content"); @@ -36,7 +47,7 @@ public class WhatAStorage { productManager = new ProductManager(); questManager = new QuestManager(); - visual = new CanvasVisual(); + visual = new JFXVisual(); } public ContentManager getContentManager() { @@ -61,7 +72,7 @@ public class WhatAStorage { contentManager.addPack(new FileContentPack(dir.getName(), dir.toPath())); } - I18n.reload(contentManager); + I18n.getInstance().reload(contentManager); productManager.clear(); questManager.clear(); @@ -71,19 +82,62 @@ public class WhatAStorage { System.out.println("Reloaded game content"); } - public void start() { + public void setup() { visual.setup(this); + loadGame(); } public void run() { visual.run(); } + public void loadGame() { + questGenerator = questManager.get("normal"); + quests.clear(); + balance = new Balance(); + } + public void scheduleStop() { stopScheduled = true; + visual.onScheduleStop(); } public boolean isStopScheduled() { return stopScheduled; } + + public Balance getBalance() { + return balance; + } + + private void addTransaction(Balance.Transaction transaction, int change) { + balance.add(transaction, change); + visual.onBalanceChanged(balance.getBudget(), transaction, change, balance.getTotalIncome(), balance.getTotalLoss()); + } + + public boolean requestQuest() { + if (quests.size() >= MAX_QUESTS) return false; + + if (!questGenerator.hasMoreElements()) { + return false; + } + + Quest next = questGenerator.nextElement(); + quests.add(next); + + visual.onQuestAdded(next, quests.size() < MAX_QUESTS); + return true; + } + + public void abandonQuest(int index) { + if (index >= quests.size()) { + System.out.println("INTERNAL ERROR: Attempted to abandon non-existent quest!"); + return; + } + + Quest quest = quests.remove(index); + addTransaction(Balance.Transaction.ABANDON, -quest.getReward()); + + visual.onQuestAbandoned(index); + } } diff --git a/src/main/java/de/siphalor/was/assets/AssetsManager.java b/src/main/java/de/siphalor/was/assets/AssetsManager.java index 9e53bd4..9208353 100644 --- a/src/main/java/de/siphalor/was/assets/AssetsManager.java +++ b/src/main/java/de/siphalor/was/assets/AssetsManager.java @@ -7,33 +7,39 @@ import javax.imageio.ImageIO; import java.awt.*; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.util.HashMap; import java.util.Map; +import java.util.Optional; public class AssetsManager { private static final Map imageCache = new HashMap<>(); + @NotNull public static final Image MISSINGNO = getImage("textures/missingno.png"); - @Nullable - public static InputStream getResource(@NotNull String path) { - return Thread.currentThread().getContextClassLoader().getResourceAsStream("assets/" + path); + @NotNull + public static Optional getStream(@NotNull String path) { + return Optional.ofNullable(Thread.currentThread().getContextClassLoader().getResourceAsStream("assets/" + path)); } + @NotNull + public static Optional getURL(@NotNull String path) { + return Optional.ofNullable(Thread.currentThread().getContextClassLoader().getResource(path)); + } + + @NotNull public static Image getImage(@NotNull String path) { Image image = imageCache.get(path); if (image == null) { - InputStream inputStream = getResource(path); - if (inputStream != null) { + image = getStream(path).map(inputStream -> { try { - image = ImageIO.read(inputStream); + return (Image) ImageIO.read(inputStream); } catch (IOException e) { e.printStackTrace(); - image = MISSINGNO; } - } else { - image = MISSINGNO; - } + return null; + }).orElse(MISSINGNO); imageCache.put(path, image); } return image; diff --git a/src/main/java/de/siphalor/was/content/lang/I18n.java b/src/main/java/de/siphalor/was/content/lang/I18n.java index f2a6994..3411f2b 100644 --- a/src/main/java/de/siphalor/was/content/lang/I18n.java +++ b/src/main/java/de/siphalor/was/content/lang/I18n.java @@ -4,21 +4,30 @@ import de.siphalor.was.content.ContentManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class I18n { +import java.util.*; + +public class I18n extends ResourceBundle { public static final String DEFAULT = "en_us"; - private static final Lang DEFAULT_LANG = new Lang(DEFAULT); + + private final Lang DEFAULT_LANG = new Lang(DEFAULT); + + private static final I18n INSTANCE = new I18n(); + + public static I18n getInstance() { + return INSTANCE; + } @Nullable - private static Lang lang; + private Lang lang; - public static void setLang(@NotNull String code, @NotNull ContentManager contentManager) { + public void setLang(@NotNull String code, @NotNull ContentManager contentManager) { if (lang == null || !lang.getCode().equals(code)) { lang = new Lang(code); lang.load(contentManager); } } - public static void reload(@NotNull ContentManager contentManager) { + public void reload(@NotNull ContentManager contentManager) { DEFAULT_LANG.load(contentManager); if (lang != null) { lang.load(contentManager); @@ -26,7 +35,13 @@ public class I18n { } @NotNull - public static String get(@NotNull String key) { + public String format(@NotNull String key, @Nullable Object... args) { + return String.format(getString(key), args); + } + + @Override + @NotNull + protected Object handleGetObject(@NotNull String key) { String val; if (lang != null) { val = lang.get(key); @@ -44,7 +59,12 @@ public class I18n { } @NotNull - public static String format(@NotNull String key, @Nullable Object... args) { - return String.format(get(key), args); + @Override + public Enumeration getKeys() { + if (lang == null) + return DEFAULT_LANG.getKeys(); + Set keys = new HashSet<>(Collections.list(lang.getKeys())); + keys.addAll(Collections.list(DEFAULT_LANG.getKeys())); + return Collections.enumeration(keys); } } diff --git a/src/main/java/de/siphalor/was/content/lang/Lang.java b/src/main/java/de/siphalor/was/content/lang/Lang.java index 9231694..8ef54c4 100644 --- a/src/main/java/de/siphalor/was/content/lang/Lang.java +++ b/src/main/java/de/siphalor/was/content/lang/Lang.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.InputStream; +import java.util.Enumeration; import java.util.Optional; import java.util.Properties; @@ -50,4 +51,11 @@ public class Lang { public String get(@NotNull String key) { return (String) properties.get(key); } + + @NotNull + public Enumeration getKeys() { + // This is really ugly but we know™ that the keys will always be strings + //noinspection unchecked + return (Enumeration)(Object) properties.keys(); + } } diff --git a/src/main/java/de/siphalor/was/content/product/Product.java b/src/main/java/de/siphalor/was/content/product/Product.java index cb1bbe8..84a7e63 100644 --- a/src/main/java/de/siphalor/was/content/product/Product.java +++ b/src/main/java/de/siphalor/was/content/product/Product.java @@ -1,14 +1,42 @@ package de.siphalor.was.content.product; +import de.siphalor.was.content.lang.I18n; import org.jetbrains.annotations.NotNull; public interface Product { @NotNull String getPropertySpecifier(); + String[] getProperties(); int getDepth(); boolean testY(int y); @NotNull ProductType getType(); + + default String getTranslationKey() { + return "products." + getType().getId(); + } + + default String getName(I18n i18n) { + return i18n.getString(getTranslationKey()); + } + + default String getDescription(I18n i18n) { + String[] props = getType().getProperties(); + String[] values = getProperties(); + + String base = getTranslationKey(); + + StringBuilder res = new StringBuilder(); + for (int i = 0; i < props.length; i++) { + res.append(i18n.getString(base + "." + props[i])); + res.append(": "); + res.append(i18n.getString(base + "." + props[i] + "." + values[i])); + res.append(", "); + } + res.delete(res.length() - 2, res.length()); + + return res.toString(); + } } diff --git a/src/main/java/de/siphalor/was/content/product/ProductManager.java b/src/main/java/de/siphalor/was/content/product/ProductManager.java index d220922..d36d595 100644 --- a/src/main/java/de/siphalor/was/content/product/ProductManager.java +++ b/src/main/java/de/siphalor/was/content/product/ProductManager.java @@ -23,7 +23,7 @@ public class ProductManager implements ResourceManager> { contentManager.getResources("products", "properties").forEach(resource -> { InputStream inputStream = resource.getInputStream(); if (inputStream != null) { - productTypes.put(resource.getId(), DynamicProductType.from(inputStream)); + productTypes.put(resource.getId(), DynamicProductType.from(resource.getId(), inputStream)); try { inputStream.close(); } catch (IOException e) { diff --git a/src/main/java/de/siphalor/was/content/product/ProductType.java b/src/main/java/de/siphalor/was/content/product/ProductType.java index c51915f..9367f4d 100644 --- a/src/main/java/de/siphalor/was/content/product/ProductType.java +++ b/src/main/java/de/siphalor/was/content/product/ProductType.java @@ -1,12 +1,11 @@ package de.siphalor.was.content.product; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public abstract class ProductType { +public interface ProductType { - public ProductType() { - } - - @Nullable - public abstract T getProduct(String[] values); + @Nullable T getProduct(@NotNull String[] values); + @NotNull String getId(); + @NotNull String[] getProperties(); } diff --git a/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProduct.java b/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProduct.java index 026cd0b..00b2a0b 100644 --- a/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProduct.java +++ b/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProduct.java @@ -25,6 +25,11 @@ public class DynamicProduct implements Product { return String.join("_", properties); } + @Override + public String[] getProperties() { + return properties; + } + public void setDepth(int depth) { this.depth = depth; } diff --git a/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProductType.java b/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProductType.java index e752e9e..0c83da4 100644 --- a/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProductType.java +++ b/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProductType.java @@ -10,11 +10,13 @@ import java.util.*; import java.util.function.IntPredicate; import java.util.stream.Collectors; -public class DynamicProductType extends ProductType { +public class DynamicProductType implements ProductType { + private final String id; private final Map variations; private final String[] properties; - public DynamicProductType(List variations, String[] properties) { + public DynamicProductType(String id, List variations, String[] properties) { + this.id = id; this.variations = new HashMap<>(); variations.forEach(p -> { p.setType(this); @@ -24,7 +26,7 @@ public class DynamicProductType extends ProductType { } @NotNull - public static DynamicProductType from(@NotNull InputStream inputStream) { + public static DynamicProductType from(@NotNull String id, @NotNull InputStream inputStream) { Properties propertiesFile = new Properties(); try { @@ -71,13 +73,13 @@ public class DynamicProductType extends ProductType { return product; }).collect(Collectors.toList()); - return new DynamicProductType(products, propNameList.toArray(String[]::new)); + return new DynamicProductType(id, products, propNameList.toArray(String[]::new)); } } catch (IOException | NullPointerException e) { e.printStackTrace(); } - return new DynamicProductType(List.of(new DynamicProduct(Util.emptyArray())), Util.emptyArray()); + return new DynamicProductType(id, List.of(new DynamicProduct(Util.emptyArray())), Util.emptyArray()); } private static ProductPrototype makePrototype(Properties propertiesFile, String property, String variant) { @@ -109,10 +111,20 @@ public class DynamicProductType extends ProductType { } @Override - public DynamicProduct getProduct(String[] values) { + public DynamicProduct getProduct(@NotNull String[] values) { return variations.get(String.join("_", values)); } + @Override + public @NotNull String getId() { + return id; + } + + @Override + public @NotNull String[] getProperties() { + return properties; + } + private static class ProductPrototype { String value; diff --git a/src/main/java/de/siphalor/was/content/quest/Quest.java b/src/main/java/de/siphalor/was/content/quest/Quest.java index 00ac82f..f9493ec 100644 --- a/src/main/java/de/siphalor/was/content/quest/Quest.java +++ b/src/main/java/de/siphalor/was/content/quest/Quest.java @@ -30,7 +30,7 @@ public class Quest { return reward; } - enum Type { + public enum Type { IN, OUT } } diff --git a/src/main/java/de/siphalor/was/content/quest/QuestGenerator.java b/src/main/java/de/siphalor/was/content/quest/QuestGenerator.java index 6e5300e..19603e3 100644 --- a/src/main/java/de/siphalor/was/content/quest/QuestGenerator.java +++ b/src/main/java/de/siphalor/was/content/quest/QuestGenerator.java @@ -1,6 +1,8 @@ package de.siphalor.was.content.quest; -public interface QuestGenerator { +import java.util.Enumeration; + +public interface QuestGenerator extends Enumeration { /** * Restarts this generator */ @@ -9,21 +11,7 @@ public interface QuestGenerator { /** * Returns the next {@link Quest} but doesn't advance * @return the next {@link Quest} - * @see QuestGenerator#next() + * @see QuestGenerator#nextElement() */ Quest peek(); - - /** - * Returns the next {@link Quest} and advances - * @return the next {@link Quest} - * @see QuestGenerator#peek() - */ - Quest next(); - - /** - * Returns whether another {@link Quest} can be generated - * @return whether the generator is at its end - * @see QuestGenerator#restart() - */ - boolean hasNext(); } diff --git a/src/main/java/de/siphalor/was/content/quest/StaticQuestGenerator.java b/src/main/java/de/siphalor/was/content/quest/StaticQuestGenerator.java index 481ebae..e79afb4 100644 --- a/src/main/java/de/siphalor/was/content/quest/StaticQuestGenerator.java +++ b/src/main/java/de/siphalor/was/content/quest/StaticQuestGenerator.java @@ -78,12 +78,12 @@ public class StaticQuestGenerator implements QuestGenerator { } @Override - public Quest next() { + public Quest nextElement() { return quests.get(index++); } @Override - public boolean hasNext() { + public boolean hasMoreElements() { return index < quests.size(); } } diff --git a/src/main/java/de/siphalor/was/game/Balance.java b/src/main/java/de/siphalor/was/game/Balance.java new file mode 100644 index 0000000..2b36bcc --- /dev/null +++ b/src/main/java/de/siphalor/was/game/Balance.java @@ -0,0 +1,50 @@ +package de.siphalor.was.game; + +import de.siphalor.was.util.Pair; + +import java.util.Locale; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class Balance { + private int budget = 0; + private int totalIncome = 0; + private int totalLoss = 0; + private final Queue> history = new ConcurrentLinkedQueue<>(); + + public void add(Transaction transaction, int change) { + history.add(Pair.of(transaction, change)); + + budget += change; + + if (change < 0) { + totalLoss -= change; + } else { + totalIncome += change; + } + } + + public Queue> getHistory() { + return history; + } + + public int getBudget() { + return budget; + } + + public int getTotalIncome() { + return totalIncome; + } + + public int getTotalLoss() { + return totalLoss; + } + + public enum Transaction { + ABANDON, DESTROY, MOVE, NOOP, SELL, STORE; + + public String getTranslationKey() { + return "game.balance.history.type." + name().toLowerCase(Locale.ENGLISH); + } + } +} diff --git a/src/main/java/de/siphalor/was/game/Storage.java b/src/main/java/de/siphalor/was/game/Storage.java index b0b2a49..ea4cf16 100644 --- a/src/main/java/de/siphalor/was/game/Storage.java +++ b/src/main/java/de/siphalor/was/game/Storage.java @@ -1,5 +1,32 @@ package de.siphalor.was.game; -public class Storage { +import de.siphalor.was.content.product.Product; +import org.jetbrains.annotations.NotNull; +public class Storage { + StorageSlot[][] slots; + + public Storage(int width, int height, int depth) { + slots = new StorageSlot[height][width]; + + for (StorageSlot[] row : slots) { + for (int i = 0; i < row.length; i++) { + row[i] = new StorageSlot(depth); + } + } + } + + @NotNull + public StorageSlot get(int x, int y) { + return slots[x][y]; + } + + public boolean move(int x1, int y1, int x2, int y2) { + StorageSlot slot1 = get(x1, y1); + Product product = slot1.front(); + if (product != null) { + return get(x2, y2).add(product); + } + return false; + } } diff --git a/src/main/java/de/siphalor/was/game/StorageSlot.java b/src/main/java/de/siphalor/was/game/StorageSlot.java new file mode 100644 index 0000000..ca710a8 --- /dev/null +++ b/src/main/java/de/siphalor/was/game/StorageSlot.java @@ -0,0 +1,73 @@ +package de.siphalor.was.game; + +import de.siphalor.was.content.product.Product; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class StorageSlot { + final int depth; + final Product[] products; + + public StorageSlot(int depth) { + this.depth = depth; + this.products = new Product[depth]; + } + + @NotNull + public Product[] getProducts() { + return products; + } + + @Nullable + public Product front() { + for (int i = products.length - 1; i >= 0; i--) { + Product product = products[i]; + if (product != null) { + return product; + } + } + return null; + } + + @Nullable + public Product pop() { + for (int i = products.length - 1; i >= 0; i--) { + Product product = products[i]; + if (product != null) { + products[i] = null; + return product; + } + } + return null; + } + + public boolean fits(@NotNull Product product) { + int blocked = 0; + for (Product p : products) { + if (p == null) break; + blocked += p.getDepth(); + } + + return product.getDepth() <= depth - blocked; + } + + public boolean add(@NotNull Product product) { + int blocked = 0; + int i; + for (i = 0; i < products.length; i++) { + Product p = products[i]; + if (p == null) { + break; + } + blocked += p.getDepth(); + if (blocked >= depth) { + return false; + } + } + if (i < products.length && product.getDepth() <= depth - blocked) { + products[i] = product; + return true; + } + return false; + } +} diff --git a/src/main/java/de/siphalor/was/visual/CanvasVisual.java b/src/main/java/de/siphalor/was/visual/CanvasVisual.java index 3f9d513..5f08a9e 100644 --- a/src/main/java/de/siphalor/was/visual/CanvasVisual.java +++ b/src/main/java/de/siphalor/was/visual/CanvasVisual.java @@ -1,6 +1,8 @@ package de.siphalor.was.visual; import de.siphalor.was.WhatAStorage; +import de.siphalor.was.content.quest.Quest; +import de.siphalor.was.game.Balance; import de.siphalor.was.visual.canvas.layout.FixedAspectLayout; import de.siphalor.was.visual.canvas.layout.FulfillingLayout; @@ -119,4 +121,24 @@ public class CanvasVisual implements Visual { frame.setVisible(false); frame.dispose(); } + + @Override + public void onScheduleStop() { + + } + + @Override + public void onBalanceChanged(int budget, Balance.Transaction transaction, int change, int totalIncome, int totalLoss) { + + } + + @Override + public void onQuestAdded(Quest newQuest, boolean canCreateMore) { + + } + + @Override + public void onQuestAbandoned(int index) { + + } } diff --git a/src/main/java/de/siphalor/was/visual/JFXVisual.java b/src/main/java/de/siphalor/was/visual/JFXVisual.java index 32aa78e..763d2dc 100644 --- a/src/main/java/de/siphalor/was/visual/JFXVisual.java +++ b/src/main/java/de/siphalor/was/visual/JFXVisual.java @@ -2,14 +2,36 @@ package de.siphalor.was.visual; import de.siphalor.was.WhatAStorage; import de.siphalor.was.assets.AssetsManager; +import de.siphalor.was.content.lang.I18n; +import de.siphalor.was.content.product.Product; +import de.siphalor.was.content.quest.Quest; +import de.siphalor.was.game.Balance; +import de.siphalor.was.util.Pair; +import de.siphalor.was.visual.jfx.BalanceEntry; +import de.siphalor.was.visual.jfx.MainController; +import de.siphalor.was.visual.jfx.QuestController; import javafx.application.Application; +import javafx.collections.ObservableList; import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; import javafx.scene.Scene; -import javafx.scene.layout.HBox; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.control.TableColumn; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.image.Image; +import javafx.scene.layout.Pane; import javafx.stage.Stage; +import java.io.IOException; + public class JFXVisual extends Application implements Visual { - private Scene mainScene; + private static Stage primaryStage; + private static final FXMLLoader loader = new FXMLLoader(); + + private static Scene mainScene; + + private static MainController controller; @Override public void setup(WhatAStorage whatAStorage) { @@ -21,9 +43,109 @@ public class JFXVisual extends Application implements Visual { } @Override - public void start(Stage primaryStage) throws Exception { - FXMLLoader loader = new FXMLLoader(); + public void onScheduleStop() { + primaryStage.close(); + } - HBox hb = loader.load(AssetsManager.getResource("jfx/main.fxml")); + @Override + public void onBalanceChanged(int budget, Balance.Transaction transaction, int change, int totalIncome, int totalLoss) { + I18n i18n = I18n.getInstance(); + + controller.budgetLabel.setText(i18n.format("game.budget", budget)); + if (budget < 0) { + controller.budgetLabel.getStyleClass().add("red"); + } else { + controller.budgetLabel.getStyleClass().remove("red"); + } + + controller.totalIncomeLabel.setText(i18n.format("game.balance.total-income", totalIncome)); + controller.totalLossLabel.setText(i18n.format("game.balance.total-loss", totalLoss)); + + ObservableList> data = controller.budgetChartSeries.getData(); + int i = data.size(); + data.add(new XYChart.Data<>(data.size(), budget)); + + if (transaction != Balance.Transaction.NOOP) { + controller.balanceHistoryTable.getItems().add(new BalanceEntry(i, change, i18n.getString(transaction.getTranslationKey()))); + controller.balanceHistoryTable.sort(); + } + } + + @Override + public void onQuestAdded(Quest newQuest, boolean canCreateMore) { + addQuest(newQuest); + + controller.nextQuestButton.setDisable(!canCreateMore); + } + + @Override + public void onQuestAbandoned(int index) { + controller.questBox.getChildren().remove(index); + + controller.nextQuestButton.setDisable(false); + } + + @Override + public void start(Stage primaryStage) throws Exception { + JFXVisual.primaryStage = primaryStage; + primaryStage.setMinWidth(850); + primaryStage.setTitle(WhatAStorage.TITLE); + + loader.setResources(I18n.getInstance()); + controller = new MainController(WhatAStorage.getInstance()); + loader.setController(controller); + + Pane pane = loader.load(AssetsManager.getStream("jfx/main.fxml").get()); + mainScene = new Scene(pane); + mainScene.getStylesheets().add("assets/jfx/main.css"); + + ObservableList> columns = controller.balanceHistoryTable.getColumns(); + columns.get(0).setCellValueFactory(new PropertyValueFactory<>("index")); + columns.get(1).setCellValueFactory(new PropertyValueFactory<>("change")); + columns.get(2).setCellValueFactory(new PropertyValueFactory<>("description")); + + loadMainScene(); + //noinspection unchecked + controller.budgetChart.getData().add((XYChart.Series)(Object) controller.budgetChartSeries); + + primaryStage.show(); + } + + public void loadMainScene() { + primaryStage.setScene(mainScene); + + AssetsManager.getStream("textures/bin_closed.png").ifPresent(inputStream -> { + controller.trash.setImage(new Image(inputStream)); + }); + controller.budgetChartSeries.setName(I18n.getInstance().getString("game.balance.chart.line")); + + onBalanceChanged(0, Balance.Transaction.NOOP, 0, 0, 0); + } + + public void addQuest(Quest quest) { + AssetsManager.getStream("jfx/quest_widget.fxml").ifPresentOrElse(is -> { + try { + FXMLLoader loader = new FXMLLoader(); + QuestController questController = new QuestController(WhatAStorage.getInstance()); + loader.setController(questController); + Parent parent = loader.load(is); + + controller.questBox.getChildren().add(parent); + + Product product = quest.getProduct(); + + I18n i18n = I18n.getInstance(); + questController.title.setText(product.getName(i18n)); + + questController.description.setText(product.getDescription(i18n)); + + questController.reward.setText(i18n.format("game.quest.reward", quest.getReward())); + + } catch (IOException e) { + e.printStackTrace(); + } + }, () -> { + System.out.println("INTERNAL ERROR: Failed to load quest widget"); + }); } } diff --git a/src/main/java/de/siphalor/was/visual/Visual.java b/src/main/java/de/siphalor/was/visual/Visual.java index 07e63b1..4813e2e 100644 --- a/src/main/java/de/siphalor/was/visual/Visual.java +++ b/src/main/java/de/siphalor/was/visual/Visual.java @@ -1,8 +1,16 @@ package de.siphalor.was.visual; import de.siphalor.was.WhatAStorage; +import de.siphalor.was.content.quest.Quest; +import de.siphalor.was.game.Balance; public interface Visual { void setup(WhatAStorage whatAStorage); void run(); + void onScheduleStop(); + + void onBalanceChanged(int budget, Balance.Transaction transaction, int change, int totalIncome, int totalLoss); + + void onQuestAdded(Quest newQuest, boolean canCreateMore); + void onQuestAbandoned(int index); } diff --git a/src/main/java/de/siphalor/was/visual/jfx/BalanceEntry.java b/src/main/java/de/siphalor/was/visual/jfx/BalanceEntry.java new file mode 100644 index 0000000..9af63e9 --- /dev/null +++ b/src/main/java/de/siphalor/was/visual/jfx/BalanceEntry.java @@ -0,0 +1,25 @@ +package de.siphalor.was.visual.jfx; + +public class BalanceEntry { + public final int index; + public final int change; + public final String description; + + public BalanceEntry(int index, int change, String description) { + this.index = index; + this.change = change; + this.description = description; + } + + public int getIndex() { + return index; + } + + public int getChange() { + return change; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/de/siphalor/was/visual/jfx/MainController.java b/src/main/java/de/siphalor/was/visual/jfx/MainController.java new file mode 100644 index 0000000..f3f49cc --- /dev/null +++ b/src/main/java/de/siphalor/was/visual/jfx/MainController.java @@ -0,0 +1,43 @@ +package de.siphalor.was.visual.jfx; + +import de.siphalor.was.WhatAStorage; +import de.siphalor.was.util.Pair; +import javafx.fxml.FXML; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TableView; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; + +public class MainController { + private final WhatAStorage was; + + public Label budgetLabel; + public Label totalIncomeLabel; + public Label totalLossLabel; + public TableView balanceHistoryTable; + public LineChart budgetChart; + public XYChart.Series budgetChartSeries = new XYChart.Series<>(); + + public ImageView trash; + + public VBox questBox; + public Button nextQuestButton; + + public MainController(WhatAStorage was) { + this.was = was; + } + + @FXML + private void scheduleStop() { + was.scheduleStop(); + } + + @FXML + private void nextQuest() { + was.requestQuest(); + } +} diff --git a/src/main/java/de/siphalor/was/visual/jfx/QuestController.java b/src/main/java/de/siphalor/was/visual/jfx/QuestController.java new file mode 100644 index 0000000..d8cf9a8 --- /dev/null +++ b/src/main/java/de/siphalor/was/visual/jfx/QuestController.java @@ -0,0 +1,30 @@ +package de.siphalor.was.visual.jfx; + +import de.siphalor.was.WhatAStorage; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; + +public class QuestController { + private final WhatAStorage was; + + @FXML + private GridPane questContainer; + + public Label title; + public Label description; + public Label reward; + + public ImageView image; + + public QuestController(WhatAStorage was) { + this.was = was; + } + + @FXML + private void abandon() { + int index = questContainer.getParent().getChildrenUnmodifiable().indexOf(questContainer); + was.abandonQuest(index); + } +} diff --git a/src/main/java/de/siphalor/was/visual/layout/MainSwingUI.form b/src/main/java/de/siphalor/was/visual/layout/MainSwingUI.form deleted file mode 100644 index 5fe8ed9..0000000 --- a/src/main/java/de/siphalor/was/visual/layout/MainSwingUI.form +++ /dev/null @@ -1,68 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/main/java/de/siphalor/was/visual/layout/MainSwingUI.java b/src/main/java/de/siphalor/was/visual/layout/MainSwingUI.java deleted file mode 100644 index fdeb49b..0000000 --- a/src/main/java/de/siphalor/was/visual/layout/MainSwingUI.java +++ /dev/null @@ -1,10 +0,0 @@ -package de.siphalor.was.visual.layout; - -import javax.swing.*; - -public class MainSwingUI { - private JPanel panel1; - private JButton nextQuestButton; - private JTable table1; - private JButton destroyButton; -} diff --git a/src/main/resources/assets/jfx/main.css b/src/main/resources/assets/jfx/main.css new file mode 100644 index 0000000..6c72d3d --- /dev/null +++ b/src/main/resources/assets/jfx/main.css @@ -0,0 +1,55 @@ +Button.green { + -fx-background-color: #56893b; +} +Button.green:pressed { + -fx-background-color: #457229; +} +Button.red { + -fx-background-color: #ff4848; +} +Button.red:pressed { + -fx-background-color: #9c2121; +} + +ScrollPane { + -fx-background-color: inherit; +} + +Label.red { + -fx-text-fill: #9c2121; +} +Label.green { + -fx-text-fill: #457229; +} + +.align-right { + -fx-alignment: center-right; +} + +.side-pane { + -fx-border-width: 0 1; + -fx-border-color: #aaaaaa; + -fx-background-color: linear-gradient(to bottom, #dddddd, #aaaaaa); +} + +.quest-container { + -fx-border-width: 1 0 0 0; + -fx-border-color: #aaaaaa; + -fx-background-color: #eeeeee; +} +.quest-reward { + -fx-text-fill: #457229; +} + +.tab-info-bar { + -fx-border-width: 0 0 1 0; + -fx-border-color: #aaaaaa; +} + +.table-column { + -fx-padding: 0 5; +} + +#main { + -fx-background-color: #dddddd; +} diff --git a/src/main/resources/assets/jfx/main.fxml b/src/main/resources/assets/jfx/main.fxml index ec8b75a..4d66a7a 100644 --- a/src/main/resources/assets/jfx/main.fxml +++ b/src/main/resources/assets/jfx/main.fxml @@ -1,9 +1,163 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/assets/jfx/quest_widget.fxml b/src/main/resources/assets/jfx/quest_widget.fxml new file mode 100644 index 0000000..19c6a30 --- /dev/null +++ b/src/main/resources/assets/jfx/quest_widget.fxml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/content/lang/en_us.lang b/src/main/resources/content/lang/en_us.lang index 9b07f7c..131d6f7 100644 --- a/src/main/resources/content/lang/en_us.lang +++ b/src/main/resources/content/lang/en_us.lang @@ -1,5 +1,28 @@ test.hello-world = Hello World! +game.budget = Budget: %d$ +game.quit = Quit Game +game.quests = Quests +game.quests.next = Next Quest +game.quest.reward = %d$ +game.trash = Recycle Bin +game.trash.hover = Destroy\n%d$ +game.storage = Storage +game.balance.history = Transactions +game.balance.history.index = +game.balance.history.change = Change in $ +game.balance.history.type = Description +game.balance.history.type.abandon = Quest abandoned +game.balance.history.type.destroy = Product destroy +game.balance.history.type.move = Product moved +game.balance.history.type.noop = NOOP +game.balance.history.type.sell = Product sold +game.balance.history.type.store = Product stored +game.balance.chart = Balance +game.balance.chart.line = Budget +game.balance.total-income = Total Income: %d$ +game.balance.total-loss = Total Loss: %d$ + quests.normal = Normal products.paper = Paper diff --git a/src/main/resources/content/products/paper.properties b/src/main/resources/content/products/paper.properties index e7b09ce..fe1acc0 100644 --- a/src/main/resources/content/products/paper.properties +++ b/src/main/resources/content/products/paper.properties @@ -1,3 +1,3 @@ properties = color, format -color.variants =white, green, blue +color.variants = white, green, blue format.variants = a3, a4, a5 diff --git a/src/main/resources/content/products/wood.properties b/src/main/resources/content/products/wood.properties index b240a85..9456cf4 100644 --- a/src/main/resources/content/products/wood.properties +++ b/src/main/resources/content/products/wood.properties @@ -2,3 +2,4 @@ properties = type, form type.variants = pine, beech, oak form.variants = boards, pieces, beams form.beams.depth = 3 +type.oak.weight = 1