package com.kartoflane.superluminal2.ui; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import javax.imageio.ImageIO; import javax.swing.undo.AbstractUndoableEdit; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; import com.kartoflane.ftl.floorgen.FloorImageFactory; import com.kartoflane.superluminal2.Superluminal; import com.kartoflane.superluminal2.components.EventHandler; import com.kartoflane.superluminal2.components.NotDeletableException; import com.kartoflane.superluminal2.components.Tuple; import com.kartoflane.superluminal2.components.enums.Images; import com.kartoflane.superluminal2.components.enums.Systems; import com.kartoflane.superluminal2.components.interfaces.Disposable; import com.kartoflane.superluminal2.components.interfaces.Follower; import com.kartoflane.superluminal2.core.Database; import com.kartoflane.superluminal2.core.DatabaseEntry; import com.kartoflane.superluminal2.core.Grid; import com.kartoflane.superluminal2.core.Grid.Snapmodes; import com.kartoflane.superluminal2.core.LayeredPainter; import com.kartoflane.superluminal2.core.LayeredPainter.Layers; import com.kartoflane.superluminal2.core.Manager; import com.kartoflane.superluminal2.events.SLAddEvent; import com.kartoflane.superluminal2.events.SLEvent; import com.kartoflane.superluminal2.events.SLListener; import com.kartoflane.superluminal2.events.SLRemoveEvent; import com.kartoflane.superluminal2.events.SLRestoreEvent; import com.kartoflane.superluminal2.ftl.DoorObject; import com.kartoflane.superluminal2.ftl.GameObject; import com.kartoflane.superluminal2.ftl.GibObject; import com.kartoflane.superluminal2.ftl.GlowObject; import com.kartoflane.superluminal2.ftl.GlowSet; import com.kartoflane.superluminal2.ftl.ImageObject; import com.kartoflane.superluminal2.ftl.MountObject; import com.kartoflane.superluminal2.ftl.RoomObject; import com.kartoflane.superluminal2.ftl.ShipObject; import com.kartoflane.superluminal2.ftl.SystemObject; import com.kartoflane.superluminal2.ftl.WeaponObject; import com.kartoflane.superluminal2.mvc.controllers.AbstractController; import com.kartoflane.superluminal2.mvc.controllers.DoorController; import com.kartoflane.superluminal2.mvc.controllers.GibController; import com.kartoflane.superluminal2.mvc.controllers.GlowController; import com.kartoflane.superluminal2.mvc.controllers.ImageController; import com.kartoflane.superluminal2.mvc.controllers.MountController; import com.kartoflane.superluminal2.mvc.controllers.ObjectController; import com.kartoflane.superluminal2.mvc.controllers.RoomController; import com.kartoflane.superluminal2.mvc.controllers.ShipController; import com.kartoflane.superluminal2.mvc.controllers.StationController; import com.kartoflane.superluminal2.mvc.controllers.SystemController; import com.kartoflane.superluminal2.mvc.controllers.props.PropController; import com.kartoflane.superluminal2.tools.CreationTool; import com.kartoflane.superluminal2.tools.Tool.Tools; import com.kartoflane.superluminal2.ui.SaveOptionsDialog.SaveOptions; import com.kartoflane.superluminal2.utils.IOUtils; import com.kartoflane.superluminal2.utils.ShipSaveUtils; import com.kartoflane.superluminal2.utils.UIUtils; import com.kartoflane.superluminal2.utils.Utils; /** * A simple class serving as a holder and communication layer between different controllers. * A meta-controller, so to say, or a semi-GUI widget. * * @author kartoFlane * */ public class ShipContainer implements Disposable, SLListener { /** The size of a single cell. Both width and height are equal to this value. */ public static final int CELL_SIZE = 35; public static final String HANGAR_IMG_PATH = "cpath:/assets/hangar.png"; public static final String ENEMY_IMG_PATH = "cpath:/assets/enemy.png"; public static final String SHIELD_RESIZE_PROP_ID = "ShieldResizeHandle"; private ArrayList<RoomController> roomControllers; private ArrayList<DoorController> doorControllers; private ArrayList<MountController> mountControllers; private ArrayList<GibController> gibControllers; private ArrayList<SystemController> systemControllers; private HashMap<GameObject, AbstractController> objectControllerMap; private HashMap<Images, ImageController> imageControllerMap; private HashMap<RoomObject, SystemObject> activeSystemMap; private boolean anchorVisible = true; private boolean stationsVisible = true; private volatile boolean animateGibs = false; private boolean shipSaved = false; private File saveDestination = null; private DatabaseEntry saveMod = null; private ShipController shipController = null; private GibPropContainer gibContainer = null; private EventHandler eventHandler = null; private EditorWindow window = null; private SLListener hangarListener = null; private ShipContainer() { roomControllers = new ArrayList<RoomController>(); doorControllers = new ArrayList<DoorController>(); mountControllers = new ArrayList<MountController>(); gibControllers = new ArrayList<GibController>(); systemControllers = new ArrayList<SystemController>(); objectControllerMap = new HashMap<GameObject, AbstractController>(); imageControllerMap = new HashMap<Images, ImageController>(); activeSystemMap = new HashMap<RoomObject, SystemObject>(); eventHandler = new EventHandler(); hangarListener = new SLListener() { public void handleEvent(SLEvent e) { if (!isPlayerShip() && e.source instanceof RoomController) { ImageController hangar = getImageController(Images.HANGAR); Point size = findShipSize(); hangar.setFollowOffset(size.x / 2, hangar.getFollowOffset().y); } } }; addListener(SLEvent.RESTORE, OverviewWindow.getInstance()); addListener(SLEvent.RESTORE, hangarListener); } public ShipContainer(EditorWindow window, ShipObject ship) { this(); this.window = window; shipController = ShipController.newInstance(this, ship); gibContainer = new GibPropContainer(); window.addListener(SLEvent.MOD_SHIFT, this); addListener(SLEvent.MOD_SHIFT, shipController); ((CreationTool) Manager.getTool(Tools.CREATOR)).setEnabled(Tools.STATION, isPlayerShip()); Grid grid = Grid.getInstance(); for (RoomObject room : ship.getRooms()) { RoomController rc = RoomController.newInstance(this, room); int totalX = (ship.getXOffset() + room.getX() + room.getW() / 2) * CELL_SIZE; int totalY = (ship.getYOffset() + room.getY() + room.getH() / 2) * CELL_SIZE; rc.setSize(room.getW() * CELL_SIZE, room.getH() * CELL_SIZE); rc.setLocation(grid.snapToGrid(totalX, totalY, rc.getSnapMode())); rc.updateFollowOffset(); add(rc); store(rc); } for (DoorObject door : ship.getDoors()) { DoorController dc = DoorController.newInstance(this, door); int totalX = (ship.getXOffset() + door.getX()) * CELL_SIZE; int totalY = (ship.getYOffset() + door.getY()) * CELL_SIZE; dc.setLocation(grid.snapToGrid(totalX, totalY, dc.getSnapMode())); dc.updateFollowOffset(); add(dc); store(dc); } createImageControllers(); ImageController hangarC = getImageController(Images.HANGAR); Point fo = hangarC.getFollowOffset(); hangarC.setFollowOffset(fo.x - ship.getHorizontal(), fo.y - ship.getVertical()); hangarC.updateFollower(); // Gibs are not always listed in their order of appearance... Get by id instead // of iterating over array. GibObject gib = null; int i = ship.getGibs().length; // Gibs need to be added in reverse order for correct layering while ((gib = ship.getGibById(i)) != null) { GibController gc = GibController.newInstance(this, gib); // Calculate offset relative to the ship's anchor to prevent rounding errors from occuring Point offset = ship.getHullOffset(); offset.x += ship.getXOffset() * CELL_SIZE + gc.getSize().x / 2 + gib.getOffsetX(); offset.y += ship.getYOffset() * CELL_SIZE + gc.getSize().y / 2 + gib.getOffsetY(); gc.setFollowOffset(offset); gc.updateFollower(); gc.setParent(getImageController(Images.HULL)); gc.updateFollowOffset(); add(gc); store(gc); i--; } Point hullOffset = ship.getHullOffset(); for (MountObject mount : ship.getMounts()) { MountController mc = MountController.newInstance(this, mount); int totalX = ship.getXOffset() * CELL_SIZE + mount.getX() + hullOffset.x; int totalY = ship.getYOffset() * CELL_SIZE + mount.getY() + hullOffset.y; mc.setFollowOffset(totalX, totalY); mc.updateFollower(); mc.setParent(getImageController(Images.HULL)); mc.updateFollowOffset(); add(mc); store(mc); } // Instantiate first, assign later for (Systems sys : Systems.values()) { for (SystemObject system : ship.getSystems(sys)) { SystemController systemC = SystemController.newInstance(this, system); add(systemC); store(systemC); if (sys.canContainStation()) { StationController sc = StationController.newInstance(this, systemC, system.getStation()); if (isPlayerShip() && sys.canContainGlow()) { GlowController gc = GlowController.newInstance(sc, system.getGlow()); add(gc); store(gc); } add(sc); store(sc); } } } for (Systems sys : Systems.getSystems()) { for (SystemObject system : ship.getSystems(sys)) { RoomController room = (RoomController) getController(system.getRoom()); if (room != null) { assign(system, room); } } } updateMounts(); updateBoundingArea(); updateChildBoundingAreas(); // Mark the ship as saved shipSaved = true; } public EditorWindow getParent() { return window; } public GibPropContainer getGibContainer() { return gibContainer; } /** * This method causes game objects to be updated with data represented by the models.<br> * It should be called right before saving the ship. */ public void updateGameObjects() { ShipObject ship = shipController.getGameObject(); Point offset = findShipOffset(); ship.setXOffset(offset.x / 35); ship.setYOffset(offset.y / 35); // Update image offsets, as they cannot be updated in ImageObjects, since they lack the needed data ImageController imageC = null; // Shield image is anchored at the center of the smallest rectangle that contains all rooms imageC = getImageController(Images.SHIELD); Point size = findShipSize(); Point center = new Point(0, 0); center.x = offset.x + size.x / 2; center.y = offset.y + size.y / 2; Rectangle ellipse = new Rectangle(0, 0, 0, 0); ellipse.x = imageC.getX() - center.x - shipController.getX(); ellipse.y = imageC.getY() - center.y - shipController.getY(); // Ellipse's width and height are half of the actual shield image's dimensions ellipse.width = imageC.getW() / 2; ellipse.height = imageC.getH() / 2; ship.setEllipse(ellipse); center = null; size = null; // Hull image is anchored at the ship origin ImageController hull = imageC = getImageController(Images.HULL); Point hullSize = imageC.getSize(); Point hullOffset = imageC.getLocation(); hullOffset.x += -imageC.getW() / 2 - shipController.getX() - ship.getXOffset() * CELL_SIZE; hullOffset.y += -imageC.getH() / 2 - shipController.getY() - ship.getYOffset() * CELL_SIZE; ship.setHullDimensions(hullOffset.x, hullOffset.y, hullSize.x, hullSize.y); hullSize = null; hullOffset = null; // Floor is anchored at the top-left corner of the hull image imageC = getImageController(Images.FLOOR); Point floorOffset = imageC.getLocation(); floorOffset.x += -imageC.getW() / 2 - (hull.getX() - hull.getW() / 2); floorOffset.y += -imageC.getH() / 2 - (hull.getY() - hull.getH() / 2); ship.setFloorOffset(floorOffset); floorOffset = null; // Cloak is anchored at the top-left corner of the hull image imageC = getImageController(Images.CLOAK); Point cloakOffset = imageC.getLocation(); cloakOffset.x += -imageC.getW() / 2 - (hull.getX() - hull.getW() / 2); cloakOffset.y += -imageC.getH() / 2 - (hull.getY() - hull.getH() / 2); ship.setCloakOffset(cloakOffset); cloakOffset = null; // Update member objects of the ship for (GameObject gameObject : objectControllerMap.keySet()) { gameObject.update(); } } /** * Posts the edit passed in argument to the UndoManager, and flags the ship as not saved. * * @param aue * the undoable edit to be posted. Must not be null. */ public void postEdit(AbstractUndoableEdit aue) { if (aue == null) throw new IllegalArgumentException("Argument must not be null."); shipSaved = false; Manager.postEdit(aue); } /** * Saves the ship at the location supplied in argument.<br> * <br> * Saving method depends on the argument:<br> * - if the argument is a directory, the ship is saved as a resource folder (ie. creates "data" and * "img" folders in the directory passed in argument) * - if the argument is a file, the ship is saved as a zip archive * * @param f * the file the ship is to be saved as, or the directory in which it is to be saved */ public void save(File f) { if (f == null) throw new IllegalStateException("Save destination must not be null."); saveDestination = f; saveMod = null; if (saveDestination.isDirectory()) { EditorWindow.log.trace("Saving ship to " + saveDestination.getAbsolutePath()); try { ShipSaveUtils.saveShipXML(saveDestination, this); shipSaved = true; EditorWindow.log.trace("Ship saved successfully."); } catch (Exception ex) { EditorWindow.log.error("An error occured while saving the ship: ", ex); UIUtils.showWarningDialog(window.getShell(), null, "An error has occured while saving the ship:\n" + ex.getMessage() + "\n\nCheck log for details."); } } else { EditorWindow.log.trace("Saving ship as " + saveDestination.getAbsolutePath()); try { ShipSaveUtils.saveShipFTL(saveDestination, this); shipSaved = true; EditorWindow.log.trace("Ship saved successfully."); } catch (Exception ex) { EditorWindow.log.error("An error occured while saving the ship: ", ex); UIUtils.showWarningDialog(window.getShell(), null, "An error has occured while saving the ship:\n" + ex.getMessage() + "\n\nCheck log for details."); } } } public void save(File f, DatabaseEntry mod) { if (f == null) throw new IllegalStateException("Save destination must not be null."); if (mod == null) { save(f); } else { saveDestination = f; saveMod = mod; if (saveDestination.isDirectory()) { EditorWindow.log.trace("Saving ship to " + saveDestination.getAbsolutePath()); try { ShipSaveUtils.saveShipModXML(saveDestination, mod, this); shipSaved = true; EditorWindow.log.trace("Ship saved successfully."); } catch (Exception ex) { EditorWindow.log.error("An error occured while saving the ship: ", ex); UIUtils.showWarningDialog(window.getShell(), null, "An error has occured while saving the ship:\n" + ex.getMessage() + "\n\nCheck log for details."); } } else { EditorWindow.log.trace("Saving ship as " + saveDestination.getAbsolutePath()); try { ShipSaveUtils.saveShipModFTL(saveDestination, mod, this); shipSaved = true; EditorWindow.log.trace("Ship saved successfully."); } catch (Exception ex) { EditorWindow.log.error("An error occured while saving the ship: ", ex); UIUtils.showWarningDialog(window.getShell(), null, "An error has occured while saving the ship:\n" + ex.getMessage() + "\n\nCheck log for details."); } } } } public SaveOptions getSaveOptions() { return new SaveOptions( saveDestination, saveMod ); } public boolean isSaved() { return shipSaved; } public boolean isPlayerShip() { return shipController.isPlayerShip(); } public ShipController getShipController() { return shipController; } public RoomController[] getRoomControllers() { return roomControllers.toArray(new RoomController[0]); } public DoorController[] getDoorControllers() { return doorControllers.toArray(new DoorController[0]); } public MountController[] getMountControllers() { return mountControllers.toArray(new MountController[0]); } public SystemController[] getSystemControllers() { return systemControllers.toArray(new SystemController[0]); } public GibController[] getGibControllers() { return gibControllers.toArray(new GibController[0]); } public GibController getGibControllerById(int id) { for (GibController gc : gibControllers) { if (gc.getGameObject().getId() == id) return gc; } return null; } public StationController getStationController(Systems systemId) { for (SystemController sys : systemControllers) { if (sys.getSystemId() == systemId) return (StationController) getController(sys.getGameObject().getStation()); } return null; } /** * @return the controller associated with the object passed in argument, or null if not found. */ public AbstractController getController(GameObject object) { return objectControllerMap.get(object); } public void setCloakedAppearance(boolean cloak) { ImageController cloakC = getImageController(Images.CLOAK); ImageController hullC = getImageController(Images.HULL); cloakC.setVisible(cloak); hullC.setAlpha(cloak ? 255 / 3 : 255); hullC.redraw(); } /** * @return the ship's offset, in pixels. */ public Point findShipOffset() { int nx = -1, ny = -1; for (RoomController room : roomControllers) { int t = room.getX() - room.getW() / 2 - shipController.getX(); if (nx == -1 || nx > t) nx = t; t = room.getY() - room.getH() / 2 - shipController.getY(); if (ny == -1 || ny > t) ny = t; } return Grid.getInstance().snapToGrid(nx, ny, Snapmodes.CROSS); } /** * @return the size of the ship, in pixels. */ public Point findShipSize() { int mx = -1, my = -1; Point offset = findShipOffset(); for (RoomController room : roomControllers) { int t = room.getX() + room.getW() / 2 - shipController.getX() - offset.x; if (t > mx) mx = t; t = room.getY() + room.getH() / 2 - shipController.getY() - offset.y; if (t > my) my = t; } return Grid.getInstance().snapToGrid(mx, my, Snapmodes.CROSS); } /** * Calculates the optimal values for the X_OFFSET and Y_OFFSET ship layout properties, * so that the ship will be centered in the game screen. * * @return point containing the calculated values; x holds x_offset, y holds y_offset */ public Point findOptimalThickOffset() { if (roomControllers.size() == 0) return new Point(0, 0); Point result = new Point(0, 0); Point size = findShipSize(); if (isPlayerShip()) { int hangarWidth = 259; int hangarHeight = 177; int horizontalSpace = hangarWidth - size.x / 2; int verticalSpace = hangarHeight - size.y / 2; result.x = Math.max(horizontalSpace / CELL_SIZE, 0); result.y = Math.max(verticalSpace / CELL_SIZE, 0); } else { // All enemy ships have to have thick offset equal to 0 result.x = 0; result.y = 0; } return result; } /** * Calculates the optimal values for the HORIZONTAL and VERTICAL ship layout properties, * so that the ship will be centered in the game screen. * * @return point containing the calculated values; x holds horizontal, y holds vertical */ public Point findOptimalFineOffset() { if (roomControllers.size() == 0) return new Point(0, 0); Point result = new Point(0, 0); Point size = findShipSize(); if (isPlayerShip()) { int hangarWidth = 259; int hangarHeight = 177; int horizontalSpace = hangarWidth - size.x / 2; int verticalSpace = hangarHeight - size.y / 2; result.x = horizontalSpace % CELL_SIZE; result.y = verticalSpace % CELL_SIZE; } else { // All enemy ships have to have fine horizontal offset equal to 0, that way they're centered // Actualy viewable area of the enemy window is 376 x 504 // Select midpoint in relation to which the ship's offset will be calculated int enemyWindowMidpoint = 249; // The amount of space taken up by the "target" text, and hull and shield indicators int topMargin = 77; // Horizontal doesn't affect enemy ships, so 0. result.x = 0; result.y = enemyWindowMidpoint - size.y / 2 - topMargin; } return result; } public void setShipOffset(int x, int y) { ImageController hangarC = getImageController(Images.HANGAR); if (hangarC == null) return; ShipObject ship = shipController.getGameObject(); for (Follower fol : shipController.getFollowers()) { if (fol instanceof PropController == false && fol != hangarC) { Point old = fol.getFollowOffset(); fol.setFollowOffset(old.x + (x - ship.getXOffset()) * CELL_SIZE, old.y + (y - ship.getYOffset()) * CELL_SIZE); fol.updateFollower(); } } ship.setXOffset(x); ship.setYOffset(y); } public Point getShipOffset() { ShipObject ship = shipController.getGameObject(); return ship.getOffsetThick(); } /** * Horizontal:<br> * - positive values move the ship to the right (hangar to the left relative to the ship)<br> * Vertical:<br> * - positive values move the ship to the bottom (hangar to the top relative to the ship) * * @param x * @param y */ public void setShipFineOffset(int x, int y) { ImageController hangarC = getImageController(Images.HANGAR); if (hangarC == null) return; ShipObject ship = shipController.getGameObject(); Point fo = hangarC.getFollowOffset(); hangarC.setFollowOffset(fo.x + ship.getHorizontal() - x, fo.y + ship.getVertical() - y); hangarC.updateFollower(); ship.setHorizontal(x); ship.setVertical(y); } public Point getShipFineOffset() { ShipObject ship = shipController.getGameObject(); return ship.getOffsetFine(); } public void generateFloorImage(FloorImageFactory fif) { ShipObject ship = shipController.getGameObject(); // Prepare the ship data updateGameObjects(); ship.coalesceRooms(); // Remember door links and recover them later -- linking doors automatically persists after saving // is completed, which can cause bugs when the user moves the doors/rooms around and saves again HashMap<DoorObject, Tuple<RoomObject, RoomObject>> doorLinkMap = new HashMap<DoorObject, Tuple<RoomObject, RoomObject>>(); for (DoorObject d : ship.getDoors()) doorLinkMap.put(d, new Tuple<RoomObject, RoomObject>(d.getLeftRoom(), d.getRightRoom())); ship.linkDoors(); // Generate the image if (fif == null) fif = new FloorImageFactory(); String content = ShipSaveUtils.generateLayoutTXT(ship); InputStream is = null; try { is = IOUtils.encodeText(content, "UTF-8", null); BufferedImage floorImage = fif.generateFloorImage(is); // Save the image to a file, since that's how the editor handles images... ImageIO.write(floorImage, "PNG", new File("floor_image.png")); setImage(Images.FLOOR, null); setImage(Images.FLOOR, "file:floor_image.png"); ImageController floorC = getImageController(Images.FLOOR); floorC.setVisible(true); } catch (Exception ex) { StringBuilder buf = new StringBuilder(); buf.append(Superluminal.APP_NAME); buf.append(" has encountered an error while generating the floor image:\n\n"); buf.append(ex.getClass().getSimpleName()); buf.append(": "); buf.append(ex.getMessage()); buf.append("\n\nCheck log for details."); Superluminal.log.warn("Error while generating floor image:", ex); UIUtils.showWarningDialog(null, null, buf.toString()); } finally { // Recover door links for (DoorObject d : ship.getDoors()) { d.setLeftRoom(doorLinkMap.get(d).getKey()); d.setRightRoom(doorLinkMap.get(d).getValue()); } try { if (is != null) is.close(); } catch (IOException ex) { } } } public void assign(SystemObject sys, RoomController room) { if (sys == null) throw new IllegalArgumentException("System id must not be null."); if (room == null) throw new IllegalArgumentException("Room controller is null. Use unassign() instead."); SystemController system = (SystemController) getController(sys); unassign(sys); system.assignTo(room.getGameObject()); system.reposition(room.getX(), room.getY()); system.setParent(room); setActiveSystem(room.getGameObject(), sys); system.notifySizeChanged(room.getW(), room.getH()); if (isPlayerShip() && system.canContainGlow() && sys.getGlow() == Database.DEFAULT_GLOW_OBJ) { Database db = Database.getInstance(); String glow = sys.getSystemId().getDefaultInteriorNamespace(); if (system.getSystemId() == Systems.CLOAKING) { GlowSet glowSet = db.getGlowSet(glow); sys.getGlow().setGlowSet(glowSet); } else { glow = glow.replace("room_", ""); GlowObject glowObject = db.getGlow(glow); if (glowObject != null) { sys.setGlow(glowObject); } } } if (isPlayerShip() && system.canContainGlow() && system.canContainStation()) { GlowController glow = (GlowController) getController(sys.getGlow()); if (glow != null) glow.applyGlowSettings(); } if (system.getSystemId() == Systems.ARTILLERY) updateMounts(); } public void unassign(SystemObject system) { if (system == null) throw new IllegalArgumentException("System must not be null."); SystemController systemC = (SystemController) getController(system); RoomObject room = system.getRoom(); if (activeSystemMap.get(room) == system) { ArrayList<SystemObject> systems = getAllAssignedSystems(room); if (systems.size() > 0) setActiveSystem(room, systems.get(0)); else activeSystemMap.remove(room); } systemC.unassign(); if (system.getSystemId() == Systems.ARTILLERY) updateMounts(); } public boolean isAssigned(Systems sys) { ShipObject ship = shipController.getGameObject(); for (SystemObject system : ship.getSystems(sys)) { if (system.isAssigned()) return true; } return false; } public void add(AbstractController controller, int index) { if (index < 0) { add(controller); return; } if (controller instanceof RoomController) { if (index >= roomControllers.size()) { add(controller); return; } RoomController room = (RoomController) controller; room.setId(index); roomControllers.add(index, room); shipController.getGameObject().add(room.getGameObject()); room.addListener(SLEvent.MOVE, hangarListener); room.addListener(SLEvent.RESIZE, hangarListener); room.addListener(SLEvent.DELETE, hangarListener); } else if (controller instanceof DoorController) { if (index >= doorControllers.size()) { add(controller); return; } DoorController door = (DoorController) controller; doorControllers.add(index, door); shipController.getGameObject().add(door.getGameObject()); } else if (controller instanceof MountController) { if (index >= mountControllers.size()) { add(controller); return; } MountController mount = (MountController) controller; mount.setId(index); mountControllers.add(index, mount); shipController.getGameObject().add(mount.getGameObject()); updateMounts(); } else if (controller instanceof GibController) { if (index >= gibControllers.size()) { add(controller); return; } GibController gib = (GibController) controller; gib.setId(index); gibControllers.add(index, gib); shipController.getGameObject().add(gib.getGameObject()); sort(); } if (controller instanceof ObjectController) { eventHandler.sendEvent(new SLAddEvent(this, controller)); } addListener(SLEvent.MOD_SHIFT, controller); } public void add(AbstractController controller) { if (controller instanceof RoomController) { RoomController room = (RoomController) controller; if (room.getId() == Database.AIRLOCK_OBJECT.getId()) room.setId(getNextRoomId()); roomControllers.add(room); shipController.getGameObject().add(room.getGameObject()); room.addListener(SLEvent.MOVE, hangarListener); room.addListener(SLEvent.RESIZE, hangarListener); room.addListener(SLEvent.DELETE, hangarListener); } else if (controller instanceof DoorController) { DoorController door = (DoorController) controller; doorControllers.add(door); shipController.getGameObject().add(door.getGameObject()); } else if (controller instanceof MountController) { MountController mount = (MountController) controller; if (mount.getId() == -2) mount.setId(getNextMountId()); mountControllers.add(mount); shipController.getGameObject().add(mount.getGameObject()); updateMounts(); } else if (controller instanceof GibController) { GibController gib = (GibController) controller; gibControllers.add(gib); shipController.getGameObject().add(gib.getGameObject()); sort(); } else if (controller instanceof SystemController) { SystemController system = (SystemController) controller; systemControllers.add(system); } if (controller instanceof ObjectController) { eventHandler.sendEvent(new SLAddEvent(this, controller)); } addListener(SLEvent.MOD_SHIFT, controller); } public void remove(AbstractController controller) { if (controller instanceof RoomController) { RoomController room = (RoomController) controller; roomControllers.remove(room); shipController.getGameObject().remove(room.getGameObject()); } else if (controller instanceof DoorController) { DoorController door = (DoorController) controller; doorControllers.remove(door); shipController.getGameObject().remove(door.getGameObject()); } else if (controller instanceof MountController) { MountController mount = (MountController) controller; mountControllers.remove(mount); shipController.getGameObject().remove(mount.getGameObject()); updateMounts(); } else if (controller instanceof GibController) { GibController gib = (GibController) controller; gibControllers.remove(gib); shipController.getGameObject().remove(gib.getGameObject()); } else if (controller instanceof SystemController) { SystemController system = (SystemController) controller; systemControllers.remove(system); } if (controller instanceof ObjectController) { eventHandler.sendEvent(new SLRemoveEvent(this, controller)); } removeListener(SLEvent.MOD_SHIFT, controller); } public void store(AbstractController controller) { if (controller instanceof ObjectController) objectControllerMap.put(((ObjectController) controller).getGameObject(), controller); } public void dispose(AbstractController controller) { if (controller instanceof ObjectController) objectControllerMap.remove(((ObjectController) controller).getGameObject()); controller.dispose(); } public void sort() { int r = roomControllers.hashCode(); int m = mountControllers.hashCode(); int g = gibControllers.hashCode(); Collections.sort(roomControllers); Collections.sort(mountControllers); Collections.sort(gibControllers); shipController.getGameObject().sort(); // Reinsert controllers into the painter so that they're drawn in the correct order // Compare hash codes to determine whether the collections have changed if (r != roomControllers.hashCode()) { for (RoomController c : roomControllers) c.removeFromPainter(); for (RoomController c : roomControllers) c.addToPainter(Layers.ROOM); } if (m != mountControllers.hashCode()) { for (MountController c : mountControllers) c.removeFromPainter(); for (MountController c : mountControllers) c.addToPainter(Layers.MOUNT); } if (g != gibControllers.hashCode()) { for (GibController c : gibControllers) c.removeFromPainter(); for (GibController c : gibControllers) c.addToPainterBottom(Layers.GIBS); } window.canvasRedraw(); } public void setImage(Images imageType, String path) { ImageController image = imageControllerMap.get(imageType); if (imageType == Images.THUMBNAIL) { // Thumbnail has no visual representation ImageObject object = image.getGameObject(); object.setImagePath(path); } else { boolean vis = getParent().isImageDrawn(imageType); image.setVisible(false); image.setImage(path); image.updateView(); image.setVisible(path != null && vis); } } public String getImage(Images imageType) { ImageController image = imageControllerMap.get(imageType); return image.getImage(); } public ImageController getImageController(Images imageType) { return imageControllerMap.get(imageType); } private int getNextRoomId() { return shipController.getGameObject().getNextRoomId(); } private int getNextMountId() { return shipController.getGameObject().getNextMountId(); } public void changeWeapon(int index, WeaponObject neu) { shipController.getGameObject().changeWeapon(index, neu); updateMounts(); } public void changeWeapon(WeaponObject old, WeaponObject neu) { shipController.getGameObject().changeWeapon(old, neu); updateMounts(); } public void updateMounts() { int i = 0; ShipObject ship = shipController.getGameObject(); WeaponObject[] weapons = ship.getWeapons(); ArrayList<SystemObject> artilleries = ship.getSystems(Systems.ARTILLERY); int slots = ship.getWeaponSlots(); for (MountController mount : mountControllers) { mount.setId(i); if (i < slots) { if (ship.getWeaponsByList()) { mount.setWeapon(Database.DEFAULT_WEAPON_OBJ); } else { mount.setWeapon(weapons[i]); } } else { // Artillery starts taking up mounts after the weapons, or 4th slot if the // ship has less than 4 slots. As such, a dummy mount is required int j = i - Math.max(4, slots); if (j >= 0 && j < artilleries.size() && artilleries.get(j).isAssigned()) { mount.setWeapon(artilleries.get(j).getWeapon()); } else { mount.setWeapon(Database.DEFAULT_WEAPON_OBJ); } } i++; } } /** * Deletes the controller and posts an undoable edit. */ public void delete(AbstractController controller) { if (controller == null) throw new IllegalArgumentException("Argument must not be null."); if (!controller.isDeletable()) throw new NotDeletableException(); controller.delete(); remove(controller); updateBoundingArea(); } /** * Restores the deleted controller. */ public void restore(AbstractController controller, int index) { if (controller == null) throw new IllegalArgumentException("Argument must not be null."); if (!controller.isDeletable()) throw new NotDeletableException(); if (!controller.isDeleted()) throw new IllegalArgumentException("The controller has not been deleted."); controller.restore(); add(controller, index); updateBoundingArea(); if (eventHandler.hooks(SLEvent.RESTORE)) eventHandler.sendEvent(new SLRestoreEvent(controller)); } public void dispose() throws NotDeletableException { if (isDisposed()) return; for (MountController mount : mountControllers) { objectControllerMap.remove(mount.getGameObject()); mount.dispose(); } for (DoorController door : doorControllers) { objectControllerMap.remove(door.getGameObject()); door.dispose(); } for (RoomController room : roomControllers) { objectControllerMap.remove(room.getGameObject()); room.dispose(); } for (SystemController system : systemControllers) { objectControllerMap.remove(system.getGameObject()); system.dispose(); } for (GibController gib : gibControllers) { objectControllerMap.remove(gib.getGameObject()); gib.dispose(); } for (ImageController image : imageControllerMap.values()) { objectControllerMap.remove(image.getGameObject()); image.dispose(); } AbstractController[] objectControllers = objectControllerMap.values().toArray(new AbstractController[0]); for (AbstractController ac : objectControllers) { ac.dispose(); } roomControllers.clear(); doorControllers.clear(); mountControllers.clear(); gibControllers.clear(); systemControllers.clear(); imageControllerMap.clear(); objectControllerMap.clear(); window.removeListener(SLEvent.MOD_SHIFT, this); gibContainer.dispose(); shipController.dispose(); shipController = null; eventHandler.dispose(); System.gc(); } public boolean isDisposed() { return shipController == null; } public void updateChildBoundingAreas() { for (RoomController rc : roomControllers) rc.updateBoundingArea(); for (DoorController dc : doorControllers) dc.updateBoundingArea(); for (MountController mc : mountControllers) mc.updateBoundingArea(); for (GibController gc : gibControllers) gc.updateBoundingArea(); for (ImageController ic : imageControllerMap.values()) ic.updateBoundingArea(); } public void updateBoundingArea() { Point gridSize = Grid.getInstance().getSize(); gridSize.x -= (gridSize.x % CELL_SIZE) + CELL_SIZE; gridSize.y -= (gridSize.y % CELL_SIZE) + CELL_SIZE; Point offset = findShipOffset(); Point size = findShipSize(); shipController.setBoundingPoints(0, 0, gridSize.x - offset.x - size.x, gridSize.y - offset.y - size.y); offset.x = offset.x / CELL_SIZE; offset.y = offset.y / CELL_SIZE; shipController.getGameObject().setXOffset(offset.x); shipController.getGameObject().setYOffset(offset.y); shipController.updateProps(); } private void createImageControllers() { // Instantiation order is important for proper layering final ShipObject ship = shipController.getGameObject(); ImageObject imgObject = null; Point offset = null; Point center = null; // Load hangar image imgObject = new ImageObject(); imgObject.setAlias("hangar"); imgObject.setImagePath(isPlayerShip() ? HANGAR_IMG_PATH : ENEMY_IMG_PATH); final ImageController hangar = ImageController.newInstance(shipController, imgObject); hangar.setSelectable(false); hangar.removeFromPainter(); hangar.addToPainter(Layers.BACKGROUND); hangar.setImage(imgObject.getImagePath()); offset = hangar.getSize(); // For FTL hangar image: 365, 30 // For SL2 hangar image: 94, 56 // For enemy window image: 193, 239 if (isPlayerShip()) { hangar.setFollowOffset(offset.x / 2 - 94, offset.y / 2 - 56); } else { // Not entirely clear correction; perhaps related to the difference between in-game enemy // window, and the image SL2 uses, which is 26 pixels smaller. 26 * 2 = 52, so kinda related... // Either way, adding this value causes all enemy ships to become correctly aligned int correction = 53; hangar.setFollowOffset(offset.x / 2 - 193, offset.y / 2 - 239 + Database.ENEMY_SHIELD_Y_OFFSET + correction); } hangar.updateFollower(); hangar.updateView(); imageControllerMap.put(Images.HANGAR, hangar); // Enemy ships don't use HORIZONTAL offset; the game automatically centers them in the enemy window // Have the hangar image listen to events when the ship's width changes, so that it can modify its own // location as needed addListener(SLEvent.ADD_OBJECT, hangarListener); addListener(SLEvent.REM_OBJECT, hangarListener); addListener(SLEvent.MOVE, hangarListener); // Load shield imgObject = ship.getImage(Images.SHIELD); ImageController shield = ImageController.newInstance(shipController, imgObject); shield.setImage(imgObject.getImagePath()); offset = findShipOffset(); center = findShipSize(); center.x = offset.x + center.x / 2; center.y = offset.y + center.y / 2; shield.setFollowOffset(center.x + ship.getEllipseX(), center.y + ship.getEllipseY()); if (!isPlayerShip()) { shield.setSize(ship.getEllipseWidth() * 2, ship.getEllipseHeight() * 2); } shield.updateView(); imageControllerMap.put(Images.SHIELD, shield); add(shield); store(shield); shield.setBounded(true); if (!isPlayerShip()) { // Shield resize prop PropController prop = new PropController(shield, SHIELD_RESIZE_PROP_ID); prop.setSelectable(true); prop.setInheritVisibility(true); prop.setDefaultBackgroundColor(128, 128, 255); prop.setDefaultBorderColor(0, 0, 0); prop.setBorderThickness(3); prop.setCompositeTitle("Shield Resize Handle"); prop.setSize(CELL_SIZE / 2, CELL_SIZE / 2); prop.setLocation(shield.getX() + shield.getW() / 2, shield.getY() + shield.getH() / 2); prop.updateFollowOffset(); prop.addToPainter(Layers.SHIP_ORIGIN); prop.updateView(); shield.addProp(prop); prop.addListener(SLEvent.MOVE, new SLListener() { @Override public void handleEvent(SLEvent e) { Point p = (Point) e.data; ImageController shieldC = getImageController(Images.SHIELD); shieldC.resize(Math.abs(p.x - shieldC.getX()) * 2, Math.abs(p.y - shieldC.getY()) * 2); } }); } // Load hull imgObject = ship.getImage(Images.HULL); ImageController hull = ImageController.newInstance(shipController, imgObject); hull.setImage(imgObject.getImagePath()); offset = ship.getHullOffset(); offset.x += ship.getXOffset() * CELL_SIZE + ship.getHullSize().x / 2; offset.y += ship.getYOffset() * CELL_SIZE + ship.getHullSize().y / 2; hull.setSize(ship.getHullSize()); hull.setFollowOffset(offset); hull.updateFollower(); hull.updateView(); imageControllerMap.put(Images.HULL, hull); add(hull); store(hull); hull.setBounded(true); // Load cloak imgObject = ship.getImage(Images.CLOAK); ImageController cloak = ImageController.newInstance(hull, imgObject); cloak.setImage(imgObject.getImagePath()); // Cloak's offset is relative to hull's top-left corner offset = ship.getCloakOffset(); center = Utils.add(hull.getLocationCorner(), ship.getCloakOffset()); cloak.setLocationCorner(center.x, center.y); cloak.updateFollowOffset(); cloak.updateView(); imageControllerMap.put(Images.CLOAK, cloak); add(cloak); store(cloak); cloak.setVisible(false); // Cloak is only displayed when View Cloak is enabled cloak.setBounded(true); // Load floor imgObject = ship.getImage(Images.FLOOR); ImageController floor = ImageController.newInstance(hull, imgObject); floor.setImage(imgObject.getImagePath()); // Floor's offset is relative to hull's top-left corner offset = ship.getFloorOffset(); // ================== int wCorrection = 0; int hCorrection = 0; if ((floor.getW() - hull.getW()) % 2 != 0) wCorrection = 1; if ((floor.getH() - hull.getH()) % 2 != 0) hCorrection = 1; // ================== center.x = (int) Math.round((floor.getW() - hull.getW() + wCorrection) / 2.0) + offset.x; center.y = (int) Math.round((floor.getH() - hull.getH() + hCorrection) / 2.0) + offset.y; floor.setFollowOffset(center.x, center.y); floor.updateView(); imageControllerMap.put(Images.FLOOR, floor); add(floor); store(floor); floor.setBounded(true); // Load thumbnail imgObject = ship.getImage(Images.THUMBNAIL); ImageController thumbnail = ImageController.newInstance(shipController, imgObject); thumbnail.setImage(imgObject.getImagePath()); imageControllerMap.put(Images.THUMBNAIL, thumbnail); add(thumbnail); store(thumbnail); thumbnail.setVisible(false); } public void setHangarVisible(boolean vis) { getImageController(Images.HANGAR).setVisible(vis); } public boolean isHangarVisible() { return getImageController(Images.HANGAR).isVisible(); } public void setAnchorVisible(boolean vis) { if (shipController.isSelected()) Manager.setSelected(null); anchorVisible = vis; shipController.setVisible(vis); } public boolean isAnchorVisible() { return anchorVisible; } public void setRoomsVisible(boolean vis) { if (Manager.getSelected() != null && Manager.getSelected() instanceof RoomController) Manager.setSelected(null); LayeredPainter painter = LayeredPainter.getInstance(); painter.setLayerDrawn(Layers.ROOM, vis); painter.setLayerDrawn(Layers.SYSTEM, vis); painter.setLayerDrawn(Layers.STATION, vis && isStationsVisible()); painter.setLayerDrawn(Layers.GLOW, vis && isStationsVisible()); window.canvasRedraw(); } public boolean isRoomsVisible() { return LayeredPainter.getInstance().isLayerDrawn(Layers.ROOM); } public void setDoorsVisible(boolean vis) { if (Manager.getSelected() != null && Manager.getSelected() instanceof DoorController) Manager.setSelected(null); LayeredPainter.getInstance().setLayerDrawn(Layers.DOOR, vis); window.canvasRedraw(); } public boolean isDoorsVisible() { return LayeredPainter.getInstance().isLayerDrawn(Layers.DOOR); } public void setMountsVisible(boolean vis) { if (Manager.getSelected() != null && Manager.getSelected() instanceof MountController) Manager.setSelected(null); LayeredPainter.getInstance().setLayerDrawn(Layers.MOUNT, vis); window.canvasRedraw(); } public boolean isMountsVisible() { return LayeredPainter.getInstance().isLayerDrawn(Layers.MOUNT); } public void setStationsVisible(boolean vis) { stationsVisible = vis; LayeredPainter.getInstance().setLayerDrawn(Layers.STATION, vis && isRoomsVisible()); LayeredPainter.getInstance().setLayerDrawn(Layers.GLOW, vis && isRoomsVisible()); window.canvasRedraw(); } public boolean isStationsVisible() { return stationsVisible; } public void setGibsVisible(boolean vis) { if (Manager.getSelected() != null && Manager.getSelected() instanceof GibController) Manager.setSelected(null); LayeredPainter.getInstance().setLayerDrawn(Layers.GIBS, vis); window.canvasRedraw(); } public boolean isGibsVisible() { return LayeredPainter.getInstance().isLayerDrawn(Layers.GIBS); } public void setActiveSystem(RoomObject room, SystemObject newSystem) { if (room == null) throw new IllegalArgumentException("Room must not be null."); if (newSystem == null) throw new IllegalArgumentException("System must not be null."); RoomController roomC = (RoomController) getController(room); SystemController newSystemC = (SystemController) getController(newSystem); SystemObject prevSystem = getActiveSystem(room); SystemController prevSystemC = (SystemController) getController(prevSystem); if (newSystemC == prevSystemC) return; // They're the same, nothing to do prevSystemC.setVisible(false); newSystemC.setVisible(true); roomC.removeListener(SLEvent.VISIBLE, prevSystemC); roomC.removeListener(SLEvent.RESIZE, prevSystemC); if (prevSystemC.canContainStation()) { StationController station = (StationController) getController(prevSystem.getStation()); roomC.removeListener(SLEvent.RESIZE, station); } activeSystemMap.put(room, newSystem); roomC.addListener(SLEvent.VISIBLE, newSystemC); roomC.addListener(SLEvent.RESIZE, newSystemC); if (newSystemC.canContainStation()) { StationController station = (StationController) getController(newSystem.getStation()); if (!isPlayerShip()) station.setSlotId(-2); roomC.addListener(SLEvent.RESIZE, station); station.updateFollowOffset(); station.updateFollower(); } } public SystemObject getActiveSystem(RoomObject room) { if (activeSystemMap.containsKey(room)) { SystemObject result = activeSystemMap.get(room); if (result != null && result.isAssigned() && result.getRoom() == room) return result; } return shipController.getGameObject().getSystem(Systems.EMPTY); } public ArrayList<SystemObject> getAllAssignedSystems(RoomObject room) { ArrayList<SystemObject> systems = new ArrayList<SystemObject>(); for (Systems sys : Systems.getSystems()) { for (SystemObject system : shipController.getGameObject().getSystems(sys)) { if (system.getRoom() == room) systems.add(system); } } SystemObject active = getActiveSystem(room); if (active.getSystemId() != Systems.EMPTY) { systems.remove(active); systems.add(active); } return systems; } public void addListener(int eventType, SLListener listener) { eventHandler.hook(eventType, listener); } public void removeListener(int eventType, SLListener listener) { eventHandler.unhook(eventType, listener); } public void removeListener(SLListener listener) { eventHandler.unhook(listener); } @Override public void handleEvent(SLEvent e) { // Send the event over to controllers eventHandler.sendEvent(e); } /** * Initiates gib animation, while also putting the editor in a non-interactable state.<br> * Does nothing if gib animation is currently in progress. */ public void triggerGibAnimation() { if (!isGibAnimationInProgress()) { if (gibControllers.size() == 0) { UIUtils.showInfoDialog(null, null, "Unable to animate gibs because the ship has no gibs."); return; } animateGibs = true; window.updateGibAnimationButton(false); window.setInteractable(false); animateGibs(); } } /** * Cancels the gib animation. */ public void stopGibAnimation() { animateGibs = false; window.updateGibAnimationButton(true); window.setInteractable(true); } public boolean isGibAnimationInProgress() { return animateGibs; } /** * Animates gibs and weapon mounts attached to them, trying to mimic how the animation will look in-game.<br> * * Animation is executed in a separate thread, leaving GUI responsive. */ private void animateGibs() { // Stores time that has elapsed since the animation started, in ms final int[] elapsed = new int[1]; // Time between animation frames, in ms final int interval = 20; // Total time the animation is supposed to last, in ms final int animTime = Database.GIB_DEATH_ANIM_TIME * 1000; // Map to store the gib's index for use with the arrays defined below // Sure there's indexOf(), but it'd have to be ran multiple times every interval final HashMap<GibController, Integer> gMap = new HashMap<GibController, Integer>(); // Store mounts that are to be animated (ie. ones that are linked to a gib) final ArrayList<MountController> animMounts = new ArrayList<MountController>(); // Remember which mounts were shown and hidden (since unlinked mounts are hidden during animation) final boolean[] mVis = new boolean[mountControllers.size()]; // Mounts that are not linked to a gib are hidden and not animated for (int i = 0; i < mountControllers.size(); i++) { MountController mc = mountControllers.get(i); boolean visible = mc.getGib() != Database.DEFAULT_GIB_OBJ; if (visible) animMounts.add(mc); mVis[i] = mc.isVisible(); mc.setVisible(mVis[i] && visible); mc.getProp(MountController.ARROW_PROP_ID).setVisible(false); } final int gSize = gibControllers.size(); final int mSize = animMounts.size(); // Select direction in which the gib will float final int[] direction = new int[gSize]; // Select linear velocity final double[] linear = new double[gSize]; // Select angular velocity, in degrees final double[] angular = new double[gSize]; // Remember old locations final Point[] gLocations = new Point[gSize]; final Point[] mLocations = new Point[mSize]; // Distance between mount and its linked gib final int[] distance = new int[mSize]; // Angle between the mount and its linked gib final double[] angles = new double[mSize]; // Gather data from gibs for (int i = 0; i < gSize; i++) { GibController gc = gibControllers.get(i); gMap.put(gc, i); gLocations[i] = gc.getLocation(); direction[i] = Utils.convertAngle(Utils.random(gc.getDirectionMin(), gc.getDirectionMax())); linear[i] = Utils.random(gc.getLinearVelocityMin(), gc.getLinearVelocityMax()) * Database.GIB_LINEAR_SPEED; angular[i] = Utils.random(gc.getAngularVelocityMin(), gc.getAngularVelocityMax()) * Math.toDegrees(Database.GIB_ANGULAR_SPEED); } // Gather data from mounts for (int i = 0; i < mSize; i++) { MountController mc = animMounts.get(i); Point p = getController(mc.getGib()).getLocation(); mLocations[i] = mc.getLocation(); distance[i] = Utils.distance(mLocations[i], p); angles[i] = Utils.convertAngle(Utils.angle(p, mLocations[i])); } final Display display = UIUtils.getDisplay(); Runnable animateRun = new Runnable() { public void run() { // Prevent the editor from crashing if the user exits the application while animation is in progress if (window.getShell().isDisposed()) return; // Animate gibs for (int i = 0; i < gSize; i++) { GibController gc = gibControllers.get(i); int x = gLocations[i].x + (int) Math.round(linear[i] * (elapsed[0] / 1000f) * Math.cos(Math.toRadians(direction[i]))); int y = gLocations[i].y + (int) Math.round(linear[i] * (elapsed[0] / 1000f) * Math.sin(Math.toRadians(direction[i]))); gc.setVisible(false); gc.setRotation((float) angular[i] * (elapsed[0] / 1000f)); gc.setLocation(x, y); gc.setVisible(true); } // Animate mounts for (int i = 0; i < mSize; i++) { MountController mc = animMounts.get(i); GibController gc = (GibController) getController(mc.getGib()); int j = gMap.get(gc); float ang = (float) angular[j] * (elapsed[0] / 1000f); Point p = Utils.polar(gc.getLocation(), Math.toRadians(angles[i] + ang), distance[i]); mc.setVisible(false); mc.setRotation((mc.isRotated() ? 90 : 0) + ang); mc.setLocation(p); mc.setVisible(true); } elapsed[0] += interval; // User can cancel animation via button, which modifies the animateGibs var, so check for that too animateGibs = elapsed[0] < animTime && animateGibs; if (animateGibs) { // If animation is supposed to continue, run the runnable again display.timerExec(interval, this); } else { // Restore data for (int i = 0; i < gSize; i++) { GibController gc = gibControllers.get(i); gc.setRotation(0); gc.reposition(gLocations[i]); } for (int i = 0; i < mountControllers.size(); i++) { MountController mc = mountControllers.get(i); mc.setVisible(mVis[i]); mc.getProp(MountController.ARROW_PROP_ID).setVisible(mVis[i]); } for (int i = 0; i < mSize; i++) { MountController mc = animMounts.get(i); mc.setRotation(mc.isRotated() ? 90 : 0); mc.reposition(mLocations[i]); } stopGibAnimation(); } } }; display.timerExec(interval, animateRun); } }