From 300a62e621d430a6c9c13e773089d00570b2c02c Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Thu, 31 Mar 2022 22:08:51 +0200 Subject: [PATCH] Added starter components. --- .gitignore | 3 + design/icon.svg | 111 ++++++++++++++++++ pom.xml | 52 ++++++++ .../andrewl/starship_arena/StarshipArena.java | 21 ++++ .../andrewl/starship_arena/model/Arena.java | 21 ++++ .../andrewl/starship_arena/model/Camera.java | 37 ++++++ .../starship_arena/model/PhysicsObject.java | 58 +++++++++ .../nl/andrewl/starship_arena/model/Ship.java | 56 +++++++++ .../starship_arena/model/ShipModel.java | 70 +++++++++++ .../starship_arena/model/ship/Cockpit.java | 7 ++ .../model/ship/ComponentDeserializer.java | 24 ++++ .../model/ship/GeometricComponent.java | 15 +++ .../starship_arena/model/ship/Gun.java | 16 +++ .../starship_arena/model/ship/Panel.java | 8 ++ .../model/ship/ShipComponent.java | 27 +++++ .../starship_arena/util/ResourceUtils.java | 21 ++++ .../starship_arena/view/ArenaPanel.java | 64 ++++++++++ .../starship_arena/view/ArenaWindow.java | 39 ++++++ .../andrewl/starship_arena/view/Renderer.java | 7 ++ .../starship_arena/view/ShipRenderer.java | 31 +++++ src/main/resources/img/icon.png | Bin 0 -> 17266 bytes src/main/resources/ships/corvette.json | 52 ++++++++ 22 files changed, 740 insertions(+) create mode 100644 .gitignore create mode 100644 design/icon.svg create mode 100644 pom.xml create mode 100644 src/main/java/nl/andrewl/starship_arena/StarshipArena.java create mode 100644 src/main/java/nl/andrewl/starship_arena/model/Arena.java create mode 100644 src/main/java/nl/andrewl/starship_arena/model/Camera.java create mode 100644 src/main/java/nl/andrewl/starship_arena/model/PhysicsObject.java create mode 100644 src/main/java/nl/andrewl/starship_arena/model/Ship.java create mode 100644 src/main/java/nl/andrewl/starship_arena/model/ShipModel.java create mode 100644 src/main/java/nl/andrewl/starship_arena/model/ship/Cockpit.java create mode 100644 src/main/java/nl/andrewl/starship_arena/model/ship/ComponentDeserializer.java create mode 100644 src/main/java/nl/andrewl/starship_arena/model/ship/GeometricComponent.java create mode 100644 src/main/java/nl/andrewl/starship_arena/model/ship/Gun.java create mode 100644 src/main/java/nl/andrewl/starship_arena/model/ship/Panel.java create mode 100644 src/main/java/nl/andrewl/starship_arena/model/ship/ShipComponent.java create mode 100644 src/main/java/nl/andrewl/starship_arena/util/ResourceUtils.java create mode 100644 src/main/java/nl/andrewl/starship_arena/view/ArenaPanel.java create mode 100644 src/main/java/nl/andrewl/starship_arena/view/ArenaWindow.java create mode 100644 src/main/java/nl/andrewl/starship_arena/view/Renderer.java create mode 100644 src/main/java/nl/andrewl/starship_arena/view/ShipRenderer.java create mode 100644 src/main/resources/img/icon.png create mode 100644 src/main/resources/ships/corvette.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b4a5277 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +target/ +*.iml diff --git a/design/icon.svg b/design/icon.svg new file mode 100644 index 0000000..a1f0762 --- /dev/null +++ b/design/icon.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1af812c --- /dev/null +++ b/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + nl.andrewl + starship-arena + 1.0.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + + + com.google.code.gson + gson + 2.9.0 + + + + + + + maven-assembly-plugin + + + + nl.andrewl.starship_arena.StarshipArena + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/src/main/java/nl/andrewl/starship_arena/StarshipArena.java b/src/main/java/nl/andrewl/starship_arena/StarshipArena.java new file mode 100644 index 0000000..08b94a6 --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/StarshipArena.java @@ -0,0 +1,21 @@ +package nl.andrewl.starship_arena; + +import nl.andrewl.starship_arena.model.Arena; +import nl.andrewl.starship_arena.model.Ship; +import nl.andrewl.starship_arena.model.ShipModel; +import nl.andrewl.starship_arena.util.ResourceUtils; +import nl.andrewl.starship_arena.view.ArenaWindow; + +/** + * The main executable class which starts the program. + */ +public class StarshipArena { + public static void main(String[] args) { + ShipModel corvette = ShipModel.load(ResourceUtils.getString("/ships/corvette.json")); + Ship s = new Ship(corvette); + Arena arena = new Arena(); + arena.getShips().add(s); + var window = new ArenaWindow(arena); + window.setVisible(true); + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/model/Arena.java b/src/main/java/nl/andrewl/starship_arena/model/Arena.java new file mode 100644 index 0000000..2dd240e --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/model/Arena.java @@ -0,0 +1,21 @@ +package nl.andrewl.starship_arena.model; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Represents the top-level model containing all objects in an arena that can + * interact with each other. + */ +public class Arena { + private final Collection ships = new ArrayList<>(); + private final Camera camera = new Camera(); + + public Collection getShips() { + return ships; + } + + public Camera getCamera() { + return camera; + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/model/Camera.java b/src/main/java/nl/andrewl/starship_arena/model/Camera.java new file mode 100644 index 0000000..827c3c4 --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/model/Camera.java @@ -0,0 +1,37 @@ +package nl.andrewl.starship_arena.model; + +import java.awt.geom.Point2D; + +public class Camera { + public static final double SCALE_INTERVAL = 50.0; + + private Object focus; + + private Point2D.Float position = new Point2D.Float(); + + private int scaleIncrement = 1; + + public Object getFocus() { + return focus; + } + + public void setFocus(Object focus) { + this.focus = focus; + } + + public Point2D.Float getPosition() { + return position; + } + + public void setPosition(Point2D.Float position) { + this.position = position; + } + + public int getScaleIncrement() { + return scaleIncrement; + } + + public void setScaleIncrement(int scaleIncrement) { + this.scaleIncrement = scaleIncrement; + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/model/PhysicsObject.java b/src/main/java/nl/andrewl/starship_arena/model/PhysicsObject.java new file mode 100644 index 0000000..fa4b73d --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/model/PhysicsObject.java @@ -0,0 +1,58 @@ +package nl.andrewl.starship_arena.model; + +import java.awt.geom.Point2D; + +public class PhysicsObject { + /** + * The position of this object in the scene, in meters from the origin. + * Positive x-axis goes to the right, and positive y-axis goes down. + */ + private final Point2D.Float position = new Point2D.Float(); + + /** + * The object's rotation in radians, from 0 to 2 PI. + */ + private float rotation; + + /** + * The object's velocity, in meters per second. + */ + private final Point2D.Float velocity = new Point2D.Float(); + + /** + * The object's rotational speed, in radians per second. + */ + private float rotationSpeed; + + public Point2D.Float getPosition() { + return position; + } + + public float getRotation() { + return rotation; + } + + public void setRotation(float rotation) { + while (rotation < 0) rotation += 2 * Math.PI; + while (rotation > 2 * Math.PI) rotation -= 2 * Math.PI; + this.rotation = rotation; + } + + public Point2D.Float getVelocity() { + return velocity; + } + + public float getRotationSpeed() { + return rotationSpeed; + } + + public void setRotationSpeed(float rotationSpeed) { + this.rotationSpeed = rotationSpeed; + } + + public void update(double delta) { + position.x += velocity.x * delta; + position.y += velocity.y * delta; + setRotation((float) (rotation + rotationSpeed * delta)); + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/model/Ship.java b/src/main/java/nl/andrewl/starship_arena/model/Ship.java new file mode 100644 index 0000000..347190e --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/model/Ship.java @@ -0,0 +1,56 @@ +package nl.andrewl.starship_arena.model; + +import nl.andrewl.starship_arena.model.ship.Gun; +import nl.andrewl.starship_arena.model.ship.Panel; +import nl.andrewl.starship_arena.model.ship.ShipComponent; +import nl.andrewl.starship_arena.util.ResourceUtils; + +import java.awt.*; +import java.util.ArrayList; +import java.util.Collection; + +public class Ship extends PhysicsObject { + private final String modelName; + private final Collection components; + + private final Collection panels; + private final Collection guns; + + private Color primaryColor = Color.GRAY; + + public Ship(ShipModel model) { + this.modelName = model.getName(); + this.components = model.getComponents(); + this.panels = new ArrayList<>(); + this.guns = new ArrayList<>(); + for (var c : components) { + c.setShip(this); + if (c instanceof Panel p) panels.add(p); + if (c instanceof Gun g) guns.add(g); + } + } + + public Ship(String modelResource) { + this(ShipModel.load(ResourceUtils.getString(modelResource))); + } + + public String getModelName() { + return modelName; + } + + public Collection getComponents() { + return components; + } + + public Color getPrimaryColor() { + return primaryColor; + } + + public float getMass() { + float m = 0; + for (var c : components) { + m += c.getMass(); + } + return m; + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/model/ShipModel.java b/src/main/java/nl/andrewl/starship_arena/model/ShipModel.java new file mode 100644 index 0000000..39db86f --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/model/ShipModel.java @@ -0,0 +1,70 @@ +package nl.andrewl.starship_arena.model; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import nl.andrewl.starship_arena.model.ship.ComponentDeserializer; +import nl.andrewl.starship_arena.model.ship.GeometricComponent; +import nl.andrewl.starship_arena.model.ship.Gun; +import nl.andrewl.starship_arena.model.ship.ShipComponent; + +import java.util.Collection; + +public class ShipModel { + private String name; + private Collection components; + + public String getName() { + return name; + } + + public Collection getComponents() { + return components; + } + + /** + * Normalizes the geometric properties of the components such that the ship + * model's components are centered around (0, 0). + * TODO: Consider scaling? + */ + private void normalizeComponents() { + float minX = Float.MAX_VALUE; + float maxX = Float.MIN_VALUE; + float minY = Float.MAX_VALUE; + float maxY = Float.MIN_VALUE; + for (var c : components) { + if (c instanceof GeometricComponent g) { + for (var p : g.getPoints()) { + minX = Math.min(minX, p.x); + maxX = Math.max(maxX, p.x); + minY = Math.min(minY, p.y); + maxY = Math.max(maxY, p.y); + } + } + } + final float width = maxX - minX; + final float height = maxY - minY; + final float offsetX = -minX - width / 2; + final float offsetY = -minY - height / 2; + // Shift all components to the top-left. + for (var c : components) { + if (c instanceof GeometricComponent g) { + for (var p : g.getPoints()) { + p.x += offsetX; + p.y += offsetY; + } + } else if (c instanceof Gun g) { + g.getLocation().x += offsetX; + g.getLocation().y += offsetY; + } + } + } + + public static ShipModel load(String json) { + Gson gson = new GsonBuilder() + .registerTypeAdapter(ShipComponent.class, new ComponentDeserializer()) + .create(); + ShipModel model = gson.fromJson(json, ShipModel.class); + model.normalizeComponents(); + return model; + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/model/ship/Cockpit.java b/src/main/java/nl/andrewl/starship_arena/model/ship/Cockpit.java new file mode 100644 index 0000000..716d20e --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/model/ship/Cockpit.java @@ -0,0 +1,7 @@ +package nl.andrewl.starship_arena.model.ship; + +/** + * A cockpit represents the control point of the ship. + */ +public class Cockpit extends GeometricComponent { +} diff --git a/src/main/java/nl/andrewl/starship_arena/model/ship/ComponentDeserializer.java b/src/main/java/nl/andrewl/starship_arena/model/ship/ComponentDeserializer.java new file mode 100644 index 0000000..8b30dd6 --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/model/ship/ComponentDeserializer.java @@ -0,0 +1,24 @@ +package nl.andrewl.starship_arena.model.ship; + +import com.google.gson.*; + +import java.lang.reflect.Type; + +/** + * Custom deserializer that's used to deserialize components based on their + * "type" property. + */ +public class ComponentDeserializer implements JsonDeserializer { + @Override + public ShipComponent deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext ctx) throws JsonParseException { + JsonObject obj = jsonElement.getAsJsonObject(); + String componentTypeName = obj.get("type").getAsString(); + Type componentType = switch (componentTypeName) { + case "panel" -> Panel.class; + case "cockpit" -> Cockpit.class; + case "gun" -> Gun.class; + default -> throw new JsonParseException("Invalid ship component type: " + componentTypeName); + }; + return ctx.deserialize(obj, componentType); + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/model/ship/GeometricComponent.java b/src/main/java/nl/andrewl/starship_arena/model/ship/GeometricComponent.java new file mode 100644 index 0000000..20eefc7 --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/model/ship/GeometricComponent.java @@ -0,0 +1,15 @@ +package nl.andrewl.starship_arena.model.ship; + +import java.awt.geom.Point2D; +import java.util.List; + +/** + * Represents a component of a ship that can be drawn as a geometric primitive. + */ +public abstract class GeometricComponent extends ShipComponent { + private List points; + + public List getPoints() { + return points; + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/model/ship/Gun.java b/src/main/java/nl/andrewl/starship_arena/model/ship/Gun.java new file mode 100644 index 0000000..49231f2 --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/model/ship/Gun.java @@ -0,0 +1,16 @@ +package nl.andrewl.starship_arena.model.ship; + +import java.awt.geom.Point2D; + +public class Gun extends ShipComponent { + private String name; + private Point2D.Float location; + + public String getName() { + return name; + } + + public Point2D.Float getLocation() { + return location; + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/model/ship/Panel.java b/src/main/java/nl/andrewl/starship_arena/model/ship/Panel.java new file mode 100644 index 0000000..d360e00 --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/model/ship/Panel.java @@ -0,0 +1,8 @@ +package nl.andrewl.starship_arena.model.ship; + +/** + * A simple structural panel that makes up all or part of a ship's body. + */ +public class Panel extends GeometricComponent { + private String name; +} diff --git a/src/main/java/nl/andrewl/starship_arena/model/ship/ShipComponent.java b/src/main/java/nl/andrewl/starship_arena/model/ship/ShipComponent.java new file mode 100644 index 0000000..852161f --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/model/ship/ShipComponent.java @@ -0,0 +1,27 @@ +package nl.andrewl.starship_arena.model.ship; + +import nl.andrewl.starship_arena.model.Ship; + +/** + * Represents the top-level component information for any part of a ship. + */ +public class ShipComponent { + /** + * The ship that this component belongs to. + */ + private transient Ship ship; + + private float mass; + + public Ship getShip() { + return ship; + } + + public void setShip(Ship ship) { + this.ship = ship; + } + + public float getMass() { + return mass; + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/util/ResourceUtils.java b/src/main/java/nl/andrewl/starship_arena/util/ResourceUtils.java new file mode 100644 index 0000000..6d30fb0 --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/util/ResourceUtils.java @@ -0,0 +1,21 @@ +package nl.andrewl.starship_arena.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; + +public class ResourceUtils { + public static InputStream get(String name) { + InputStream in = ResourceUtils.class.getResourceAsStream(name); + if (in == null) throw new UncheckedIOException(new IOException("Could not load resource: " + name)); + return in; + } + + public static String getString(String name) { + try (var in = get(name)) { + return new String(in.readAllBytes()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/view/ArenaPanel.java b/src/main/java/nl/andrewl/starship_arena/view/ArenaPanel.java new file mode 100644 index 0000000..a151877 --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/view/ArenaPanel.java @@ -0,0 +1,64 @@ +package nl.andrewl.starship_arena.view; + +import nl.andrewl.starship_arena.model.Arena; +import nl.andrewl.starship_arena.model.Camera; + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.AffineTransform; + +public class ArenaPanel extends JPanel { + private final Arena arena; + + private final ShipRenderer shipRenderer = new ShipRenderer(); + + public ArenaPanel(Arena arena) { + this.arena = arena; + this.addMouseWheelListener(e -> { + arena.getCamera().setScaleIncrement(arena.getCamera().getScaleIncrement() + e.getWheelRotation()); + repaint(); + }); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + g2.setColor(Color.BLACK); + g2.fillRect(0, 0, getWidth(), getHeight()); + + AffineTransform originalTx = g2.getTransform(); + AffineTransform tx = new AffineTransform(); + + Camera cam = arena.getCamera(); + + double translateX = (double) getWidth() / 2; + double translateY = (double) getHeight() / 2; + if (cam.getFocus() == null) { + translateX += cam.getPosition().x; + translateY += cam.getPosition().y; + } + + double scale = 1 * Camera.SCALE_INTERVAL; + if (cam.getScaleIncrement() > 0) { + scale = cam.getScaleIncrement() * Camera.SCALE_INTERVAL; + } else if (cam.getScaleIncrement() < 0) { + scale = 1.0 / Math.abs(cam.getScaleIncrement() * Camera.SCALE_INTERVAL); + } + + tx.translate(translateX, translateY); + tx.scale(scale, scale); + g2.setTransform(tx); + + for (var s : arena.getShips()) { + shipRenderer.render(s, g2); + } + + g2.setTransform(originalTx); + + g2.setColor(Color.GREEN); + g2.fillRect(0, 0, 20, 20); + g2.setColor(Color.BLUE); + g2.fillRect(getWidth() - 20, getHeight() - 20, 20, 20); + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/view/ArenaWindow.java b/src/main/java/nl/andrewl/starship_arena/view/ArenaWindow.java new file mode 100644 index 0000000..ca3e92b --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/view/ArenaWindow.java @@ -0,0 +1,39 @@ +package nl.andrewl.starship_arena.view; + +import nl.andrewl.starship_arena.model.Arena; +import nl.andrewl.starship_arena.util.ResourceUtils; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.io.IOException; +import java.io.InputStream; + +public class ArenaWindow extends JFrame { + private final ArenaPanel arenaPanel; + + public ArenaWindow(Arena arena) { + super("Starship Arena"); + setDefaultCloseOperation(EXIT_ON_CLOSE); + setUndecorated(true); + try { + GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + device.setFullScreenWindow(this); + setPreferredSize(new Dimension(device.getDisplayMode().getWidth(), device.getDisplayMode().getHeight())); + } catch (HeadlessException e) { + System.err.println("Cannot start the program on systems without a screen."); + System.exit(1); + } + try { + InputStream in = ResourceUtils.get("/img/icon.png"); + setIconImage(ImageIO.read(in)); + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + arenaPanel = new ArenaPanel(arena); + add(arenaPanel); + pack(); + } +} diff --git a/src/main/java/nl/andrewl/starship_arena/view/Renderer.java b/src/main/java/nl/andrewl/starship_arena/view/Renderer.java new file mode 100644 index 0000000..1f30f88 --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/view/Renderer.java @@ -0,0 +1,7 @@ +package nl.andrewl.starship_arena.view; + +import java.awt.*; + +public interface Renderer { + void render(T obj, Graphics2D g); +} diff --git a/src/main/java/nl/andrewl/starship_arena/view/ShipRenderer.java b/src/main/java/nl/andrewl/starship_arena/view/ShipRenderer.java new file mode 100644 index 0000000..d17b085 --- /dev/null +++ b/src/main/java/nl/andrewl/starship_arena/view/ShipRenderer.java @@ -0,0 +1,31 @@ +package nl.andrewl.starship_arena.view; + +import nl.andrewl.starship_arena.model.Ship; +import nl.andrewl.starship_arena.model.ship.Cockpit; +import nl.andrewl.starship_arena.model.ship.GeometricComponent; + +import java.awt.*; +import java.awt.geom.Path2D; + +public class ShipRenderer implements Renderer { + @Override + public void render(Ship ship, Graphics2D g) { + for (var c : ship.getComponents()) { + if (c instanceof GeometricComponent geo) { + Path2D.Float path = new Path2D.Float(); + var first = geo.getPoints().get(0); + path.moveTo(first.x, first.y); + for (int i = 0; i < geo.getPoints().size(); i++) { + var point = geo.getPoints().get(i); + path.lineTo(point.x, point.y); + } + if (geo instanceof Cockpit) { + g.setColor(new Color(0f, 0f, 0.5f, 0.5f)); + } else { + g.setColor(ship.getPrimaryColor()); + } + g.fill(path); + } + } + } +} diff --git a/src/main/resources/img/icon.png b/src/main/resources/img/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b21f0b71273691dc3515cb821a0dce4417147689 GIT binary patch literal 17266 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/ships/corvette.json b/src/main/resources/ships/corvette.json new file mode 100644 index 0000000..f7270ab --- /dev/null +++ b/src/main/resources/ships/corvette.json @@ -0,0 +1,52 @@ +{ + "name": "Corvette", + "components": [ + { + "type": "panel", + "name": "Main Fuselage", + "mass": 5000, + "points": [ + {"x": 0.3, "y": 0.6}, + {"x": 0.2, "y": 0.1}, + {"x": 0.1, "y": 0.5}, + {"x": 0.2, "y": 0.8}, + {"x": 0.8, "y": 0.8}, + {"x": 0.9, "y": 0.5}, + {"x": 0.8, "y": 0.1}, + {"x": 0.7, "y": 0.6} + ] + }, + { + "type": "panel", + "name": "Front Cargo Bay", + "mass": 1000, + "points": [ + {"x": 0.4, "y": 0.2}, + {"x": 0.35, "y": 0.6}, + {"x": 0.65, "y": 0.6}, + {"x": 0.6, "y": 0.2} + ] + }, + { + "type": "cockpit", + "mass": 800, + "points": [ + {"x": 0.5, "y": 0.0}, + {"x": 0.4, "y": 0.2}, + {"x": 0.6, "y": 0.2} + ] + }, + { + "type": "gun", + "name": "Port-Side Machine Gun", + "mass": 500, + "location": {"x": 0.15, "y": 0.4} + }, + { + "type": "gun", + "name": "Starboard-Side Machine Gun", + "mass": 500, + "location": {"x": 0.85, "y": 0.4} + } + ] +} \ No newline at end of file