A huge amount of work on GUI

This commit is contained in:
2020-07-06 19:42:48 +02:00
parent f3b8ae2385
commit 2249ddbc68
31 changed files with 940 additions and 149 deletions

View File

@@ -1 +1,55 @@
test.hello-world = Hallo Welt! 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

View File

@@ -6,10 +6,10 @@ public class Start {
public static void main(String[] args) { public static void main(String[] args) {
WhatAStorage was = WhatAStorage.getInstance(); WhatAStorage was = WhatAStorage.getInstance();
was.reload(); was.reload();
was.start(); was.setup();
I18n.setLang("de_de", was.getContentManager()); I18n.getInstance().setLang("de_de", was.getContentManager());
System.out.println(I18n.get("test.hello-world")); System.out.println(I18n.getInstance().getString("test.hello-world"));
was.run(); was.run();
} }

View File

@@ -5,14 +5,21 @@ import de.siphalor.was.content.lang.I18n;
import de.siphalor.was.content.pack.FileContentPack; import de.siphalor.was.content.pack.FileContentPack;
import de.siphalor.was.content.pack.JarContentPack; import de.siphalor.was.content.pack.JarContentPack;
import de.siphalor.was.content.product.ProductManager; 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.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 de.siphalor.was.visual.Visual;
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class WhatAStorage { public class WhatAStorage {
private static final int MAX_QUESTS = 3;
public static final String TITLE = "What a Storage"; public static final String TITLE = "What a Storage";
private static final WhatAStorage INSTANCE = new WhatAStorage(); private static final WhatAStorage INSTANCE = new WhatAStorage();
@@ -29,6 +36,10 @@ public class WhatAStorage {
private boolean stopScheduled; private boolean stopScheduled;
private Balance balance;
private QuestGenerator questGenerator;
private final List<Quest> quests = new ArrayList<>(MAX_QUESTS);
private WhatAStorage() { private WhatAStorage() {
contentManager = new ContentManager(); contentManager = new ContentManager();
mainPack = new JarContentPack("", "content"); mainPack = new JarContentPack("", "content");
@@ -36,7 +47,7 @@ public class WhatAStorage {
productManager = new ProductManager(); productManager = new ProductManager();
questManager = new QuestManager(); questManager = new QuestManager();
visual = new CanvasVisual(); visual = new JFXVisual();
} }
public ContentManager getContentManager() { public ContentManager getContentManager() {
@@ -61,7 +72,7 @@ public class WhatAStorage {
contentManager.addPack(new FileContentPack(dir.getName(), dir.toPath())); contentManager.addPack(new FileContentPack(dir.getName(), dir.toPath()));
} }
I18n.reload(contentManager); I18n.getInstance().reload(contentManager);
productManager.clear(); productManager.clear();
questManager.clear(); questManager.clear();
@@ -71,19 +82,62 @@ public class WhatAStorage {
System.out.println("Reloaded game content"); System.out.println("Reloaded game content");
} }
public void start() { public void setup() {
visual.setup(this); visual.setup(this);
loadGame();
} }
public void run() { public void run() {
visual.run(); visual.run();
} }
public void loadGame() {
questGenerator = questManager.get("normal");
quests.clear();
balance = new Balance();
}
public void scheduleStop() { public void scheduleStop() {
stopScheduled = true; stopScheduled = true;
visual.onScheduleStop();
} }
public boolean isStopScheduled() { public boolean isStopScheduled() {
return stopScheduled; 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);
}
} }

View File

@@ -7,33 +7,39 @@ import javax.imageio.ImageIO;
import java.awt.*; import java.awt.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
public class AssetsManager { public class AssetsManager {
private static final Map<String, Image> imageCache = new HashMap<>(); private static final Map<String, Image> imageCache = new HashMap<>();
@NotNull
public static final Image MISSINGNO = getImage("textures/missingno.png"); public static final Image MISSINGNO = getImage("textures/missingno.png");
@Nullable @NotNull
public static InputStream getResource(@NotNull String path) { public static Optional<InputStream> getStream(@NotNull String path) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream("assets/" + path); return Optional.ofNullable(Thread.currentThread().getContextClassLoader().getResourceAsStream("assets/" + path));
} }
@NotNull
public static Optional<URL> getURL(@NotNull String path) {
return Optional.ofNullable(Thread.currentThread().getContextClassLoader().getResource(path));
}
@NotNull
public static Image getImage(@NotNull String path) { public static Image getImage(@NotNull String path) {
Image image = imageCache.get(path); Image image = imageCache.get(path);
if (image == null) { if (image == null) {
InputStream inputStream = getResource(path); image = getStream(path).map(inputStream -> {
if (inputStream != null) {
try { try {
image = ImageIO.read(inputStream); return (Image) ImageIO.read(inputStream);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
image = MISSINGNO;
} }
} else { return null;
image = MISSINGNO; }).orElse(MISSINGNO);
}
imageCache.put(path, image); imageCache.put(path, image);
} }
return image; return image;

View File

@@ -4,21 +4,30 @@ import de.siphalor.was.content.ContentManager;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class I18n { import java.util.*;
public class I18n extends ResourceBundle {
public static final String DEFAULT = "en_us"; 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 @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)) { if (lang == null || !lang.getCode().equals(code)) {
lang = new Lang(code); lang = new Lang(code);
lang.load(contentManager); lang.load(contentManager);
} }
} }
public static void reload(@NotNull ContentManager contentManager) { public void reload(@NotNull ContentManager contentManager) {
DEFAULT_LANG.load(contentManager); DEFAULT_LANG.load(contentManager);
if (lang != null) { if (lang != null) {
lang.load(contentManager); lang.load(contentManager);
@@ -26,7 +35,13 @@ public class I18n {
} }
@NotNull @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; String val;
if (lang != null) { if (lang != null) {
val = lang.get(key); val = lang.get(key);
@@ -44,7 +59,12 @@ public class I18n {
} }
@NotNull @NotNull
public static String format(@NotNull String key, @Nullable Object... args) { @Override
return String.format(get(key), args); public Enumeration<String> getKeys() {
if (lang == null)
return DEFAULT_LANG.getKeys();
Set<String> keys = new HashSet<>(Collections.list(lang.getKeys()));
keys.addAll(Collections.list(DEFAULT_LANG.getKeys()));
return Collections.enumeration(keys);
} }
} }

View File

@@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Enumeration;
import java.util.Optional; import java.util.Optional;
import java.util.Properties; import java.util.Properties;
@@ -50,4 +51,11 @@ public class Lang {
public String get(@NotNull String key) { public String get(@NotNull String key) {
return (String) properties.get(key); return (String) properties.get(key);
} }
@NotNull
public Enumeration<String> getKeys() {
// This is really ugly but we know™ that the keys will always be strings
//noinspection unchecked
return (Enumeration<String>)(Object) properties.keys();
}
} }

View File

@@ -1,14 +1,42 @@
package de.siphalor.was.content.product; package de.siphalor.was.content.product;
import de.siphalor.was.content.lang.I18n;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public interface Product { public interface Product {
@NotNull @NotNull
String getPropertySpecifier(); String getPropertySpecifier();
String[] getProperties();
int getDepth(); int getDepth();
boolean testY(int y); boolean testY(int y);
@NotNull @NotNull
ProductType<?> getType(); 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();
}
} }

View File

@@ -23,7 +23,7 @@ public class ProductManager implements ResourceManager<ProductType<?>> {
contentManager.getResources("products", "properties").forEach(resource -> { contentManager.getResources("products", "properties").forEach(resource -> {
InputStream inputStream = resource.getInputStream(); InputStream inputStream = resource.getInputStream();
if (inputStream != null) { if (inputStream != null) {
productTypes.put(resource.getId(), DynamicProductType.from(inputStream)); productTypes.put(resource.getId(), DynamicProductType.from(resource.getId(), inputStream));
try { try {
inputStream.close(); inputStream.close();
} catch (IOException e) { } catch (IOException e) {

View File

@@ -1,12 +1,11 @@
package de.siphalor.was.content.product; package de.siphalor.was.content.product;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public abstract class ProductType<T extends Product> { public interface ProductType<T extends Product> {
public ProductType() { @Nullable T getProduct(@NotNull String[] values);
} @NotNull String getId();
@NotNull String[] getProperties();
@Nullable
public abstract T getProduct(String[] values);
} }

View File

@@ -25,6 +25,11 @@ public class DynamicProduct implements Product {
return String.join("_", properties); return String.join("_", properties);
} }
@Override
public String[] getProperties() {
return properties;
}
public void setDepth(int depth) { public void setDepth(int depth) {
this.depth = depth; this.depth = depth;
} }

View File

@@ -10,11 +10,13 @@ import java.util.*;
import java.util.function.IntPredicate; import java.util.function.IntPredicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class DynamicProductType extends ProductType<DynamicProduct> { public class DynamicProductType implements ProductType<DynamicProduct> {
private final String id;
private final Map<String, DynamicProduct> variations; private final Map<String, DynamicProduct> variations;
private final String[] properties; private final String[] properties;
public DynamicProductType(List<DynamicProduct> variations, String[] properties) { public DynamicProductType(String id, List<DynamicProduct> variations, String[] properties) {
this.id = id;
this.variations = new HashMap<>(); this.variations = new HashMap<>();
variations.forEach(p -> { variations.forEach(p -> {
p.setType(this); p.setType(this);
@@ -24,7 +26,7 @@ public class DynamicProductType extends ProductType<DynamicProduct> {
} }
@NotNull @NotNull
public static DynamicProductType from(@NotNull InputStream inputStream) { public static DynamicProductType from(@NotNull String id, @NotNull InputStream inputStream) {
Properties propertiesFile = new Properties(); Properties propertiesFile = new Properties();
try { try {
@@ -71,13 +73,13 @@ public class DynamicProductType extends ProductType<DynamicProduct> {
return product; return product;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
return new DynamicProductType(products, propNameList.toArray(String[]::new)); return new DynamicProductType(id, products, propNameList.toArray(String[]::new));
} }
} catch (IOException | NullPointerException e) { } catch (IOException | NullPointerException e) {
e.printStackTrace(); 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) { private static ProductPrototype makePrototype(Properties propertiesFile, String property, String variant) {
@@ -109,10 +111,20 @@ public class DynamicProductType extends ProductType<DynamicProduct> {
} }
@Override @Override
public DynamicProduct getProduct(String[] values) { public DynamicProduct getProduct(@NotNull String[] values) {
return variations.get(String.join("_", 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 { private static class ProductPrototype {
String value; String value;

View File

@@ -30,7 +30,7 @@ public class Quest {
return reward; return reward;
} }
enum Type { public enum Type {
IN, OUT IN, OUT
} }
} }

View File

@@ -1,6 +1,8 @@
package de.siphalor.was.content.quest; package de.siphalor.was.content.quest;
public interface QuestGenerator { import java.util.Enumeration;
public interface QuestGenerator extends Enumeration<Quest> {
/** /**
* Restarts this generator * Restarts this generator
*/ */
@@ -9,21 +11,7 @@ public interface QuestGenerator {
/** /**
* Returns the next {@link Quest} but doesn't advance * Returns the next {@link Quest} but doesn't advance
* @return the next {@link Quest} * @return the next {@link Quest}
* @see QuestGenerator#next() * @see QuestGenerator#nextElement()
*/ */
Quest peek(); 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();
} }

View File

@@ -78,12 +78,12 @@ public class StaticQuestGenerator implements QuestGenerator {
} }
@Override @Override
public Quest next() { public Quest nextElement() {
return quests.get(index++); return quests.get(index++);
} }
@Override @Override
public boolean hasNext() { public boolean hasMoreElements() {
return index < quests.size(); return index < quests.size();
} }
} }

View File

@@ -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<Pair<Transaction, Integer>> 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<Pair<Transaction, Integer>> 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);
}
}
}

View File

@@ -1,5 +1,32 @@
package de.siphalor.was.game; 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;
}
} }

View File

@@ -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;
}
}

View File

@@ -1,6 +1,8 @@
package de.siphalor.was.visual; package de.siphalor.was.visual;
import de.siphalor.was.WhatAStorage; 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.FixedAspectLayout;
import de.siphalor.was.visual.canvas.layout.FulfillingLayout; import de.siphalor.was.visual.canvas.layout.FulfillingLayout;
@@ -119,4 +121,24 @@ public class CanvasVisual implements Visual {
frame.setVisible(false); frame.setVisible(false);
frame.dispose(); 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) {
}
} }

View File

@@ -2,14 +2,36 @@ package de.siphalor.was.visual;
import de.siphalor.was.WhatAStorage; import de.siphalor.was.WhatAStorage;
import de.siphalor.was.assets.AssetsManager; 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.application.Application;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene; 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 javafx.stage.Stage;
import java.io.IOException;
public class JFXVisual extends Application implements Visual { 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 @Override
public void setup(WhatAStorage whatAStorage) { public void setup(WhatAStorage whatAStorage) {
@@ -21,9 +43,109 @@ public class JFXVisual extends Application implements Visual {
} }
@Override @Override
public void start(Stage primaryStage) throws Exception { public void onScheduleStop() {
FXMLLoader loader = new FXMLLoader(); 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<XYChart.Data<Integer, Integer>> 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<TableColumn<BalanceEntry, ?>> 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<NumberAxis, NumberAxis>)(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");
});
} }
} }

View File

@@ -1,8 +1,16 @@
package de.siphalor.was.visual; package de.siphalor.was.visual;
import de.siphalor.was.WhatAStorage; import de.siphalor.was.WhatAStorage;
import de.siphalor.was.content.quest.Quest;
import de.siphalor.was.game.Balance;
public interface Visual { public interface Visual {
void setup(WhatAStorage whatAStorage); void setup(WhatAStorage whatAStorage);
void run(); 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);
} }

View File

@@ -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;
}
}

View File

@@ -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<BalanceEntry> balanceHistoryTable;
public LineChart<NumberAxis, NumberAxis> budgetChart;
public XYChart.Series<Integer, Integer> 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();
}
}

View File

@@ -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);
}
}

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="de.siphalor.was.visual.layout.MainSwingUI">
<grid id="27dc6" binding="panel1" default-binding="true" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="639" height="461"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<splitpane id="d075b">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
<preferred-size width="200" height="200"/>
</grid>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="643da" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<splitpane position="left"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="833b4" class="javax.swing.JTable" binding="table1" default-binding="true">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false">
<preferred-size width="150" height="50"/>
</grid>
</constraints>
<properties/>
</component>
<component id="bfa1c" class="javax.swing.JButton" binding="destroyButton" default-binding="true">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Destroy"/>
</properties>
</component>
</children>
</grid>
<grid id="f0669" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<splitpane position="right"/>
</constraints>
<properties/>
<border type="none" title="Quests"/>
<children>
<component id="f8569" class="javax.swing.JButton" binding="nextQuestButton" default-binding="true">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Next Quest"/>
</properties>
</component>
</children>
</grid>
</children>
</splitpane>
</children>
</grid>
</form>

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,9 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<VBox> <?import javafx.geometry.Insets?>
<children> <?import javafx.scene.chart.LineChart?>
<Label text="Hello world FXML"/> <?import javafx.scene.chart.NumberAxis?>
</children> <?import javafx.scene.control.Accordion?>
</VBox> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="450.0" prefWidth="850.0" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="250.0" percentWidth="30.0" prefWidth="250.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" percentWidth="70.0" prefWidth="380.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints fillHeight="false" vgrow="NEVER" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<GridPane styleClass="side-pane" GridPane.rowIndex="1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints fillHeight="false" minHeight="-Infinity" vgrow="NEVER" />
<RowConstraints fillHeight="false" vgrow="NEVER" />
<RowConstraints maxHeight="1.7976931348623157E308" vgrow="SOMETIMES" />
<RowConstraints fillHeight="false" maxHeight="-Infinity" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<ScrollPane id="quest-pane" fitToWidth="true" hbarPolicy="NEVER" prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="2">
<content>
<VBox fx:id="questBox" />
</content>
</ScrollPane>
<Label text="%game.trash" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="BOTTOM">
<font>
<Font name="System Bold" size="19.0" />
</font>
</Label>
<ImageView fx:id="trash" fitHeight="80.0" pickOnBounds="true" preserveRatio="true" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="TOP">
<GridPane.margin>
<Insets bottom="35.0" top="10.0" />
</GridPane.margin>
</ImageView>
<Label text="%game.quests" GridPane.rowIndex="1">
<font>
<Font size="23.0" />
</font>
<padding>
<Insets left="10.0" />
</padding>
</Label>
<Button fx:id="nextQuestButton" mnemonicParsing="false" onAction="#nextQuest" styleClass="green" text="%game.quests.next" textFill="WHITE" GridPane.halignment="RIGHT" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets right="5.0" />
</GridPane.margin>
</Button>
<Accordion GridPane.rowIndex="1" />
</children>
</GridPane>
<TabPane tabClosingPolicy="UNAVAILABLE" GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets />
</GridPane.margin>
<tabs>
<Tab text="%game.storage">
<content>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>
</content>
</Tab>
<Tab text="%game.balance.history">
<content>
<TableView fx:id="balanceHistoryTable" prefHeight="200.0" prefWidth="200.0">
<columns>
<TableColumn prefWidth="50.666626400416135" styleClass="align-right" text="%game.balance.history.index" />
<TableColumn maxWidth="500.0" prefWidth="149.77776718139648" styleClass="align-right" text="%game.balance.history.change" />
<TableColumn maxWidth="1.7976931348623157E308" prefWidth="400.0" text="%game.balance.history.type" />
</columns>
</TableView>
</content>
</Tab>
<Tab text="%game.balance.chart">
<content>
<VBox>
<children>
<HBox fillHeight="false" maxWidth="1.7976931348623157E308" styleClass="tab-info-bar">
<children>
<Label fx:id="totalIncomeLabel" styleClass="green" text="Label">
<font>
<Font size="14.0" />
</font>
<HBox.margin>
<Insets right="20.0" />
</HBox.margin>
</Label>
<Label fx:id="totalLossLabel" styleClass="red" text="Label">
<font>
<Font size="14.0" />
</font>
</Label>
</children>
<padding>
<Insets bottom="5.0" left="20.0" right="20.0" top="5.0" />
</padding>
</HBox>
<LineChart fx:id="budgetChart" verticalGridLinesVisible="false">
<xAxis>
<NumberAxis minorTickVisible="false" side="BOTTOM" tickLabelsVisible="false" tickMarkVisible="false" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" />
</yAxis>
</LineChart>
</children>
</VBox>
</content>
</Tab>
</tabs>
</TabPane>
<ToolBar prefHeight="40.0" prefWidth="200.0" GridPane.columnSpan="2147483647">
<items>
<Label fx:id="budgetLabel" text="%game.budget">
<font>
<Font size="19.0" />
</font>
<padding>
<Insets left="5.0" />
</padding>
</Label>
</items>
</ToolBar>
<Button mnemonicParsing="false" onMouseClicked="#scheduleStop" styleClass="red" text="%game.quit" textFill="WHITE" GridPane.columnSpan="2147483647" GridPane.halignment="RIGHT" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets right="10.0" />
</GridPane.margin>
</Button>
</children>
</GridPane>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<GridPane fx:id="questContainer" maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="250.0" prefHeight="80.0" styleClass="quest-container" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="-Infinity" minWidth="-Infinity" prefWidth="80.0" />
<ColumnConstraints hgrow="SOMETIMES" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="-Infinity" minWidth="50.0" prefWidth="50.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<ImageView fx:id="image" fitHeight="80.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets />
</GridPane.margin>
</ImageView>
<Button maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#abandon" prefHeight="0.0" prefWidth="0.0" styleClass="red" text="X" textFill="WHITE" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<font>
<Font name="System Bold" size="19.0" />
</font>
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
</Button>
<VBox prefWidth="100.0" GridPane.columnIndex="1">
<children>
<Label fx:id="title" maxWidth="1.7976931348623157E308" styleClass="quest-title" text="Label">
<font>
<Font name="System Bold" size="21.0" />
</font>
</Label>
<Label fx:id="description" styleClass="quest-desc" text="Label">
<font>
<Font size="13.0" />
</font>
</Label>
<Label fx:id="reward" styleClass="quest-reward" text="Label">
<font>
<Font size="13.0" />
</font>
</Label>
</children>
<padding>
<Insets left="5.0" />
</padding>
</VBox>
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</GridPane>

View File

@@ -1,5 +1,28 @@
test.hello-world = Hello World! 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 quests.normal = Normal
products.paper = Paper products.paper = Paper

View File

@@ -1,3 +1,3 @@
properties = color, format properties = color, format
color.variants =white, green, blue color.variants = white, green, blue
format.variants = a3, a4, a5 format.variants = a3, a4, a5

View File

@@ -2,3 +2,4 @@ properties = type, form
type.variants = pine, beech, oak type.variants = pine, beech, oak
form.variants = boards, pieces, beams form.variants = boards, pieces, beams
form.beams.depth = 3 form.beams.depth = 3
type.oak.weight = 1