package com.kartoflane.superluminal2.core; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import net.vhati.ftldat.FTLDat.FTLPack; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.eclipse.swt.graphics.Image; import com.kartoflane.superluminal2.components.enums.DroneTypes; import com.kartoflane.superluminal2.components.enums.PlayerShipBlueprints; import com.kartoflane.superluminal2.components.enums.WeaponTypes; import com.kartoflane.superluminal2.components.interfaces.Predicate; import com.kartoflane.superluminal2.ftl.AnimationObject; import com.kartoflane.superluminal2.ftl.AugmentObject; import com.kartoflane.superluminal2.ftl.DroneList; import com.kartoflane.superluminal2.ftl.DroneObject; import com.kartoflane.superluminal2.ftl.GibObject; import com.kartoflane.superluminal2.ftl.GlowObject; import com.kartoflane.superluminal2.ftl.GlowSet; import com.kartoflane.superluminal2.ftl.RoomObject; import com.kartoflane.superluminal2.ftl.ShipMetadata; import com.kartoflane.superluminal2.ftl.WeaponList; import com.kartoflane.superluminal2.ftl.WeaponObject; import com.kartoflane.superluminal2.utils.UIUtils; public class Database { private static final Logger log = LogManager.getLogger(Database.class); public static final AugmentObject DEFAULT_AUGMENT_OBJ = new AugmentObject(); public static final AnimationObject DEFAULT_ANIM_OBJ = new AnimationObject(); public static final WeaponObject DEFAULT_WEAPON_OBJ = new WeaponObject(); public static final DroneObject DEFAULT_DRONE_OBJ = new DroneObject(); public static final GlowSet DEFAULT_GLOW_SET = new GlowSet(); public static final GlowObject DEFAULT_GLOW_OBJ = new GlowObject(); public static final GibObject DEFAULT_GIB_OBJ = new GibObject(); public static final WeaponList DEFAULT_WEAPON_LIST = new WeaponList(); public static final DroneList DEFAULT_DRONE_LIST = new DroneList(); public static final RoomObject AIRLOCK_OBJECT = new RoomObject(); /** * Arbitrary value that needs to be added to enemy ships' ellipse's Y offset * in order for them to load correctly. */ public static final int ENEMY_SHIELD_Y_OFFSET = 110; /** * The amount of time it takes for the ship explosion animation to complete before the * score screen appears, in seconds.<br> * The death time in hangar is 6 seconds. */ public static final int GIB_DEATH_ANIM_TIME = 4; /** * Amount of pixels a gib with linear velocity of 1 moves in 1 second. */ public static final int GIB_LINEAR_SPEED = 30; /** * The angle that a gib with angular velocity of 1 gets rotated by in 1 second, in radians.<br> * A gib with angular velocity of 10 does a full rotation over the course of death animation. */ public static final double GIB_ANGULAR_SPEED = Math.PI / 20; private static Database instance; // Constant private HashMap<String, String> shipFileMap = new HashMap<String, String>(); // Dynamically loaded private ArrayList<DatabaseEntry> dataEntries = new ArrayList<DatabaseEntry>(); /** * Creates an empty Database. */ public Database() { instance = this; String blueprints = "blueprints.xml"; String dlcBlueprints = "dlcBlueprints.xml"; String dlcOverwrite = "dlcBlueprintsOverwrite.xml"; String bosses = "bosses.xml"; // All pre-AE player ships are located in blueprints.xml shipFileMap.put(PlayerShipBlueprints.HARD.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.HARD_2.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.MANTIS.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.MANTIS_2.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.STEALTH.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.STEALTH_2.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.CIRCLE.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.CIRCLE_2.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.FED.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.FED_2.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.JELLY.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.JELLY_2.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.ROCK.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.ROCK_2.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.ENERGY.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.ENERGY_2.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.CRYSTAL.toString(), blueprints); shipFileMap.put(PlayerShipBlueprints.CRYSTAL_2.toString(), blueprints); // Lanius' ships are in dlcBlueprints.xml shipFileMap.put(PlayerShipBlueprints.ANAEROBIC.toString(), dlcBlueprints); shipFileMap.put(PlayerShipBlueprints.ANAEROBIC_2.toString(), dlcBlueprints); // Type C for all ships are located in dlcBlueprintsOverwrite.xml shipFileMap.put(PlayerShipBlueprints.HARD_3.toString(), dlcOverwrite); shipFileMap.put(PlayerShipBlueprints.MANTIS_3.toString(), dlcOverwrite); shipFileMap.put(PlayerShipBlueprints.STEALTH_3.toString(), dlcOverwrite); shipFileMap.put(PlayerShipBlueprints.CIRCLE_3.toString(), dlcOverwrite); shipFileMap.put(PlayerShipBlueprints.FED_3.toString(), dlcOverwrite); shipFileMap.put(PlayerShipBlueprints.JELLY_3.toString(), dlcOverwrite); shipFileMap.put(PlayerShipBlueprints.ROCK_3.toString(), dlcOverwrite); shipFileMap.put(PlayerShipBlueprints.ENERGY_3.toString(), dlcOverwrite); // Boss ships are located in bosses.xml shipFileMap.put("BOSS_1_EASY", bosses); shipFileMap.put("BOSS_2_EASY", bosses); shipFileMap.put("BOSS_3_EASY", bosses); shipFileMap.put("BOSS_1_EASY_DLC", bosses); shipFileMap.put("BOSS_2_EASY_DLC", bosses); shipFileMap.put("BOSS_3_EASY_DLC", bosses); shipFileMap.put("BOSS_1_NORMAL", bosses); shipFileMap.put("BOSS_2_NORMAL", bosses); shipFileMap.put("BOSS_3_NORMAL", bosses); shipFileMap.put("BOSS_1_NORMAL_DLC", bosses); shipFileMap.put("BOSS_2_NORMAL_DLC", bosses); shipFileMap.put("BOSS_3_NORMAL_DLC", bosses); shipFileMap.put("BOSS_1_HARD", bosses); shipFileMap.put("BOSS_2_HARD", bosses); shipFileMap.put("BOSS_3_HARD", bosses); shipFileMap.put("BOSS_1_HARD_DLC", bosses); shipFileMap.put("BOSS_2_HARD_DLC", bosses); shipFileMap.put("BOSS_3_HARD_DLC", bosses); } /** * Creates a new Database and fills it with data from the specified archives. * * @param data * the FTLPack representing the data.dat archive * @param resource * the FTLPack representing the resource.dat archive */ public Database(FTLPack data, FTLPack resource) throws FileNotFoundException, IOException { this(); loadCore(data, resource); } public static Database getInstance() { return instance; } /** * @return the first DatabaseEntry in the Database, ie. the DatabaseEntry associated with base game archives. */ public DatabaseEntry getCore() { return dataEntries.size() > 0 ? dataEntries.get(0) : null; } /** * Reloads the core of the Database using the specified archives. * * @param data * the FTLPack representing the data.dat archive * @param resource * the FTLPack representing the resource.dat archive */ public void loadCore(FTLPack data, FTLPack resource) { if (dataEntries.size() > 0) dataEntries.remove(0); DatabaseEntry core = new DatabaseEntry(data, resource); core.store(DEFAULT_ANIM_OBJ); dataEntries.add(0, core); } /** * Verifies the Database, determining whether it is safe to read data from the archives. * * @return true if the database has passed the verification, false otherwise. * * @throws IllegalStateException * if the database has not been initialised yet */ public boolean verify() { DatabaseEntry core = getCore(); if (core == null) throw new IllegalStateException("Database has not been initialised yet."); Image img = null; try { InputStream is = core.getInputStream("img/nullResource.png"); img = new Image(UIUtils.getDisplay(), is); return true; } catch (Exception e) { return false; } finally { if (img != null) img.dispose(); } } /** * @return all database entries currently installed in the database, in the order in which they take effect. * Entry at index 0 is the core entry (ie. represents the content of the game's archives) */ public DatabaseEntry[] getEntries() { return dataEntries.toArray(new DatabaseEntry[0]); } /** * Adds a new database entry to the database.<br> * {@link #cacheAnimations()} should be called afterwards to update weapon sprites and animations. */ public void addEntry(DatabaseEntry de) { dataEntries.add(de); de.load(); } /** * Removes the specified database entry from the database.<br> * {@link #cacheAnimations()} should be called afterwards to update weapon sprites and animations. */ public void removeEntry(DatabaseEntry de) { try { dataEntries.remove(de); de.dispose(); } catch (IOException e) { log.error(String.format("An error has occured while closing database entry '%s': ", de.getName()), e); } } /** * Reorders the specified database entry, allowing to manipulate the order in which the entries' contents take effect.<br> * {@link #cacheAnimations()} should be called afterwards to update weapon sprites and animations. * * @param de * the entry to be reordered * @param index * index at which the entry is to be inserted * @throws IndexOutOfBoundsException * if the index is out of range (index < 0 || index > size()) */ public void reorderEntry(DatabaseEntry de, int index) throws IndexOutOfBoundsException { if (index < 0 || index > dataEntries.size()) throw new IndexOutOfBoundsException(); dataEntries.remove(de); dataEntries.add(index, de); log.trace(String.format("%s reordered to position %s", de, index)); } /** * Updates all WeaponObject's animations, so that they display the correct weapon image. */ public void cacheAnimations() { for (WeaponTypes type : WeaponTypes.values()) { for (WeaponObject object : getWeaponsByType(type)) { object.cacheAnimation(); } } } public boolean isPlayerShip(String blueprintName) { try { PlayerShipBlueprints.valueOf(blueprintName.replace("PLAYER_SHIP_", "")); return true; } catch (Exception e) { return false; } } /** * @param blueprint * blueprint name of a ship * @return name of the file in which the given shipBlueprint should be saved */ public String getAssociatedFile(String blueprint) { if (shipFileMap.containsKey(blueprint)) return shipFileMap.get(blueprint); else return "autoBlueprints.xml"; } public AnimationObject getAnimation(String animName) { AnimationObject result = null; for (int i = dataEntries.size() - 1; i >= 0 && result == null; i--) { DatabaseEntry de = dataEntries.get(i); result = de.getAnimation(animName); } return result; } public AugmentObject getAugment(String blueprintName) { AugmentObject result = null; for (int i = dataEntries.size() - 1; i >= 0 && result == null; i--) { DatabaseEntry de = dataEntries.get(i); result = de.getAugment(blueprintName); } return result; } public ArrayList<AugmentObject> getAugments() { ArrayList<AugmentObject> result = new ArrayList<AugmentObject>(); for (int i = dataEntries.size() - 1; i >= 0; i--) { DatabaseEntry de = dataEntries.get(i); for (AugmentObject o : de.getAugments()) if (!result.contains(o)) result.add(o); } return result; } public ArrayList<DroneList> getDroneLists() { ArrayList<DroneList> result = new ArrayList<DroneList>(); for (int i = dataEntries.size() - 1; i >= 0; i--) { DatabaseEntry de = dataEntries.get(i); for (DroneList o : de.getDroneLists()) if (!result.contains(o)) result.add(o); } return result; } public DroneList getDroneList(String name) { DroneList result = null; for (int i = dataEntries.size() - 1; i >= 0 && result == null; i--) { DatabaseEntry de = dataEntries.get(i); result = de.getDroneList(name); } return result; } public DroneObject getDrone(String blueprint) { DroneObject result = null; for (int i = dataEntries.size() - 1; i >= 0 && result == null; i--) { DatabaseEntry de = dataEntries.get(i); result = de.getDrone(blueprint); } return result; } public ArrayList<DroneObject> getDronesByType(DroneTypes type) { ArrayList<DroneObject> result = new ArrayList<DroneObject>(); for (int i = dataEntries.size() - 1; i >= 0; i--) { DatabaseEntry de = dataEntries.get(i); for (DroneObject o : de.getDronesByType(type)) if (!result.contains(o)) result.add(o); } return result; } public GlowObject getGlow(String id) { GlowObject result = null; for (int i = dataEntries.size() - 1; i >= 0 && result == null; i--) { DatabaseEntry de = dataEntries.get(i); result = de.getGlow(id); } return result; } public ArrayList<GlowObject> getGlows() { ArrayList<GlowObject> result = new ArrayList<GlowObject>(); for (int i = dataEntries.size() - 1; i >= 0; i--) { DatabaseEntry de = dataEntries.get(i); for (GlowObject o : de.getGlows()) if (!result.contains(o)) result.add(o); } return result; } public GlowSet getGlowSet(String id) { GlowSet result = null; for (int i = dataEntries.size() - 1; i >= 0 && result == null; i--) { DatabaseEntry de = dataEntries.get(i); result = de.getGlowSet(id); } return result; } public ArrayList<GlowSet> getGlowSets() { ArrayList<GlowSet> result = new ArrayList<GlowSet>(); for (int i = dataEntries.size() - 1; i >= 0; i--) { DatabaseEntry de = dataEntries.get(i); for (GlowSet o : de.getGlowSets()) if (!result.contains(o)) result.add(o); } return result; } public HashMap<String, ArrayList<ShipMetadata>> getShipMetadata() { HashMap<String, ArrayList<ShipMetadata>> map = new HashMap<String, ArrayList<ShipMetadata>>(); for (DatabaseEntry de : dataEntries) { for (ShipMetadata metadata : de.getShipMetadata()) { ArrayList<ShipMetadata> list = map.get(metadata.getBlueprintName()); if (list == null) list = new ArrayList<ShipMetadata>(); list.add(metadata); map.put(metadata.getBlueprintName(), list); } } return map; } public ArrayList<WeaponList> getWeaponLists() { ArrayList<WeaponList> result = new ArrayList<WeaponList>(); for (int i = dataEntries.size() - 1; i >= 0; i--) { DatabaseEntry de = dataEntries.get(i); for (WeaponList o : de.getWeaponLists()) if (!result.contains(o)) result.add(o); } return result; } public WeaponList getWeaponList(String name) { WeaponList result = null; for (int i = dataEntries.size() - 1; i >= 0 && result == null; i--) { DatabaseEntry de = dataEntries.get(i); result = de.getWeaponList(name); } return result; } public WeaponObject getWeapon(String blueprint) { WeaponObject result = null; for (int i = dataEntries.size() - 1; i >= 0 && result == null; i--) { DatabaseEntry de = dataEntries.get(i); result = de.getWeapon(blueprint); } return result; } public ArrayList<WeaponObject> getWeaponsByType(WeaponTypes type) { ArrayList<WeaponObject> result = new ArrayList<WeaponObject>(); for (int i = dataEntries.size() - 1; i >= 0; i--) { DatabaseEntry de = dataEntries.get(i); for (WeaponObject o : de.getWeaponsByType(type)) if (!result.contains(o)) result.add(o); } return result; } /** * @param innerPath * the sought innerPath * @return true if any of the DatabaseEntries contains the innerPath, false otherwise */ public boolean contains(String innerPath) { boolean result = false; for (int i = dataEntries.size() - 1; i >= 0 && !result; i--) { DatabaseEntry de = dataEntries.get(i); result = de.contains(innerPath); } return result; } /** * * @param innerPath * path to the sought resource * @return InputStream for the sought resource, from the first DatabaseEntry that contains it (starting from last) * * @throws FileNotFoundException * if the Database did not contain the specified innerPath * @throws IOException * if an IO error occurs */ public InputStream getInputStream(String innerPath) throws FileNotFoundException, IOException { InputStream is = null; for (int i = dataEntries.size() - 1; i >= 0 && is == null; i--) { DatabaseEntry de = dataEntries.get(i); if (de.contains(innerPath)) is = de.getInputStream(innerPath); } if (is == null) throw new FileNotFoundException(String.format("Inner path '%s' was not found in the database.", innerPath)); return is; } public List<String> listFiles(Predicate<String> filter) { List<String> result = new ArrayList<String>(); for (int i = dataEntries.size() - 1; i >= 0; i--) { DatabaseEntry de = dataEntries.get(i); for (String innerPath : de.list()) { if (!result.contains(innerPath) && (filter == null || filter.accept(innerPath))) result.add(innerPath); } } return result; } }