diff --git a/app/js/controllers/controller-outfit.js b/app/js/controllers/controller-outfit.js index 147fca76..fc123275 100755 --- a/app/js/controllers/controller-outfit.js +++ b/app/js/controllers/controller-outfit.js @@ -211,6 +211,24 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', ' } }; + /** + * Export the build to detailed JSON + */ + $scope.exportBuild = function(e) { + e.stopPropagation(); + + if ($scope.buildName) { + $state.go('modal.export', { + data: Serializer.toJsonBuild( + $scope.buildName, + ship, + $state.href($state.current.name, $state.params, { absolute: true }), + $scope.code || Serializer.fromShip(ship) + ) + }); + } + }; + /** * Permanently delete the current build and redirect/reload this controller * with the 'factory' build of the current ship. diff --git a/app/js/service-serializer.js b/app/js/service-serializer.js index a17ffc4d..3b84ad9a 100755 --- a/app/js/service-serializer.js +++ b/app/js/service-serializer.js @@ -1,7 +1,7 @@ /** * Service managing seralization and deserialization of models for use in URLs and persistene. */ -angular.module('app').service('Serializer', ['lodash', function(_) { +angular.module('app').service('Serializer', ['lodash', 'GroupMap', 'MountMap', function(_, GroupMap, MountMap) { /** * Serializes the ships selected components for all slots to a URL friendly string. @@ -32,8 +32,8 @@ angular.module('app').service('Serializer', ['lodash', function(_) { * Updates an existing ship instance's slots with components determined by the * code. * - * @param {Ship} ship The ship instance to be updated - * @param {string} code The string to deserialize + * @param {Ship} ship The ship instance to be updated + * @param {string} dataString The string to deserialize */ this.toShip = function(ship, dataString) { var common = new Array(ship.common.length), @@ -54,10 +54,6 @@ angular.module('app').service('Serializer', ['lodash', function(_) { decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, common, 1))); - // get the remaining substring / split into parts for - // - priorities - // - enabled/disabled - ship.buildWith( { bulkheads: code.charAt(0) * 1, @@ -70,6 +66,48 @@ angular.module('app').service('Serializer', ['lodash', function(_) { ); }; + this.toJsonBuild = function(buildName, ship, url, code) { + var standard = ship.common, + hardpoints = ship.hardpoints, + internal = ship.internal; + + var data = { + $schema: 'http://cdn.coriolis.io/schemas/ship-loadout/1-draft.json#', + name: buildName, + ship: ship.name, + references: [{ + name: 'Coriolis.io', + url: url, + code: code, + shipId: ship.id + }], + components: { + standard: { + bulkheads: ship.bulkheads.c.name, + powerPlant: { class: standard[0].c.class, rating: standard[0].c.rating }, + thrusters: { class: standard[1].c.class, rating: standard[1].c.rating }, + frameShiftDrive: { class: standard[2].c.class, rating: standard[2].c.rating }, + lifeSupport: { class: standard[3].c.class, rating: standard[3].c.rating }, + powerDistributor: { class: standard[4].c.class, rating: standard[4].c.rating }, + sensors: { class: standard[5].c.class, rating: standard[5].c.rating }, + fuelTank: { class: standard[6].c.class, rating: standard[6].c.rating } + }, + hardpoints: _.map(_.filter(hardpoints, function(slot) { return slot.maxClass > 0; }), slotToSchema), + utility: _.map(_.filter(hardpoints, function(slot) { return slot.maxClass === 0; }), slotToSchema), + internal: _.map(internal, slotToSchema) + }, + stats: {} + }; + + for (var stat in ship) { + if (!isNaN(ship[stat])) { + data.stats[stat] = Math.round(ship[stat] * 100) / 100; + } + } + + return data; + }; + this.fromComparison = function(name, builds, facets, predicate, desc) { var shipBuilds = []; @@ -118,4 +156,19 @@ angular.module('app').service('Serializer', ['lodash', function(_) { return codePos; } + function slotToSchema(slot) { + if (slot.c) { + var o = { class: slot.c.class, rating: slot.c.rating, group: GroupMap[slot.c.grp] }; + if (slot.c.name) { + o.name = slot.c.name; + } + if (slot.c.mode) { + o.mount = MountMap[slot.c.mode]; + } + return o; + } + return null; + } + + }]); diff --git a/app/js/shipyard/factory-component-set.js b/app/js/shipyard/factory-component-set.js index 15aeca64..59009639 100755 --- a/app/js/shipyard/factory-component-set.js +++ b/app/js/shipyard/factory-component-set.js @@ -14,26 +14,27 @@ angular.module('shipyard').factory('ComponentSet', ['lodash', function(_) { this.hpClass = {}; this.intClass = {}; - for (var i = 0; i < components.common.length; i++) { - var max = maxCommonArr[i]; - switch (i) { - // Slots where component class must be equal to slot class - case 3: // Life Support - case 5: // Sensors - this.common[i] = filter(components.common[i], max, max, this.mass); - break; - // Other slots can have a component of class lower than the slot class - default: - this.common[i] = filter(components.common[i], max, 0, this.mass); - } - } + this.common[0] = filter(components.common[0], maxCommonArr[0], 0, mass); // Power Plant + this.common[2] = filter(components.common[2], maxCommonArr[2], 0, mass); // FSD + this.common[4] = filter(components.common[4], maxCommonArr[4], 0, mass); // Power Distributor + this.common[6] = filter(components.common[6], maxCommonArr[6], 0, mass); // Fuel Tank + + // Thrusters, filter components by class only (to show full list of ratings for that class) + var minThrusterClass = _.reduce(components.common[1], function(minClass, thruster) { + return (thruster.maxmass >= mass && thruster.class < minClass) ? thruster.class : minClass; + }, maxCommonArr[1]); + this.common[1] = filter(components.common[1], maxCommonArr[1], minThrusterClass, 0); // Thrusters + + // Slots where component class must be equal to slot class + this.common[3] = filter(components.common[3], maxCommonArr[3], maxCommonArr[3], 0); // Life Supprt + this.common[5] = filter(components.common[5], maxCommonArr[5], maxCommonArr[5], mass); // Sensors for (var h in components.hardpoints) { - this.hardpoints[h] = filter(components.hardpoints[h], maxHardPoint, 0, this.mass); + this.hardpoints[h] = filter(components.hardpoints[h], maxHardPoint, 0, mass); } for (var g in components.internal) { - this.internal[g] = filter(components.internal[g], maxInternal, 0, this.mass); + this.internal[g] = filter(components.internal[g], maxInternal, 0, mass); } } diff --git a/app/js/shipyard/factory-ship.js b/app/js/shipyard/factory-ship.js index fc8401f9..d3be8a14 100755 --- a/app/js/shipyard/factory-ship.js +++ b/app/js/shipyard/factory-ship.js @@ -40,7 +40,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', } } // Make a Ship 'slot'/item similar to other slots - this.c = { incCost: true, type: 'SHIP', discountedCost: this.cost, c: { name: this.name, cost: this.cost } }; + this.c = { incCost: true, type: 'SHIP', discountedCost: this.hullCost, c: { name: this.name, cost: this.hullCost } }; this.costList = _.union(this.internal, this.common, this.hardpoints); this.costList.push(this.bulkheads); // Add The bulkheads @@ -55,8 +55,8 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', this.powerList.unshift(this.common[2]); // Add FSD this.powerList.unshift(this.common[0]); // Add Power Plant - this.shipDiscount = 1; - this.componentDiscount = 1; + this.shipCostMultiplier = 1; + this.componentCostMultiplier = 1; this.priorityBands = [ { deployed: 0, retracted: 0, retOnly: 0 }, @@ -86,8 +86,8 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', this.armourAdded = 0; this.shieldMultiplier = 1; this.totalCost = this.c.incCost ? this.c.discountedCost : 0; - this.unladenMass = this.mass; - this.armourTotal = this.armour; + this.unladenMass = this.hullMass; + this.armour = this.baseArmour; this.totalDps = 0; this.bulkheads.c = null; @@ -157,7 +157,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', var oldBulkhead = this.bulkheads.c; this.bulkheads.id = index; this.bulkheads.c = Components.bulkheads(this.id, index); - this.bulkheads.discountedCost = this.bulkheads.c.cost * this.componentDiscount; + this.bulkheads.discountedCost = this.bulkheads.c.cost * this.componentCostMultiplier; this.updateStats(this.bulkheads, this.bulkheads.c, oldBulkhead, preventUpdate); }; @@ -186,7 +186,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', var oldComponent = slot.c; slot.id = id; slot.c = component; - slot.discountedCost = (component && component.cost) ? component.cost * this.componentDiscount : 0; + slot.discountedCost = (component && component.cost) ? component.cost * this.componentCostMultiplier : 0; this.updateStats(slot, component, oldComponent, preventUpdate); } }; @@ -313,7 +313,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', } if (slot.incCost && old.cost) { - this.totalCost -= old.cost * this.componentDiscount; + this.totalCost -= old.cost * this.componentCostMultiplier; } if (old.power && slot.enabled) { @@ -335,9 +335,6 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', case 'cr': this.cargoCapacity += n.capacity; break; - case 't': - this.maxMass = n.maxmass; - break; case 'hr': this.armourAdded += n.armouradd; break; @@ -347,7 +344,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', } if (slot.incCost && n.cost) { - this.totalCost += n.cost * this.componentDiscount; + this.totalCost += n.cost * this.componentCostMultiplier; } if (n.power && slot.enabled) { @@ -362,7 +359,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', } this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity; - this.armourTotal = this.armourAdded + this.armour; + this.armour = this.armourAdded + this.baseArmour; if (!preventUpdate) { if (powerChange) { @@ -390,7 +387,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', Ship.prototype.updateShieldStrength = function() { var sgSlot = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any - this.shieldStrength = sgSlot && sgSlot.enabled ? calcShieldStrength(this.mass, this.shields, sgSlot.c, this.shieldMultiplier) : 0; + this.shieldStrength = sgSlot && sgSlot.enabled ? calcShieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.c, this.shieldMultiplier) : 0; }; /** @@ -408,24 +405,24 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', /** * Recalculate all item costs and total based on discounts. - * @param {number} shipDiscount Ship cost multiplier discount (e.g. 0.9 === 10% discount) - * @param {number} componentDiscount Component cost multiplier discount (e.g. 0.75 === 25% discount) + * @param {number} shipCostMultiplier Ship cost multiplier discount (e.g. 0.9 === 10% discount) + * @param {number} componentCostMultiplier Component cost multiplier discount (e.g. 0.75 === 25% discount) */ - Ship.prototype.applyDiscounts = function(shipDiscount, componentDiscount) { + Ship.prototype.applyDiscounts = function(shipCostMultiplier, componentCostMultiplier) { var total = 0; var costList = this.costList; for (var i = 0, l = costList.length; i < l; i++) { var item = costList[i]; if (item.c && item.c.cost) { - item.discountedCost = item.c.cost * (item.type == 'SHIP' ? shipDiscount : componentDiscount); + item.discountedCost = item.c.cost * (item.type == 'SHIP' ? shipCostMultiplier : componentCostMultiplier); if (item.incCost) { total += item.discountedCost; } } } - this.shipDiscount = shipDiscount; - this.componentDiscount = componentDiscount; + this.shipCostMultiplier = shipCostMultiplier; + this.componentCostMultiplier = componentCostMultiplier; this.totalCost = total; }; diff --git a/app/js/shipyard/module-shipyard.js b/app/js/shipyard/module-shipyard.js index 368e5f78..30585261 100755 --- a/app/js/shipyard/module-shipyard.js +++ b/app/js/shipyard/module-shipyard.js @@ -66,6 +66,11 @@ angular.module('shipyard', ['ngLodash']) sb: 'Shield Booster', tp: 'Torpedo Pylon' }) + .value('MountMap', { + 'F': 'Fixed', + 'G': 'Gimballed', + 'T': 'Turret' + }) .value('shipSize', [ 'N/A', 'Small', @@ -102,7 +107,7 @@ angular.module('shipyard', ['ngLodash']) }, { // 2 title: 'Armour', - props: ['armourTotal'], + props: ['armour'], unit: '', fmt: 'fCrd' }, diff --git a/app/js/shipyard/service-components.js b/app/js/shipyard/service-components.js index 05a8f202..de59dc2a 100755 --- a/app/js/shipyard/service-components.js +++ b/app/js/shipyard/service-components.js @@ -51,7 +51,7 @@ angular.module('shipyard').service('Components', ['lodash', 'ComponentsDB', 'Shi */ this.forShip = function(shipId) { var ship = Ships[shipId]; - return new ComponentSet(C, ship.properties.mass + 5, ship.slots.common, ship.slots.internal[0], ship.slots.hardpoints[0]); + return new ComponentSet(C, ship.minMassFilter || ship.properties.hullMass + 5, ship.slots.common, ship.slots.internal[0], ship.slots.hardpoints[0]); }; }]); diff --git a/app/schemas/ship-loadout/1-draft.json b/app/schemas/ship-loadout/1-draft.json new file mode 100644 index 00000000..c5dd2a00 --- /dev/null +++ b/app/schemas/ship-loadout/1-draft.json @@ -0,0 +1,287 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://cdn.coriolis.io/schemas/ship-loadout/1-draft.json#", + "title": "Ship Loadout", + "type": "object", + "description": "The details for a specific ship build/loadout", + "required": ["name", "ship", "components"], + "properties": { + "name": { + "description": "The name of the build/loadout", + "type": "string", + "minLength": 2 + }, + "ship": { + "description": "The full display name of the ship", + "type": "string", + "minimum": 3 + }, + "manufacturer": { + "description": "The ship manufacturer", + "type": "string" + }, + "references" : { + "description": "3rd Party references and/or links to this build/loadout", + "type": "array", + "items": { + "type": "object", + "required": ["name","url"], + "properties": { + "name": { + "description": "The name of the 3rd party, .e.g 'Coriolis.io' or 'E:D Shipyard'", + "type": "string" + }, + "url": { + "description": "The link/url to the 3rd party referencing this build/loadout", + "type": "string" + }, + "shipId": { + "type": "string" + }, + "code": { + "description": "The serialized code or string for the build/loadout", + "type": "string" + } + } + } + }, + "components": { + "description": "The components used by this build", + "type": "object", + "additionalProperties": false, + "required": ["standard", "internal", "hardpoints", "utility"], + "properties": { + "standard": { + "description": "The set of standard components across all ships", + "type": "object", + "additionalProperties": false, + "required": ["bulkheads", "powerPlant", "thrusters", "frameShiftDrive", "lifeSupport", "powerDistributor", "sensors", "fuelTank"], + "properties": { + "bulkheads": { + "enum": ["Lightweight Alloy", "Reinforced Alloy", "Military Grade Composite", "Mirrored Surface Composite", "Reactive Surface Composite"] + }, + "powerPlant": { + "required": ["class", "rating"], + "properties": { + "class": { "type": "integer", "minimum": 2, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" } + } + }, + "thrusters": { + "required": ["class", "rating"], + "properties": { + "class": { "type": "integer", "minimum": 2, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" } + } + }, + "frameShiftDrive": { + "required": ["class", "rating"], + "properties": { + "class": { "type": "integer", "minimum": 2, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" } + } + }, + "lifeSupport": { + "required": ["class", "rating"], + "properties": { + "class": { "type": "integer", "minimum": 1, "maximum": 6 }, + "rating": { "$ref": "#/definitions/standardRatings" } + } + }, + "powerDistributor": { + "required": ["class", "rating"], + "properties": { + "class": { "type": "integer", "minimum": 1, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" } + } + }, + "sensors": { + "required": ["class", "rating"], + "properties": { + "class": { "type": "integer", "minimum": 1, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" } + } + }, + "fuelTank": { + "required": ["class", "rating"], + "properties": { + "class": { "type": "integer", "minimum": 1, "maximum": 6 }, + "rating": { "$ref": "#/definitions/standardRatings" } + } + } + } + }, + "internal": { + "type": "array", + "items": { + "type": ["object", "null"], + "required": ["class", "rating", "group"], + "properties" : { + "class": { "type": "integer", "minimum": 1, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" }, + "group": { + "description": "The group of the component, e.g. 'Shield Generator', or 'Cargo Rack'", + "type": "string" + }, + "name": { + "description": "The name identifying the component (if applicable), e.g. 'Advance Discovery Scanner', or 'Detailed Surface Scanner'", + "type": "string" + } + } + }, + "minItems": 3 + }, + "hardpoints": { + "type": "array", + "items": { + "type": ["object", "null"], + "required": ["class", "rating", "group", "mount"], + "properties" : { + "class": { "type": "integer", "minimum": 1, "maximum": 4 }, + "rating": { "$ref": "#/definitions/allRatings" }, + "mount": { "type": "string", "enum": ["Fixed", "Gimballed", "Turret"] }, + "group": { + "description": "The group of the component, e.g. 'Beam Laser', or 'Missile Rack'", + "type": "string" + }, + "name": { + "description": "The name identifing the component (if applicable), e.g. 'Retributor', or 'Mining Lance'", + "type": "string" + } + } + }, + "minItems": 1 + }, + "utility": { + "type": "array", + "items": { + "type": ["object", "null"], + "required": ["class", "rating", "group"], + "properties" : { + "class": { "type": "integer", "minimum": 0, "maximum": 0 }, + "rating": { "$ref": "#/definitions/allRatings" }, + "group": { + "description": "The group of the component, e.g. 'Shield Booster', or 'Kill Warrant Scanner'", + "type": "string" + }, + "name": { + "description": "The name identifing the component (if applicable), e.g. 'Point Defence', or 'Electronic Countermeasure'", + "type": "string" + } + } + }, + "minItems": 1 + } + } + }, + "stats": { + "description": "Optional statistics from the build", + "type": "object", + "additionalProperties": true, + "properties": { + "agility": { + "type": "integer", + "minimum": 0 + }, + "armour": { + "description": "Sum of base armour + any hull reinforcements", + "type": "integer", + "minimum": 1 + }, + "armourAdded":{ + "description": "Armour added through Hull reinforcement", + "type": "integer", + "minimum": 1 + }, + "baseShieldStrength": { + "type": "integer", + "minimum": 1 + }, + "baseArmour": { + "type": "integer", + "minimum": 1 + }, + "boost": { + "description": "Maximum boost speed of the ships (4 pips, straight-line)", + "type": "number", + "minimum": 1 + }, + "cargoCapacity": { + "type": "integer", + "minimum": 0 + }, + "class": { + "description": "Ship Class/Size [Small, Medium, Large]", + "enum": [1,2,3] + }, + "dps": { + "description": "Cumulative DPS based on the in-game 1-10 statistic", + "type": "integer", + "minimum": 0 + }, + "hullCost": { + "description": "Cost of the ship's hull", + "type": "integer", + "minimum": 1 + }, + "hullMass": { + "description": "Mass of the Ship hull only", + "type": "number", + "minimum": 1 + }, + "fuelCapacity": { + "type": "integer", + "minimum": 1 + }, + "fullTankRange": { + "description": "Single Jump range with a full tank (unladenMass + fuel)", + "type": "number", + "minimum": 0 + }, + "ladenMass": { + "description": "Mass of the Ship + fuel + cargo (hull + all components + fuel tank + cargo capacity)", + "type": "number", + "minimum": 1 + }, + "ladenRange": { + "description": "Single Jump range with full cargo load, see ladenMass", + "type": "number", + "minimum": 0 + }, + "masslock": { + "description": "Mass Lock Factor of the Ship", + "type": "integer", + "minimum": 1 + }, + "shieldStrength": { + "description": "Shield strengh in Mega Joules (Mj)", + "type": "number", + "minimum": 0 + }, + "speed": { + "description": "Maximum speed of the ships (4 pips, straight-line)", + "type": "number", + "minimum": 1 + }, + "totalCost": { + "type": "integer", + "minimum": 1 + }, + "unladenRange": { + "description": "Single Jump range when unladen, see unladenMass", + "type": "number", + "minimum": 0 + }, + "unladenMass": { + "description": "Mass of the Ship (hull + all components)", + "type": "number", + "minimum": 1 + } + } + } + }, + "definitions": { + "standardRatings": { "enum": ["A", "B", "C", "D", "E"] }, + "allRatings": { "enum": ["A", "B", "C", "D", "E", "F", "I" ] } + } +} \ No newline at end of file diff --git a/app/views/page-outfit.html b/app/views/page-outfit.html index 27b862e2..bea2cd15 100644 --- a/app/views/page-outfit.html +++ b/app/views/page-outfit.html @@ -19,6 +19,9 @@ + @@ -59,9 +62,9 @@