The whole logic documentation

This commit is contained in:
2020-07-20 22:24:53 +02:00
parent 605941395b
commit 60f59ae503
34 changed files with 1182 additions and 25 deletions

View File

@@ -7,7 +7,14 @@ import de.siphalor.was.visual.JFXVisual;
import java.util.logging.Level;
/**
* The main entry point class
*/
public class Start {
/**
* The main program entry point
* @param args The args given from the console
*/
public static void main(String[] args) {
WhatAStorage was = WhatAStorage.getInstance();
was.reload();

View File

@@ -26,33 +26,88 @@ import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
/**
* The main game class. This is a singleton class.
*/
public class WhatAStorage {
/**
* The maximum number of quests at a time
*/
private static final int MAX_QUESTS = 3;
/**
* The size of the storage grid
*/
private static final int GRID_SIZE = 3;
/**
* The window title
*/
public static final String TITLE = "What a Storage";
/**
* The singleton instance
*/
private static final WhatAStorage INSTANCE = new WhatAStorage();
/**
* Gets the singleton instance.
* @return The instance
*/
public static WhatAStorage getInstance() {
return INSTANCE;
}
/**
* The main content manager
*/
private final ContentManager contentManager;
/**
* The internal content pack
*/
private final JarContentPack mainPack;
/**
* The main product manager
*/
private final ProductManager productManager;
/**
* The main quest manager
*/
private final QuestManager questManager;
/**
* The game's options
*/
private final Options options;
/**
* The used visual
*/
private Visual visual;
/**
* Whether the game has been requested to stop
*/
private boolean stopScheduled;
/**
* The current balance
*/
private Balance balance;
/**
* The current quest generator
*/
private QuestGenerator questGenerator;
/**
* The current quest list
*/
private final List<Quest> quests = new ArrayList<>(MAX_QUESTS);
/**
* The current storage
*/
private Storage storage;
/**
* Constructs a new instance.
*/
private WhatAStorage() {
contentManager = new ContentManager();
mainPack = new JarContentPack("", "content");
@@ -62,26 +117,49 @@ public class WhatAStorage {
options = new Options();
}
/**
* Sets the visual to use. <b>Must be called before {@link WhatAStorage#setup()}</b>
* @param visual The visual to use
*/
public void setVisual(Visual visual) {
this.visual = visual;
}
/**
* Gets the content manager.
* @return The content manager
*/
public ContentManager getContentManager() {
return contentManager;
}
/**
* Gets the product manager.
* @return The product manager
*/
public ProductManager getProductManager() {
return productManager;
}
/**
* Gets the quest manager.
* @return The quest manager
*/
public QuestManager getQuestManager() {
return questManager;
}
/**
* Gets the game's options
* @return The game's options
*/
public Options getOptions() {
return options;
}
/**
* Reloads all resources from the packs.
*/
public void reload() {
contentManager.clear();
@@ -105,6 +183,9 @@ public class WhatAStorage {
Util.LOGGER.log(Level.INFO, "Reloaded game content");
}
/**
* Setups the game.
*/
public void setup() {
if (visual == null) {
throw new IllegalStateException("No visual set for WhatAStorage!");
@@ -112,10 +193,16 @@ public class WhatAStorage {
visual.setup(this);
}
/**
* Runs the game. Hands over control to the visual.
*/
public void run() {
visual.run();
}
/**
* Start a new game.
*/
public void startGame() {
//questGenerator = questManager.get("example.test");
questGenerator = new RandomQuestGenerator();
@@ -132,19 +219,27 @@ public class WhatAStorage {
}
}
/**
* Request a game stop.
*/
public void scheduleStop() {
stopScheduled = true;
visual.onScheduleStop();
}
public boolean isStopScheduled() {
return stopScheduled;
}
/**
* Gets the balance.
* @return The balance
*/
public Balance getBalance() {
return balance;
}
/**
* Gets all available language codes.
* @return The language codes.
*/
@NotNull
public String[] getLangs() {
return contentManager.getResources("lang", "properties").map(resource -> {
int index = resource.getId().lastIndexOf('.');
@@ -152,15 +247,27 @@ public class WhatAStorage {
}).toArray(String[]::new);
}
/**
* Add a new transaction and propagate to the visual.
* @param type The transaction type
* @param change The money change
* @param product The reference product if any
*/
private void addTransaction(@NotNull Transaction.Type type, int change, @Nullable Product product) {
Transaction transaction = balance.add(type, change, product);
visual.onBalanceChanged(balance.getBudget(), transaction, balance.getTotalIncome(), balance.getTotalLoss());
}
/**
* Indicate that the language has been changed.
*/
public void invalidateI18n() {
visual.invalidateI18n();
}
/**
* Requests that the main visual data (storage and budget) should be resend to the visual.
*/
public void resendVisualGameData() {
for (int x = 0; x < storage.getWidth(); x++) {
for (int y = 0; y < storage.getHeight(); y++) {
@@ -175,6 +282,10 @@ public class WhatAStorage {
visual.onBalanceChanged(balance.getBudget(), new Transaction(Transaction.Type.NOOP, 0, ""), balance.getTotalIncome(), balance.getTotalLoss());
}
/**
* Gets and adds the next quest if possible.
* @return Returns whether a quest has been added.
*/
private boolean nextQuest() {
if (quests.size() >= MAX_QUESTS) return false;
@@ -189,10 +300,18 @@ public class WhatAStorage {
return true;
}
/**
* Indicate that the user requested the next quest.
* @return Returns whether a quest has been added
*/
public boolean userRequestQuest() {
return nextQuest();
}
/**
* Indicate that the user abandoned a quest.
* @param index The quest's index in the quest list
*/
public void userAbandonQuest(int index) {
if (index >= quests.size()) {
Util.LOGGER.log(Level.SEVERE, "Attempted to abandon non-existent quest!");
@@ -207,18 +326,46 @@ public class WhatAStorage {
if (options.getAutoRefillQuests()) nextQuest();
}
/**
* Checks whether a product can be stored at the given storage location.
* @param product The product to check
* @param x The storage x position
* @param y The storage y position
* @return Returns whether the product is legal at that position
*/
public boolean canStoreProduct(@Nullable Product product, int x, int y) {
return product != null && storage.get(x, y).fits(product) && product.testY(y);
}
/**
* Checks whether a quest's product can be stored at the given storage location.
* @param questIndex The quest's index in the quest list
* @param x The storage x position
* @param y The storage y position
* @return Returns whether the operation is legal
*/
public boolean canStoreProduct(int questIndex, int x, int y) {
return canStoreProduct(quests.get(questIndex).getProduct(), x, y);
}
/**
* Checks whether a product can be moved from one storage position to the other.
* @param x1 The origin x position
* @param y1 The origin y position
* @param x2 The destination x position
* @param y2 The destination y position
* @return Returns whether the operation is legal
*/
public boolean canMoveProduct(int x1, int y1, int x2, int y2) {
return canStoreProduct(storage.get(x1, y1).front(), x2, y2);
}
/**
* Indicate that the user wants to store a quest's product.
* @param questIndex The quest's index in the quest list
* @param x The storage x position
* @param y The storage y position
*/
public void userStoreProduct(int questIndex, int x, int y) {
Quest quest = quests.get(questIndex);
if (quest != null && quest.getType() == Quest.Type.IN) {
@@ -236,6 +383,13 @@ public class WhatAStorage {
}
}
/**
* Checks whether a product can fulfill a quest.
* @param questIndex The quest's index in the quest list
* @param x The storage x position of the product origin
* @param y The storage y position of the product origin
* @return Returns whether the operation is legal
*/
public boolean canDeliverProduct(int questIndex, int x, int y) {
Quest quest = quests.get(questIndex);
if (quest != null && quest.getType() == Quest.Type.OUT) {
@@ -244,6 +398,13 @@ public class WhatAStorage {
return false;
}
/**
* Indicate that that the user wants to deliver a product
* @param questIndex The quest's index in the quest list
* @param x The storage x position of the product origin
* @param y The storage y position of the product origin
* @return Returns whether the operation was successful
*/
public boolean userDeliverProduct(int questIndex, int x, int y) {
if (canDeliverProduct(questIndex, x, y)) {
Product product = storage.get(x, y).front();
@@ -259,6 +420,12 @@ public class WhatAStorage {
return false;
}
/**
* Checks whether two quests can resolve against each other.
* @param in The first quest's index in the quest list
* @param out The first quest's index in the quest list
* @return Returns whether the operation is legal
*/
public boolean canQuestsResolve(int in, int out) {
int s = quests.size();
if (in < s && in >= 0 && out < s && out >= 0) {
@@ -270,6 +437,12 @@ public class WhatAStorage {
return false;
}
/**
* Indicate that the user wants to resolve two quests.
* @param in The first quest's index in the quest list
* @param out The first quest's index in the quest list
* @return Returns whether the operation was successful
*/
public boolean userResolveQuests(int in, int out) {
if (options.getAllowQuestResolving()) {
int s = quests.size();
@@ -298,6 +471,13 @@ public class WhatAStorage {
return false;
}
/**
* Indicate that the user want to move a product.
* @param x1 The origin x position
* @param y1 The origin y position
* @param x2 The destination x position
* @param y2 The destination y position
*/
public void userMoveProduct(int x1, int y1, int x2, int y2) {
Product product = storage.get(x1, y1).front();
if (product != null && product.testY(y2)) {
@@ -312,6 +492,11 @@ public class WhatAStorage {
}
}
/**
* Indicate that the user wants to destroy a product.
* @param x The storage x position
* @param y The storage y position
*/
public void userDestroyProduct(int x, int y) {
Product product = storage.get(x, y).front();
int z = storage.get(x, y).pop();

View File

@@ -6,13 +6,26 @@ import java.io.InputStream;
import java.net.URL;
import java.util.Optional;
/**
* This class controls the game's assets. Assets are all the file in the assets package in the jar. It can mostly be safely assumed that the certain asset files exist.
*/
public class AssetsManager {
/**
* Returns a new InputStream to the requested asset.
* @param path The path beginning from the <code>assets</code> package
* @return An optional with the input stream if the asset exists
*/
@NotNull
public static Optional<InputStream> getStream(@NotNull String path) {
return Optional.ofNullable(Thread.currentThread().getContextClassLoader().getResourceAsStream("assets/" + path));
}
/**
* Returns an URL to the requested asset.
* @param path The path beginning from the <code>assets</code> package
* @return An optional with the url if the asset exists
*/
@NotNull
public static Optional<URL> getURL(@NotNull String path) {
return Optional.ofNullable(Thread.currentThread().getContextClassLoader().getResource(path));

View File

@@ -10,34 +10,66 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
/**
* The main manager that for holding and accessing resources in {@link ContentPack}s.
*/
public class ContentManager {
/**
* All contained content packs
*/
private final List<ContentPack> packs = new LinkedList<>();
/**
* Constructs a new, empty content manager.
*/
public ContentManager() {
}
/**
* Forgets all known content packs.
*/
public void clear() {
packs.clear();
}
/**
* Adds a new content pack to this manager.
* @param pack The pack to add
*/
public void addPack(ContentPack pack) {
packs.add(pack);
}
/**
* Gets all contained content packs.
* @return A collection of all packs
*/
public Collection<ContentPack> getPacks() {
return packs;
}
/**
* Gets all resources that exist in all content packs in the given location with the given extension. Finds nested resources.
* @param location The base location where to find resources in
* @param type The file extension to look for (without the dot)
* @return A stream of all matching resources
*/
@NotNull
public Stream<Resource> getResources(@NotNull String location, @NotNull String type) {
return packs.stream().flatMap(pack -> pack.getResources(location, type)).distinct();
}
/**
* Gets the first resource with the given identifier.
* @param identifier The resource identifier
* @return The found resource or empty if none of the packs contains such a resource
* @see ContentManager#getAllOfResource(String)
*/
@NotNull
public Optional<Resource> getResource(@NotNull String location) {
public Optional<Resource> getResource(@NotNull String identifier) {
Resource resource;
for (ContentPack pack : packs) {
resource = pack.getResource(location);
resource = pack.getResource(identifier);
if (resource != null) {
return Optional.of(resource);
}
@@ -45,8 +77,14 @@ public class ContentManager {
return Optional.empty();
}
/**
* Gets all resources with the given identifier in the content packs.
* @param identifier The resource identifier to look for
* @return A stream of all those resources
* @see ContentManager#getResource(String)
*/
@NotNull
public Stream<Resource> getAllOfResource(@NotNull String location) {
return packs.stream().flatMap(pack -> Stream.ofNullable(pack.getResource(location)));
public Stream<Resource> getAllOfResource(@NotNull String identifier) {
return packs.stream().flatMap(pack -> Stream.ofNullable(pack.getResource(identifier)));
}
}

View File

@@ -6,26 +6,55 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* A singleton class handling all of the translation actions and the currently active language with a fallback language
*/
public class I18n extends ResourceBundle {
/**
* The language code of the fallback language
*/
public static final String DEFAULT = "en_us";
/**
* The fallback language
*/
private final Lang DEFAULT_LANG = new Lang(DEFAULT);
/**
* The class' instance
*/
private static final I18n INSTANCE = new I18n();
/**
* Gets the singleton instance
* @return the singleton instance
*/
public static I18n getInstance() {
return INSTANCE;
}
/**
* the current language
*/
@Nullable
private Lang lang;
/**
* Returns the current language
* @return the currently selected language or the fallback
*/
public Lang getLang() {
if (lang != null)
return lang;
return DEFAULT_LANG;
}
/**
* Sets the current language to the language identified by <code>code</code>. It loads the language using the given {@link ContentManager}
* @param code The language's identification code
* @param contentManager The content manager to use
* @return Returns whether the language was updated
*/
public boolean setLang(@NotNull String code, @NotNull ContentManager contentManager) {
if (lang == null || !lang.getCode().equals(code)) {
Lang l = new Lang(code);
@@ -37,6 +66,10 @@ public class I18n extends ResourceBundle {
return false;
}
/**
* Reloads the current and the fallback language language using the given content manager.
* @param contentManager The content manager to use
*/
public void reload(@NotNull ContentManager contentManager) {
DEFAULT_LANG.load(contentManager);
if (lang != null) {
@@ -44,11 +77,22 @@ public class I18n extends ResourceBundle {
}
}
/**
* Translates and formats with the translation string.
* @param key The translation key
* @param args The arguments to use for formatting
* @return The formatted string
*/
@NotNull
public String format(@NotNull String key, @Nullable Object... args) {
return String.format(getString(key), args);
}
/**
* Loads the given data by the translation key.
* @param key The translation key
* @return The associated data or <code>null</code> if none
*/
@Override
@NotNull
protected Object handleGetObject(@NotNull String key) {
@@ -68,6 +112,10 @@ public class I18n extends ResourceBundle {
return val;
}
/**
* Returns all valid keys existing in the current language or the fallback language.
* @return An enumeration of all of these keys
*/
@NotNull
@Override
public Enumeration<String> getKeys() {

View File

@@ -11,19 +11,41 @@ import java.util.Enumeration;
import java.util.Properties;
import java.util.logging.Level;
/**
* A class for holding a language's data
*/
public class Lang {
/**
* The language's data
*/
private final String code;
/**
* The internally used properties file
*/
private final Properties properties = new Properties();
/**
* Constructs a new language by its identification code.
* @param code The identifying code
*/
public Lang(@NotNull String code) {
this.code = code;
}
/**
* Gets the language's code.
* @return The identifying code for this language
*/
@NotNull
public String getCode() {
return code;
}
/**
* Loads the language's data using the given content manager.
* @param contentManager The content manager to use
* @return Returns whether the load was successful
*/
public boolean load(@NotNull ContentManager contentManager) {
properties.clear();
contentManager.getAllOfResource("lang/" + code + ".properties").forEachOrdered(resource -> {
@@ -50,11 +72,20 @@ public class Lang {
return true;
}
/**
* Gets the translation string
* @param key The key for the translation
* @return Returns the translation data
*/
@Nullable
public String get(@NotNull String key) {
return (String) properties.get(key);
}
/**
* Gets all of the translation keys
* @return Returns all of the translation keys
*/
@NotNull
public Enumeration<String> getKeys() {
// This is really ugly but we know™ that the keys will always be strings

View File

@@ -6,11 +6,31 @@ import org.jetbrains.annotations.Nullable;
import java.util.stream.Stream;
/**
* An interface for some kind of content pack
*/
public interface ContentPack {
/**
* Returns a stream to all of the resources in the given location and the given file extension. Search is performed recursively.
* @param location The location to search
* @param type The file extension
* @return Returns a stream of all of the resources
*/
@NotNull
Stream<Resource> getResources(@NotNull String location, @NotNull String type);
/**
* Gets the resource with the specified identifier.
* @param identifier
* @return The resource or <code>null</code> if it doesn't exist
*/
@Nullable
Resource getResource(@NotNull String location);
Resource getResource(@NotNull String identifier);
/**
* Returns the id of the content pack
* @return The unique identifier
*/
@NotNull
String getId();
}

View File

@@ -11,15 +11,35 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
/**
* An implementation of a {@link ContentPack} as a directory.
*/
public class FileContentPack implements ContentPack {
/**
* The pack's id
*/
private final String id;
/**
* The base path where the pack is located
*/
private final Path base;
/**
* Constructs a new file content pack.
* @param id The pack id
* @param base The path to the pack directory
*/
public FileContentPack(@NotNull String id, @NotNull Path base) {
this.id = id;
this.base = base;
}
/**
* Gets all of the resources in the specified location with the specified type
* @param location The location to search
* @param type The file extension
* @return Returns a stream of these resources
*/
@Override
public @NotNull Stream<Resource> getResources(@NotNull String location, @NotNull String type) {
final String extension = "." + type;
@@ -36,15 +56,24 @@ public class FileContentPack implements ContentPack {
return Stream.empty();
}
/**
* Gets a single file resource with the given identifier.
* @param identifier The resource identifier
* @return Returns the file resource or <code>null</code> if it doesn't exist
*/
@Override
public Resource getResource(@NotNull String location) {
File file = base.resolve(Path.of(location)).toFile();
public Resource getResource(@NotNull String identifier) {
File file = base.resolve(Path.of(identifier)).toFile();
if (file.isFile()) {
return new FileResource(Util.pathToId(id, location), file);
return new FileResource(Util.pathToId(id, identifier), file);
}
return null;
}
/**
* Get the pack's id
* @return The pack's id
*/
@Override
@NotNull
public String getId() {

View File

@@ -20,25 +20,58 @@ import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
/**
* An implementation for a {@link ContentPack} in a loaded jar file.
*/
public class JarContentPack implements ContentPack {
/**
* The pack's id
*/
private final String id;
/**
* The base package path
*/
private final String baseLocation;
/**
* The class loader that's used for resource loading
*/
private final ClassLoader classLoader;
/**
* Constructs a new jar content pack based at the root package with the current classloader.
* @param id The pack's id
*/
public JarContentPack(String id) {
this(id, "");
}
/**
* Constructs a new jar content pack based at the given package with the current classloader.
* @param id The pack's id
* @param baseLocation The base package to load resources from
*/
public JarContentPack(String id, String baseLocation) {
this(id, baseLocation, Thread.currentThread().getContextClassLoader());
}
/**
* Constructs a new jar content pack based at the given package with the given classloader.
* @param id The pack's id
* @param baseLocation The base package to load resources from
* @param classLoader The classloader to use
*/
public JarContentPack(String id, String baseLocation, ClassLoader classLoader) {
this.id = id;
this.baseLocation = baseLocation;
this.classLoader = classLoader;
}
/**
* Finds all the resources in the given package with the given file extension.
* @param location The location to search
* @param type The file extension
* @return A stream of these resources
*/
// Inspired from this: https://stackoverflow.com/a/48190582/7582022
@Override
@NotNull
@@ -83,16 +116,25 @@ public class JarContentPack implements ContentPack {
return Stream.empty();
}
/**
* Gets the resource identified by the given id.
* @param identifier The resource's id
* @return Returns the resource or <code>null</code> if it doesn't exist
*/
@Override
@Nullable
public Resource getResource(@NotNull String location) {
URL url = classLoader.getResource(baseLocation + "/" + location);
public Resource getResource(@NotNull String identifier) {
URL url = classLoader.getResource(baseLocation + "/" + identifier);
if (url != null) {
return new CallbackResource(Util.pathToId(id, location), () -> classLoader.getResourceAsStream(baseLocation + "/" + location));
return new CallbackResource(Util.pathToId(id, identifier), () -> classLoader.getResourceAsStream(baseLocation + "/" + identifier));
}
return null;
}
/**
* Gets the id of this pack.
* @return The pack's id
*/
@NotNull
@Override
public String getId() {

View File

@@ -4,29 +4,74 @@ import org.jetbrains.annotations.NotNull;
import java.util.ResourceBundle;
/**
* An interface for a generic product
*/
public interface Product {
/**
* The property specifier. Should contain all property values separated by low dashes.
* @return The specifier
*/
@NotNull
String getPropertySpecifier();
/**
* Gets all the property values
* @return The values as array
*/
String[] getProperties();
/**
* Gets this product's depth
* @return The depth
*/
int getDepth();
/**
* Checks whether the product is allowed to go into the specified y layer.
* @param y The y layer to test
* @return Whether the product is allowed to go into that y layer
*/
boolean testY(int y);
/**
* Gets the products type.
* @return Its type
*/
@NotNull
ProductType<?> getType();
/**
* Checks the two products for equality.
* @param product A product to check
* @return Whether they match
*/
boolean equals(Product product);
/**
* Gets the translation key for this product. Usually the same as the products specific.
* @return The translation key
*/
@NotNull
default String getTranslationKey() {
return "products." + getType().getId();
}
/**
* Gets the name of this product. This will
* @param i18n The resources to load the translation from
* @return The localized name
*/
@NotNull
default String getName(@NotNull ResourceBundle i18n) {
return i18n.getString(getTranslationKey());
}
/**
* Gets the localized description for this product. This includes all the properties their values.
* @param i18n The resources to load the translations from
* @return The localized description
*/
@NotNull
default String getDescription(@NotNull ResourceBundle i18n) {
String[] props = getType().getProperties();
@@ -46,11 +91,19 @@ public interface Product {
return res.toString();
}
/**
* Gets the resource location for this product
* @return The resource location
*/
@NotNull
default String getTextureLocation() {
return "textures/products/" + getType().getId() + "/" + getPropertySpecifier() + ".png";
}
/**
* Return the placeholder texture for products. Why isn't this static?
* @return The resource location
*/
@NotNull
static String getPlaceholderTextureLocation() {
return "textures/products/package.png";

View File

@@ -10,14 +10,27 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* A class which manages the loading and access to products.
*/
public class ProductManager implements ResourceManager<ProductType<?>> {
/**
* Map with all {@link ProductType}s mapped by their ids.
*/
private final Map<String, ProductType<?>> productTypes = new HashMap<>();
/**
* Removes all loaded products
*/
@Override
public void clear() {
productTypes.clear();
}
/**
* Loads all products from the given content manager.
* @param contentManager The content manager to load from
*/
public void reload(@NotNull ContentManager contentManager) {
contentManager.getResources("products", "properties").forEach(resource -> {
InputStream inputStream = resource.getInputStream();
@@ -32,17 +45,31 @@ public class ProductManager implements ResourceManager<ProductType<?>> {
});
}
/**
* Gets a single product type by its id.
* @param id The id
* @return The associated product type or <code>null</code> if no product type with this id exists
*/
@Override
@Nullable
public ProductType<?> get(String id) {
return productTypes.get(id);
}
/**
* Gets all loaded product types.
* @return All loaded product types
*/
@NotNull
public Collection<ProductType<?>> getTypes() {
return productTypes.values();
}
/**
* Returns a random product out of all product types. Randomness is distributed equally over all types not over the product variants.
* @param random The random instance to use
* @return A random product or <code>null</code> if no products are loaded
*/
@Nullable
public Product randomProduct(Random random) {
if (!productTypes.isEmpty()) {

View File

@@ -5,11 +5,35 @@ import org.jetbrains.annotations.Nullable;
import java.util.Random;
/**
* Interface for product types.
* @param <T> The base class vor all products of this type
*/
public interface ProductType<T extends Product> {
/**
* Gets a product of this type with the given values as properties.
* @param values Some properties
* @return The product or <code>null</code> if no such product exists
*/
@Nullable T getProduct(@NotNull String[] values);
/**
* Gets the id of this type.
* @return The id
*/
@NotNull String getId();
/**
* Gets all property ids.
* @return An array of the property ids
*/
@NotNull String[] getProperties();
@NotNull Product randomProduct(Random random);
/**
* Gets a random product of this type.
* @param random The random instance to use
* @return A random product
*/
@NotNull T randomProduct(Random random);
}

View File

@@ -8,62 +8,122 @@ import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.function.IntPredicate;
/**
* An implementation of {@link Product} that dynamically loads its data from a properties file
*/
public class DynamicProduct implements Product {
/**
* The product's type
*/
private ProductType<?> type;
/**
* The property values
*/
private final String[] properties;
/**
* The depth of this product
*/
private int depth = 1;
/**
* A function that checks if the product can go into the given y location
*/
@NotNull
private IntPredicate yPredicate = Util.dummyIntPredicate();
/**
* Constructs a new product with the given values as properties.
* @param properties The property values
*/
public DynamicProduct(String[] properties) {
this.properties = properties;
}
/**
* Gets an identifier that uniquely represents the property values
* @return A concatenated list of the property values
*/
@Override
@NotNull
public String getPropertySpecifier() {
return String.join("_", properties);
}
/**
* Gets the property values
* @return The property values
*/
@Override
public String[] getProperties() {
return properties;
}
/**
* Sets the depth of this product
* @param depth The depth
*/
public void setDepth(int depth) {
this.depth = depth;
}
/**
* Gets the product's depth.
* @return The depth
*/
@Override
public int getDepth() {
return depth;
}
/**
* Test if the product can go into the given y layer.
* @param y The given y layer
* @return Whether the product is allowed to go into it
*/
@Override
public boolean testY(int y) {
return yPredicate.test(y);
}
// This function exists to resolve the circular references of ProductType <-> Product on creation
/**
* Sets the type of this product. This is needed because the {@link DynamicProductType} needs to know all of its products and all products need to know their type
* @param type The products type
*/
void setType(ProductType<?> type) {
this.type = type;
}
/**
* Gets the product's type.
* @return Returns the type.
*/
@Override
@NotNull
public ProductType<?> getType() {
return type;
}
/**
* Checks if both products match.
* @param product The other product
* @return Returns whether both products match
*/
@Override
public boolean equals(Product product) {
return super.equals(product);
}
/**
* Sets the function for checking the y layer.
* @param yPredicate The function
*/
public void setYPredicate(@NotNull IntPredicate yPredicate) {
this.yPredicate = yPredicate;
}
/**
* A toString function. Nothing to explain
* @return The object and its data converted to a string
*/
@Override
public String toString() {
return "DynamicProduct{" +

View File

@@ -4,6 +4,7 @@ import de.siphalor.was.content.product.Product;
import de.siphalor.was.content.product.ProductType;
import de.siphalor.was.util.Util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
@@ -11,11 +12,30 @@ import java.util.*;
import java.util.function.IntPredicate;
import java.util.stream.Collectors;
/**
* An implementation for {@link ProductType} that dynamically reads its data and products from a properties file.
*/
public class DynamicProductType implements ProductType<DynamicProduct> {
/**
* The type's id
*/
private final String id;
/**
* All of the products mapped by their property specifiers.
* @see Product#getPropertySpecifier()
*/
private final Map<String, DynamicProduct> variations;
/**
* The property ids
*/
private final String[] properties;
/**
* Constructs a new dynamic product type.
* @param id The id
* @param variations All of the products that are derived of this type
* @param properties The property ids
*/
public DynamicProductType(String id, List<DynamicProduct> variations, String[] properties) {
this.id = id;
this.variations = new HashMap<>();
@@ -26,6 +46,12 @@ public class DynamicProductType implements ProductType<DynamicProduct> {
this.properties = properties;
}
/**
* Deserializes a product type from an input stream with a properties file
* @param id The type's id
* @param inputStream The input stream containing a properties file
* @return Returns the constructed product type
*/
@NotNull
public static DynamicProductType from(@NotNull String id, @NotNull InputStream inputStream) {
Properties propertiesFile = new Properties();
@@ -83,6 +109,13 @@ public class DynamicProductType implements ProductType<DynamicProduct> {
return new DynamicProductType(id, List.of(new DynamicProduct(Util.emptyArray())), Util.emptyArray());
}
/**
* Creates a new product prototype for the given property value.
* @param propertiesFile The properties file to load from
* @param property The property id to target
* @param variant The property value to target.
* @return Returns a new prototype
*/
private static ProductPrototype makePrototype(Properties propertiesFile, String property, String variant) {
ProductPrototype prototype = new ProductPrototype(variant);
String base = property + "." + variant + ".";
@@ -111,41 +144,85 @@ public class DynamicProductType implements ProductType<DynamicProduct> {
return prototype;
}
/**
* Gets the product with the given values.
* @param values The properties' values
* @return Returns the associated product or <code>null</code> if it's nonexistent
*/
@Override
@Nullable
public DynamicProduct getProduct(@NotNull String[] values) {
return variations.get(String.join("_", values));
}
/**
* Gets the type's id
* @return The id
*/
@Override
public @NotNull String getId() {
return id;
}
/**
* Gets the property ids
* @return The property ids
*/
@Override
public @NotNull String[] getProperties() {
return properties;
}
/**
* Retrieves a random product from the variations.
* @param random An instance of {@link Random} to use for the calculation
* @return A random product
*/
@Override
public @NotNull Product randomProduct(Random random) {
public @NotNull DynamicProduct randomProduct(Random random) {
List<DynamicProduct> products = new ArrayList<>(variations.values());
return products.get(random.nextInt(products.size()));
}
/**
* Gets all of the products mapped by their property specifier.
* @return A map of those
* @see DynamicProduct#getPropertySpecifier()
*/
public Map<String, DynamicProduct> getVariations() {
return variations;
}
/**
* A prototype used for building the product variations
*/
private static class ProductPrototype {
/**
* The property specifier with all properties that already have been used
*/
String value;
/**
* The depth of the prototype. <code>null</code> if it should be inherited
*/
Integer depth = null;
/**
* The y tester of the prototype. <code>null</code> if it should be inherited
*/
IntPredicate yPredicate = null;
/**
* Constructs a new prototype with the given property specifier
* @param value The property specifier
*/
public ProductPrototype(String value) {
this.value = value;
}
/**
* Applies the prototypes' data to the given product
* @param product The product which shall get adapted
*/
public void apply(DynamicProduct product) {
if (depth != null)
product.setDepth(depth);
@@ -153,6 +230,11 @@ public class DynamicProductType implements ProductType<DynamicProduct> {
product.setYPredicate(yPredicate);
}
/**
* Merges two prototypes into each other. The result has the properties of both. The other prototype is recessive
* @param prototype The other prototype
* @return A newly constructed prototype with the calculated properties.
*/
public ProductPrototype combine(ProductPrototype prototype) {
ProductPrototype result = new ProductPrototype(value + ";" + prototype.value);
if (prototype.depth != null)
@@ -167,6 +249,10 @@ public class DynamicProductType implements ProductType<DynamicProduct> {
}
}
/**
* Converts to string. That's all.
* @return The stringified product type.
*/
@Override
public String toString() {
return "DynamicProductType{" +

View File

@@ -3,33 +3,68 @@ package de.siphalor.was.content.quest;
import de.siphalor.was.content.product.Product;
import org.jetbrains.annotations.NotNull;
/**
* Represents a quest.
*/
public class Quest {
/**
* The type of this quest.
*/
@NotNull
private final Type type;
/**
* The quest reward/punishment
*/
private final int reward;
/**
* The product to store/deliver
*/
@NotNull
private final Product product;
/**
* Constructs a new quest.
* @param type The type
* @param reward The reward/punishment
* @param product The product to store/deliver
*/
public Quest(@NotNull Type type, int reward, @NotNull Product product) {
this.product = product;
this.reward = reward;
this.type = type;
}
/**
* Gets the product to store/deliver.
* @return The product
*/
@NotNull
public Product getProduct() {
return product;
}
/**
* Gets the type of this quest.
* @return The type
*/
@NotNull
public Type getType() {
return type;
}
/**
* Gets the reward/punishment for this quest.
* @return The reward/punishment
*/
public int getReward() {
return reward;
}
/**
* Represents the type of a quest. <br />
* <code>IN</code> for incoming (store) <br />
* <code>OUT</code> for outgoing (deliver)
*/
public enum Type {
IN, OUT
}

View File

@@ -2,6 +2,9 @@ package de.siphalor.was.content.quest;
import java.util.Enumeration;
/**
* A class that provides the game with quests and optionally finishes.
*/
public interface QuestGenerator extends Enumeration<Quest> {
/**
* Restarts this generator

View File

@@ -10,14 +10,28 @@ import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* This class handles the loading and access of quest generators.
*/
public class QuestManager implements ResourceManager<QuestGenerator> {
/**
* All of the quest generators mapped by their ids
*/
private final Map<String, QuestGenerator> questGenerators = new HashMap<>();
/**
* Clears all loaded quest generators.
*/
@Override
public void clear() {
questGenerators.clear();
}
/**
* Loads all quest generators from the given content manager
* @param contentManager The content manager to load from
* @param productManager The product manager to check product definitions against
*/
public void reload(@NotNull ContentManager contentManager, @NotNull ProductManager productManager) {
contentManager.getResources("quests", "csv").forEach(resource -> {
InputStream inputStream = resource.getInputStream();
@@ -32,6 +46,11 @@ public class QuestManager implements ResourceManager<QuestGenerator> {
});
}
/**
* Gets a quest generator by its id.
* @param id The id
* @return The associated quest generator
*/
@Override
public QuestGenerator get(String id) {
return questGenerators.get(id);

View File

@@ -5,18 +5,36 @@ import de.siphalor.was.content.product.Product;
import java.util.Random;
/**
* An implementation of {@link QuestGenerator} that randomly spits out all kinds of quests with all kinds of products loaded.
*/
public class RandomQuestGenerator implements QuestGenerator {
/**
* The internal random instance
*/
private static final Random RANDOM = new Random();
/**
* Does nothing.
*/
@Override
public void restart() {
}
/**
* This runs infinitely, so always <code>true</code>.
* @return <code>true</code>
*/
@Override
public boolean hasMoreElements() {
return true;
}
/**
* Gets the next quest.
* @return Another quest
*/
@Override
public Quest nextElement() {
Product product = WhatAStorage.getInstance().getProductManager().randomProduct(RANDOM);

View File

@@ -17,15 +17,34 @@ import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
/**
* A quest generator that follows a static list of quests.
*/
public class StaticQuestGenerator implements QuestGenerator {
/**
* The list of quests to follow
*/
@NotNull
private final List<Quest> quests;
/**
* The current index in the quest list
*/
private int index = 0;
/**
* Constructs a new quest generator with the given quest list.
* @param quests The quest list to use
*/
public StaticQuestGenerator(@NotNull List<Quest> quests) {
this.quests = quests;
}
/**
* Loads a new static quest generator from the given input stream. The input stream should contain CSV format.
* @param inputStream An input stream with csv format
* @param productManager The product manager to check product definitions against
* @return The newly loaded quest generator or <code>null</code> if the laoding failed
*/
@Nullable
public static StaticQuestGenerator fromCsv(@NotNull InputStream inputStream, @NotNull ProductManager productManager) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
@@ -69,16 +88,27 @@ public class StaticQuestGenerator implements QuestGenerator {
return null;
}
/**
* Restarts this generator.
*/
@Override
public void restart() {
index = 0;
}
/**
* Gets the next quest and advances.
* @return The next quest
*/
@Override
public Quest nextElement() {
return quests.get(index++);
}
/**
* Gets whether there are still new quests in the list.
* @return Whether there are quests left
*/
@Override
public boolean hasMoreElements() {
return index < quests.size();

View File

@@ -5,14 +5,29 @@ import org.jetbrains.annotations.Nullable;
import java.io.InputStream;
import java.util.function.Supplier;
/**
* An implementation for {@link Resource} that runs a callback to get the input stream.
*/
public class CallbackResource extends Resource {
/**
* The callback to run for the input stream
*/
Supplier<InputStream> supplier;
/**
* Constructs a new callback resource.
* @param id The id
* @param supplier The supplier that will provide the input stream
*/
public CallbackResource(String id, Supplier<InputStream> supplier) {
super(id);
this.supplier = supplier;
}
/**
* Runs the callback to get the input stream.
* @return An input stream or <code>null</code> if the callback failed
*/
@Override
public @Nullable InputStream getInputStream() {
return supplier.get();

View File

@@ -5,14 +5,29 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
/**
* A file based implementation for {@link Resource}.
*/
public class FileResource extends Resource {
/**
* The file that this resource points to
*/
private final File file;
/**
* Constructs a new file resource
* @param id The id to use
* @param file The file to point to
*/
public FileResource(String id, File file) {
super(id);
this.file = file;
}
/**
* Opens a new {@link FileInputStream} to the contained file.
* @return The input stream or <code>null</code> if opening the stream failed
*/
@Override
public InputStream getInputStream() {
try {

View File

@@ -4,16 +4,34 @@ import org.jetbrains.annotations.Nullable;
import java.io.InputStream;
/**
* A representation for some kind of resource.
*/
public abstract class Resource {
/**
* The resource's id
*/
private final String id;
/**
* Constructs a new resource with the given id.
* @param id The id
*/
protected Resource(String id) {
this.id = id;
}
/**
* Gets an input stream for this resource.
* @return An input stream to the resource
*/
@Nullable
public abstract InputStream getInputStream();
/**
* Gets the id of this resource. Usually namespaced
* @return The id
*/
public String getId() {
return id;
}

View File

@@ -7,16 +7,43 @@ import org.jetbrains.annotations.Nullable;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Represents the current budget and a transaction history.
*/
public class Balance {
/**
* The current budget
*/
private int budget = 0;
/**
* The total money gained
*/
private int totalIncome = 0;
/**
* The total money lost
*/
private int totalLoss = 0;
/**
* The transaction history
*/
private final Queue<Transaction> history = new ConcurrentLinkedQueue<>();
/**
* Adds a new transaction to the history.
* @param type The transaction type
* @param change The amount of money received/lost
* @param product The product that this was about or <code>null</code> if none
* @return The added transaction
*/
public Transaction add(@NotNull Transaction.Type type, int change, @Nullable Product product) {
return add(new Transaction(type, change, product != null ? product.getTranslationKey() : ""));
}
/**
* Adds a new transaction to the history.
* @param transaction The transaction
* @return the added transaction
*/
public Transaction add(@NotNull Transaction transaction) {
history.add(transaction);
@@ -30,19 +57,35 @@ public class Balance {
return transaction;
}
/**
* Gets the complete transaction history.
* @return The history
*/
@NotNull
public Queue<Transaction> getHistory() {
return history;
}
/**
* Gets the current budget.
* @return The current budget
*/
public int getBudget() {
return budget;
}
/**
* Gets the total amount of money gained.
* @return the total income
*/
public int getTotalIncome() {
return totalIncome;
}
/**
* Gets the total amount of money lost
* @return the total loss
*/
public int getTotalLoss() {
return totalLoss;
}

View File

@@ -6,30 +6,62 @@ import de.siphalor.was.content.lang.I18n;
import java.util.Locale;
import java.util.prefs.Preferences;
/**
* Holds the game's options and is responsible for saving and loading them.
*/
public class Options {
/**
* Whether quest should get automatically added when fulfilled
*/
private boolean autoRefillQuests;
/**
* Allow resolving of two quests that target the same product. One asks to deposit it, the other to deliver it
*/
private boolean allowQuestResolving;
/**
* Gets whether quest resolving is enabled.
* @return Whether quest resolving is enabled
*/
public boolean getAllowQuestResolving() {
return allowQuestResolving;
}
/**
* Sets whether quest resolving should be enabled.
* @param allowQuestResolving Whether it should be enabled
*/
public void setAllowQuestResolving(boolean allowQuestResolving) {
this.allowQuestResolving = allowQuestResolving;
}
/**
* Gets whether fulfilled quests should automatically be refilled.
* @return Whether fulfilled quests should automatically be refilled
*/
public boolean getAutoRefillQuests() {
return autoRefillQuests;
}
/**
* Gets whether fulfilled quests should automatically be refilled.
* @param autoRefillQuests Whether fulfilled quests should automatically be refilled
*/
public void setAutoRefillQuests(boolean autoRefillQuests) {
this.autoRefillQuests = autoRefillQuests;
}
/**
* Gets a reference to the preferences object used for session independent storage.
* @return The preferences object
*/
private Preferences getPreferences() {
return Preferences.userNodeForPackage(WhatAStorage.class);
}
/**
* Loads the options from the storage.
*/
public void load() {
Preferences preferences = getPreferences();
autoRefillQuests = preferences.getBoolean("auto-refill-quests", false);
@@ -39,6 +71,9 @@ public class Options {
I18n.getInstance().setLang(lang, WhatAStorage.getInstance().getContentManager());
}
/**
* Saves the options data to the storage
*/
public void save() {
Preferences preferences = getPreferences();
preferences.putBoolean("auto-refill-quests", autoRefillQuests);

View File

@@ -2,9 +2,22 @@ package de.siphalor.was.game;
import org.jetbrains.annotations.NotNull;
/**
* The class holding the stored products.
*/
public class Storage {
/**
* The storage slots
*/
StorageSlot[][] slots;
/**
* Constructs a new storage with the given lengths.
* @param width The width
* @param height The height
* @param depth The depth of the {@link StorageSlot}s
* @throws IllegalArgumentException if any of the lengths is smaller or equal to zero
*/
public Storage(int width, int height, int depth) {
if (width <= 0 || height <= 0 || depth <= 0) {
throw new IllegalArgumentException("Storage lengths must be bigger than zero");
@@ -18,19 +31,36 @@ public class Storage {
}
}
/**
* Gets the storage slot at the specified location. <b>Coordinate must be valid!</b>
* @param x The x position (<b>Must be in bounds</b>)
* @param y The y position (<b>Must be in bounds</b>
* @return The storage slot at the given location
*/
@NotNull
public StorageSlot get(int x, int y) {
return slots[x][y];
}
/**
* Gets the width of the storage
* @return The width
*/
public int getWidth() {
return slots.length;
}
/**
* Gets the height of the storage
* @return The height
*/
public int getHeight() {
return slots[0].length;
}
/**
* Clears this storage from all products
*/
public void clear() {
for (StorageSlot[] row : slots) {
for (StorageSlot slot : row) {

View File

@@ -10,22 +10,43 @@ import java.util.Collection;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Represent a single storage slot.
*/
public class StorageSlot {
/**
* The depth of the slot
*/
final int depth;
/**
* The products in this slot
*/
final Product[] products;
/**
* Constructs a new storage slot.
* @param depth The depth of this slot
*/
public StorageSlot(int depth) {
this.depth = depth;
this.products = new Product[depth];
}
/**
* Gets an array of the products.
* @return The product array. Some entries might be <code>null</code> to indicate empty storage
*/
@NotNull
public Product[] getProducts() {
return products;
}
/**
* Gets all stored products indexed by their visual position (depth-dependent).
* @return A collection of indexed products
*/
@NotNull
public Collection<Pair<Integer, Product>> getProductsIndexed() {
public Collection<Pair<@NotNull Integer, @NotNull Product>> getProductsIndexed() {
Queue<Pair<Integer, Product>> queue = new ConcurrentLinkedQueue<>();
int index = 0;
for (int i = 0; i < depth; i++) {
@@ -39,10 +60,17 @@ public class StorageSlot {
return queue;
}
/**
* Removes all products from this slot.
*/
public void clear() {
Arrays.fill(products, null);
}
/**
* Gets the visually first product.
* @return The product
*/
@Nullable
public Product front() {
for (int i = products.length - 1; i >= 0; i--) {
@@ -54,6 +82,10 @@ public class StorageSlot {
return null;
}
/**
* Removes the visually first product.
* @return The visual position where this product was removed from or <code>-1</code> if there was no product to remove
*/
public int pop() {
if (products[0] == null)
return -1;
@@ -69,6 +101,11 @@ public class StorageSlot {
return d;
}
/**
* Returns whether the given product fits into this slot depth-wise.
* @param product The product to check
* @return Returns whether it fits
*/
public boolean fits(@NotNull Product product) {
int blocked = 0;
for (Product p : products) {
@@ -79,6 +116,11 @@ public class StorageSlot {
return product.getDepth() <= depth - blocked;
}
/**
* Adds a new product to the visual front of this slot.
* @param product The product to add
* @return The visual position where the product went or <code>-1</code> if insertion failed
*/
public int add(@NotNull Product product) {
int blocked = 0;
int i;

View File

@@ -4,34 +4,81 @@ import org.jetbrains.annotations.NotNull;
import java.util.Locale;
/**
* Represents a transaction.
*/
public class Transaction {
/**
* The {@link Type} of this transaction
*/
private final Type type;
/**
* The money change of this transaction
*/
private final int change;
/**
* An optional translation key for a product. Might be empty
*/
private final String productKey;
/**
* Constructs a new transaction.
* @param type The type
* @param change The money change of this transaction
* @param productKey A translation key for a product
*/
public Transaction(@NotNull Type type, int change, @NotNull String productKey) {
this.type = type;
this.change = change;
this.productKey = productKey;
}
/**
* Gets the type.
* @return The type
*/
@NotNull
public Type getType() {
return type;
}
/**
* Gets the money change
* @return The change
*/
public int getChange() {
return change;
}
/**
* Gets the product translation key.
* @return The translation key or <code>""</code> if none is present
*/
@NotNull
public String getProductKey() {
return productKey;
}
/**
* Represents the type of a transaction:
* <ul>
* <li><code>ABANDON</code>: Abandoned a quest</li>
* <li><code>DESTROY</code>: destroyed a product</li>
* <li><code>MOVE</code>: Moved a product</li>
* <li><code>NOOP</code>: For internal use. To be used with a change of <code>0</code></li>
* <li><code>DELIVER</code>: Delivered a product</li>
* <li><code>STORE</code>: Stored a product</li>
* <li><code>RESOLVE</code>: Resolved two quests against each other</li>
* </ul>
*/
public enum Type {
ABANDON, DESTROY, MOVE, NOOP, DELIVER, STORE, RESOLVE;
/**
* Gets the translation key for this type
* @return The translation key
*/
@NotNull
public String getTranslationKey() {
return "game.balance.history.type." + name().toLowerCase(Locale.ENGLISH);
}

View File

@@ -1,28 +1,69 @@
package de.siphalor.was.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* Represents the pair of two values.
* @param <A> The type of the first value
* @param <B> The type of the second value
*/
public class Pair<A, B> {
/**
* The first value
*/
private final A first;
/**
* The second value
*/
private final B second;
/**
* Constructs a new pair.
* @param first The first value
* @param second The second value
* @param <A> The type of the first value
* @param <B> The type of the second value
* @return The new pair
*/
@NotNull
public static <A, B> Pair<A, B> of(A first, B second) {
return new Pair<>(first, second);
}
/**
* Constructs a new pair.
* @param first The first value
* @param second The second value
*/
public Pair(A first, B second) {
this.first = first;
this.second = second;
}
/**
* Gets the first value.
* @return The first value
*/
public A getFirst() {
return first;
}
/**
* Gets the second value.
* @return The second value
*/
public B getSecond() {
return second;
}
/**
* Checks for equality.
* @param o The other object
* @return Returns whether both are equal
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -32,6 +73,10 @@ public class Pair<A, B> {
Objects.equals(second, pair.second);
}
/**
* The combined hash code of both values
* @return The hash code
*/
@Override
public int hashCode() {
return Objects.hash(first, second);

View File

@@ -7,19 +7,31 @@ import java.io.IOException;
import java.io.InputStream;
/**
* An input stream that can be reused. <br />
* Close with {@link PersistentInputStream#realClose()} <br />
* <b>Taken from here: https://stackoverflow.com/questions/924990/how-to-cache-inputstream-for-multiple-use#1303314</b>
*/
public class PersistentInputStream extends BufferedInputStream {
/**
* Constructs a new persistent input stream.
* @param in The base input stream
*/
public PersistentInputStream(@NotNull InputStream in) {
super(in);
super.mark(Integer.MAX_VALUE);
}
/**
* Resets this input stream.
*/
@Override
public void close() throws IOException {
super.reset();
}
/**
* Closes this input stream.
*/
public void realClose() throws IOException {
super.close();
}

View File

@@ -1,7 +1,19 @@
package de.siphalor.was.util;
/**
* An interface for classes that manage some kind of resources.
* @param <T> The type of the resources
*/
public interface ResourceManager<T> {
/**
* Clear this manager's resources.
*/
void clear();
/**
* Gets a resource by the given id.
* @param id The resource's id
* @return The associated resource
*/
T get(String id);
}

View File

@@ -1,6 +1,5 @@
package de.siphalor.was.util;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
@@ -9,28 +8,63 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
/**
* A utility collection.
*/
public class Util {
/**
* The main logger
*/
public static final Logger LOGGER;
/**
* A predicate that always returns <code>true</code>
*/
private static final Predicate<?> DUMMY_PREDICATE = o -> true;
/**
* An int predicate that always returns <code>true</code>
*/
private static final IntPredicate DUMMY_INT_PREDICATE = value -> true;
/**
* An empty array
*/
private static final Object[] EMPTY_ARRAY = new Object[0];
/**
* Gets an always true predicate.
* @param <T> The type of the predicate
* @return A predicate that always returns <code>true</code>
*/
@SuppressWarnings("unchecked")
public static <T> Predicate<T> dummyPredicate() {
return (Predicate<T>) DUMMY_PREDICATE;
}
/**
* Gets an always true int predicate.
* @return An int predicate that always returns <code>true</code>
*/
public static IntPredicate dummyIntPredicate() {
return DUMMY_INT_PREDICATE;
}
/**
* Gets an empty array of the given type. No memory allocations for this stuff. Also convenient
* @param <T> The array type
* @return The empty array
*/
@SuppressWarnings("unchecked")
public static <T> T[] emptyArray() {
return (T[]) EMPTY_ARRAY;
}
/**
* Converts a relative path to a resource id.
* @param packId The pack where the resource lives
* @param path The relative path to the resource
* @return The resource id
*/
public static String pathToId(String packId, String path) {
int dot = path.lastIndexOf('.');
if (dot >= 0) {

View File

@@ -5,17 +5,59 @@ import de.siphalor.was.content.product.Product;
import de.siphalor.was.content.quest.Quest;
import de.siphalor.was.game.Transaction;
/**
* An interface for handling the display.
*/
public interface Visual {
/**
* Called in the setup stage of the application.
* @param whatAStorage The main instance of the game
*/
void setup(WhatAStorage whatAStorage);
/**
* Starts the display and hands over control to this method.
*/
void run();
/**
* Called when a game starts.
* @param width The storage width
* @param height The storage height
* @param depth The storage depth
*/
void onGameStart(int width, int height, int depth);
/**
* Called when the a program stop got requested.
*/
void onScheduleStop();
/**
* Called when the localization changed. Should reload all of the localized UIs.
*/
void invalidateI18n();
/**
* Called when a transactions gets added.
* @param budget The new budget
* @param transaction The new transaction
* @param totalIncome The total income
* @param totalLoss The total loss
*/
void onBalanceChanged(int budget, Transaction transaction, int totalIncome, int totalLoss);
/**
* Called when a quest gets added.
* @param newQuest The new quest
* @param canCreateMore Whether the user can request more quests
*/
void onQuestAdded(Quest newQuest, boolean canCreateMore);
/**
* Called when a quest gets fulfilled or abandoned.
* @param index The index of the quest to remove
*/
void onQuestRemoved(int index);
void onProductSet(int x, int y, int z, Product product);

View File

@@ -23,9 +23,9 @@ public class DummyContentPack implements ContentPack {
}
@Override
public @Nullable Resource getResource(@NotNull String location) {
if (content.containsKey(location))
return new DummyResource(location, content.get(location));
public @Nullable Resource getResource(@NotNull String identifier) {
if (content.containsKey(identifier))
return new DummyResource(identifier, content.get(identifier));
return null;
}

View File

@@ -3,7 +3,6 @@ package de.siphalor.was.dummy;
import de.siphalor.was.content.product.Product;
import de.siphalor.was.content.product.ProductType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Random;
@@ -87,7 +86,7 @@ public class DummyProduct implements Product {
}
@Override
public @NotNull Product randomProduct(Random random) {
public @NotNull DummyProduct randomProduct(Random random) {
return null;
}
}