diff --git a/app/js/app.js b/app/js/app.js
index ced40f8a..d85ecb32 100755
--- a/app/js/app.js
+++ b/app/js/app.js
@@ -1,5 +1,5 @@
angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'ui.sortable', 'shipyard', 'ngLodash', 'app.templates'])
-.run(['$rootScope', '$location', '$window', '$document','$state','commonArray','shipPurpose','shipSize','hardPointClass','internalGroupMap','hardpointsGroupMap', 'Persist', function ($rootScope, $location, $window, $doc, $state, CArr, shipPurpose, sz, hpc, igMap, hgMap, Persist) {
+.run(['$rootScope', '$location', '$window', '$document','$state','commonArray','shipPurpose','shipSize','hardPointClass','GroupMap', 'Persist', function ($rootScope, $location, $window, $doc, $state, CArr, shipPurpose, sz, hpc, GroupMap, Persist) {
// App is running as a standalone web app on tablet/mobile
var isStandAlone = $window.navigator.standalone || ($window.external && $window.external.msIsSiteMode && $window.external.msIsSiteMode());
@@ -31,10 +31,15 @@ angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'ui.sortable',
$rootScope.SP = shipPurpose;
$rootScope.SZ = sz;
$rootScope.HPC = hpc;
- $rootScope.igMap = igMap;
- $rootScope.hgMap = hgMap;
+ $rootScope.GMAP = GroupMap;
+ $rootScope.STATUS = ['','DISABLED', 'OFF', 'ON'];
+ $rootScope.STATUS_CLASS = ['','disabled', 'warning', 'secondary-disabled'];
$rootScope.title = 'Coriolis';
+ $rootScope.cName = function (c) {
+ return c.c? c.c.name? c.c.name : GroupMap[c.c.grp] : null;
+ }
+
// Formatters
$rootScope.fCrd = d3.format(',.0f');
$rootScope.fPwr = d3.format(',.2f');
diff --git a/app/js/controllers/controller-outfit.js b/app/js/controllers/controller-outfit.js
index 1d9e5ee8..cbb9ab04 100755
--- a/app/js/controllers/controller-outfit.js
+++ b/app/js/controllers/controller-outfit.js
@@ -1,6 +1,7 @@
-angular.module('app').controller('OutfitController', ['$window','$rootScope','$scope', '$state', '$stateParams', 'ShipsDB', 'Ship', 'Components', 'Serializer', 'Persist', function ($window, $rootScope, $scope, $state, $p, Ships, Ship, Components, Serializer, Persist) {
+angular.module('app').controller('OutfitController', ['$window','$rootScope','$scope', '$state', '$stateParams', 'ShipsDB', 'Ship', 'Components', 'Serializer', 'Persist', 'lodash', function ($window, $rootScope, $scope, $state, $p, Ships, Ship, Components, Serializer, Persist, _) {
var data = Ships[$p.shipId]; // Retrieve the basic ship properties, slots and defaults
var ship = new Ship($p.shipId, data.properties, data.slots); // Create a new Ship instance
+ var win = angular.element($window); // Angularized window object for event triggering
// Update the ship instance with the code (if provided) or the 'factory' defaults.
if ($p.code) {
@@ -22,11 +23,18 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
$scope.ft = ship.common[6]; // Fuel Tank
$scope.hps = ship.hardpoints;
$scope.internal = ship.internal;
+ $scope.costList = ship.costList;
+ $scope.powerList = ship.powerList;
+ $scope.priorityBands = ship.priorityBands;
$scope.availCS = Components.forShip(ship.id);
$scope.selectedSlot = null;
$scope.savedCode = Persist.getBuild(ship.id, $scope.buildName);
$scope.canSave = Persist.isEnabled();
$scope.fuel = 0;
+ $scope.pwrDesc = false;
+ $scope.pwrPredicate = null;
+ $scope.costDesc = true;
+ $scope.costPredicate = 'c.cost';
$scope.jrSeries = {
xMin: 0,
@@ -149,39 +157,71 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
* @param {object} item The component being toggled
*/
$scope.toggleCost = function(item) {
- item.incCost = !item.incCost;
- ship.updateTotals();
+ ship.setCostIncluded(item, !item.incCost);
+ };
+
+ /**
+ * [sortCost description]
+ * @param {[type]} key [description]
+ * @return {[type]} [description]
+ */
+ $scope.sortCost = function (key) {
+ $scope.costDesc = ($scope.costPredicate == key)? !$scope.costDesc : $scope.costDesc;
+ $scope.costPredicate = key;
+ };
+
+ $scope.sortPwr = function (key) {
+ $scope.pwrDesc = ($scope.pwrPredicate == key)? !$scope.pwrDesc : $scope.pwrDesc;
+ $scope.pwrPredicate = key;
};
/**
* Toggle the power on/off for the selected component
* @param {object} item The component being toggled
*/
- $scope.togglePwr = function(item) {
- // Update serialize code
- // updateState();
- item.enabled = !item.enabled;
- ship.updateTotals();
+ $scope.togglePwr = function(c) {
+ ship.setSlotEnabled(c, !c.enabled);
+ $scope.code = Serializer.fromShip(ship);
+ updateState();
+ };
+
+ $scope.incPriority = function (c) {
+ if (ship.changePriority(c, c.priority + 1)) {
+ $scope.code = Serializer.fromShip(ship);
+ updateState();
+ }
+ };
+
+ $scope.decPriority = function (c) {
+ if (ship.changePriority(c, c.priority - 1)) {
+ $scope.code = Serializer.fromShip(ship);
+ updateState();
+ }
};
$scope.fuelChange = function (fuel) {
$scope.fuel = fuel;
- angular.element($window).triggerHandler('render');
+ win.triggerHandler('render');
+ };
+
+ $scope.statusRetracted = function (slot) {
+ return ship.getSlotStatus(slot, false);
+ };
+
+ $scope.statusDeployed = function (slot) {
+ return ship.getSlotStatus(slot, true);
};
// Utilify functions
+
function updateState() {
$state.go('outfit', {shipId: ship.id, code: $scope.code, bn: $scope.buildName}, {location:'replace', notify:false});
$scope.jrSeries.xMax = ship.cargoCapacity;
$scope.jrSeries.yMax = ship.jumpRangeWithMass(ship.unladenMass);
$scope.jrSeries.mass = ship.unladenMass;
+ win.triggerHandler('pwrchange');
}
- // Hide any open menu/slot/etc if escape key is pressed
- $scope.$on('escape', function () {
- $scope.selectedSlot = null;
- $scope.$apply();
- });
// Hide any open menu/slot/etc if the background is clicked
$scope.$on('close', function () {
$scope.selectedSlot = null;
diff --git a/app/js/directives/directive-power-bands.js b/app/js/directives/directive-power-bands.js
new file mode 100644
index 00000000..a5ac1e21
--- /dev/null
+++ b/app/js/directives/directive-power-bands.js
@@ -0,0 +1,119 @@
+angular.module('app').directive('powerBands', ['$window', function ($window) {
+ return {
+ restrict: 'A',
+ scope:{
+ bands: '=',
+ available: '='
+ },
+ link: function(scope, element) {
+ var color = d3.scale.ordinal().range([ '#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c']),
+ margin = {top: 20, right: 130, bottom: 20, left: 40},
+ barHeight = 20,
+ innerHeight = (barHeight * 2) + 3,
+ height = innerHeight + margin.top + margin.bottom + 1,
+ wattScale = d3.scale.linear(),
+ pctScale = d3.scale.linear().domain([0, 1]),
+ wattFmt = d3.format('.2f');
+ pctFmt = d3.format('.1%');
+ wattAxis = d3.svg.axis().scale(wattScale).outerTickSize(0).orient('top').tickFormat(d3.format('.2r')),
+ pctAxis = d3.svg.axis().scale(pctScale).outerTickSize(0).orient('bottom').tickFormat(d3.format('%')),
+ // Create chart
+ svg = d3.select(element[0]).append('svg'),
+ vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'),
+ deployed = vis.append('g'),
+ retracted = vis.append('g');
+
+ // Create Y Axis SVG Elements
+ vis.append('g').attr('class', 'watt axis');
+ vis.append('g').attr('class', 'pct axis');
+ vis.append("text").attr('x', -35).attr('y', 15).attr('class','primary').text('RET');
+ vis.append("text").attr('x', -35).attr('y', barHeight + 17).attr('class','primary').text('DEP');
+
+ var retLbl = vis.append("text").attr('y', 15)
+ var depLbl = vis.append("text").attr('y', barHeight + 17).attr('class','primary');
+
+ // Watch for changes to data and events
+ scope.$watchCollection('available', render);
+ angular.element($window).bind('orientationchange resize pwrchange', render);
+
+ function render() {
+ var bands = scope.bands,
+ available = scope.available,
+ width = element[0].offsetWidth,
+ w = width - margin.left - margin.right,
+ maxBand = bands[bands.length - 1];
+ maxPwr = Math.max(available, maxBand.deployedSum);
+
+ // Update chart size
+ svg.attr('width', width).attr('height', height);
+
+ // Remove existing elements
+ retracted.selectAll('rect').remove();
+ retracted.selectAll('text').remove();
+ deployed.selectAll('rect').remove();
+ deployed.selectAll('text').remove();
+
+ // Update X & Y Axis
+ wattScale.range([0, w]).domain([0, maxPwr]);
+ pctScale.range([0, w]).domain([0, maxPwr / available]);
+
+ vis.selectAll('.watt.axis').call(wattAxis);
+ vis.selectAll('.pct.axis').attr('transform', 'translate(0,' + innerHeight + ')').call(pctAxis);
+
+ retLbl
+ .attr('x', wattScale(maxBand.retractedSum) + 5 )
+ .attr('class',maxBand.retractedSum > available? 'warning': 'primary')
+ .text(wattFmt(maxBand.retractedSum) + ' (' + pctFmt(maxBand.retractedSum / available) + ')');
+ depLbl
+ .attr('x', wattScale(maxBand.deployedSum) + 5 )
+ .attr('class',maxBand.deployedSum > available? 'warning': 'primary')
+ .text(wattFmt(maxBand.deployedSum) + ' (' + pctFmt(maxBand.deployedSum / available) + ')');
+
+ retracted.selectAll("rect").data(bands).enter().append("rect")
+ .attr("height", barHeight)
+ .attr("width", function(d) { return d.retracted? (wattScale(d.retracted) - 1) : 0; })
+ .attr("x", function(d) { return wattScale(d.retractedSum) - wattScale(d.retracted); })
+ .attr('y', 1)
+ .attr('class',function(d){ return (d.retractedSum > available)? 'warning' :'primary'; });
+
+ retracted.selectAll("text").data(bands).enter().append("text")
+ .attr('x', function(d) { return wattScale(d.retractedSum) - (wattScale(d.retracted) / 2); })
+ .attr('y', 15)
+ .style('text-anchor', 'middle')
+ .attr('class','primary-bg')
+ .text(function(d,i) { return bandText(d.retracted, i, available); });
+
+ deployed.selectAll("rect").data(bands).enter().append("rect")
+ .attr("height", barHeight)
+ .attr("width", function(d) { return (d.deployed || d.retracted)? (wattScale(d.deployed + d.retracted) - 1) : 0; })
+ .attr("x", function(d) { return wattScale(d.deployedSum) - wattScale(d.retracted) - wattScale(d.deployed); })
+ .attr('y', barHeight + 2)
+ .attr('class',function(d){ return (d.deployedSum > available)? 'warning' :'primary'; });
+
+ deployed.selectAll("text").data(bands).enter().append("text")
+ .attr('x', function(d) { return wattScale(d.deployedSum) - ((wattScale(d.retracted) + wattScale(d.deployed)) / 2); })
+ .attr('y', barHeight + 17)
+ .style('text-anchor', 'middle')
+ .attr('class','primary-bg')
+ .text(function(d,i) { return bandText(d.deployed + d.retracted, i, available); });
+
+ }
+
+ function bandText(val, index, available) {
+ if (val > 0) {
+ if( wattScale(val) > 100) {
+ return (index + 1) + ' (' + wattFmt(val) + ' MW)';
+ }
+ if( wattScale(val) > 10) {
+ return index + 1;
+ }
+ }
+ return '';
+ }
+
+ scope.$on('$destroy', function() {
+ angular.element($window).unbind('orientationchange resize pwrchange', render);
+ });
+ }
+ };
+}]);
\ No newline at end of file
diff --git a/app/js/shipyard/factory-ship.js b/app/js/shipyard/factory-ship.js
index fe46f2a9..130f77e5 100755
--- a/app/js/shipyard/factory-ship.js
+++ b/app/js/shipyard/factory-ship.js
@@ -9,8 +9,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
*/
function Ship(id, properties, slots) {
this.id = id;
- this.incCost = true;
- this.cargoScoop = { enabled: true, c: Components.cargoScoop() };
+ this.cargoScoop = { enabled: true, c: Components.cargoScoop(), type: 'SYS' };
this.bulkheads = { incCost: true, maxClass: 8 };
for (var p in properties) { this[p] = properties[p]; } // Copy all base properties from shipData
@@ -22,6 +21,37 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
group.push({id: null, c: null, enabled: true, incCost: true, maxClass: slotGroup[i]});
}
}
+ this.c = { incCost: true, c: { name: this.name, cost: this.cost } }; // Make a 'Ship' component similar to other components
+
+ this.costList = _.union(this.internal, this.common, this.hardpoints);
+ this.costList.push(this.bulkheads); // Add The bulkheads
+ this.costList.unshift(this.c); // Add the ship itself to the list
+
+ this.powerList = _.union(this.internal, this.hardpoints);
+ this.powerList.unshift(this.cargoScoop);
+ this.powerList.unshift(this.common[1]); // Add Thrusters
+ this.powerList.unshift(this.common[5]); // Add Sensors
+ this.powerList.unshift(this.common[4]); // Add Power Distributor
+ this.powerList.unshift(this.common[3]); // Add Life Support
+ this.powerList.unshift(this.common[2]); // Add FSD
+ this.powerList.unshift(this.common[0]); // Add Power Plant
+ this.priorityBands = [
+ {deployed: 0, retracted: 0},
+ {deployed: 0, retracted: 0},
+ {deployed: 0, retracted: 0},
+ {deployed: 0, retracted: 0},
+ {deployed: 0, retracted: 0}
+ ];
+
+ // Reset cumulative and aggragate stats
+ this.fuelCapacity = 0;
+ this.cargoCapacity = 0;
+ this.ladenMass = 0;
+ this.armourAdded = 0;
+ this.shieldMultiplier = 1;
+ this.totalCost = this.cost;
+ this.unladenMass = this.mass;
+ this.armourTotal = this.armour;
}
/**
@@ -32,142 +62,68 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
var internal = this.internal;
var common = this.common;
var hps = this.hardpoints;
+ var bands = this.priorityBands;
var i,l;
- this.bulkheads.id = comps.bulkheads || 0;
- this.bulkheads.c = Components.bulkheads(this.id, this.bulkheads.id);
+ this.useBulkhead(comps.bulkheads || 0, true);
+ this.cargoScoop.priority = 0; // TODO set from comps
+ bands[this.cargoScoop.priority].retracted += this.cargoScoop.c.power;
for(i = 0, l = comps.common.length; i < l; i++) {
- common[i].id = comps.common[i];
- common[i].c = Components.common(i, comps.common[i]);
+ common[i].enabled = true; // TODO set enabled from comps
+ common[i].priority = 0; // TODO set from comps
+ common[i].type = 'SYS';
+ this.use(common[i], comps.common[i], Components.common(i, comps.common[i]), true);
}
+ common[1].type = 'ENG'; // Thrusters
+ common[2].type = 'ENG'; // FSD
+
for(i = 0, l = comps.hardpoints.length; i < l; i++) {
+ hps[i].enabled = true; // TODO set enabled from comps
+ hps[i].priority = 0; // TODO set from comps
+ hps[i].type = hps[i].maxClass? 'WEP' : 'SYS';
if (comps.hardpoints[i] !== 0) {
- hps[i].id = comps.hardpoints[i];
- hps[i].c = Components.hardpoints(comps.hardpoints[i]);
+ this.use(hps[i], comps.hardpoints[i], Components.hardpoints(comps.hardpoints[i]), true);
} else {
hps[i].c = hps[i].id = null;
}
}
for(i = 0, l = comps.internal.length; i < l; i++) {
+ internal[i].enabled = true; // TODO set enabled from comps
+ internal[i].priority = 0; // TODO set from comps
+ internal[i].type = 'SYS';
if (comps.internal[i] !== 0) {
- internal[i].id = comps.internal[i];
- internal[i].c = Components.internal(comps.internal[i]);
+ this.use(internal[i], comps.internal[i], Components.internal(comps.internal[i]), true);
} else {
- internal[i].id = internal[i].c = null;
+ internal[i].id = internal[i].c = null;
}
}
- this.updateTotals();
+
+ // Update aggragated stats
+ this.updatePower();
+ this.updateJumpStats();
+ this.updateShieldStrength();
};
- /**
- * Updates the ship totals based on the components for every slot.
- */
- Ship.prototype.updateTotals = function() {
- var c = _.reduce(this.common, optsSum, {cost: 0, power: 0, mass: 0});
- var i = _.reduce(this.internal, optsSum, {cost: 0, power: 0, mass: 0, fuel: 0, cargo: 0, armouradd: 0});
- var h = _.reduce(this.hardpoints, hpSum, {cost: 0, active: 0, passive: 0, mass: 0, shieldmul: 1});
- var fsd = this.common[2].c; // Frame Shift Drive;
- var sgSI = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any
-
- this.totalCost = c.cost + i.cost + h.cost + (this.incCost? this.cost : 0) + (this.bulkheads.incCost? this.bulkheads.c.cost : 0);
- this.unladenMass = c.mass + i.mass + h.mass + this.mass + this.bulkheads.c.mass;
- this.powerAvailable = this.common[0].c.pGen; // Power Plant
- this.fuelCapacity = this.common[6].c.capacity + i.fuel; // Fuel Tank + Internal Fuel Tanks
- this.maxMass = this.common[1].c.maxmass; // Thrusters Max Mass
- this.cargoCapacity = i.cargo;
- this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity;
- this.powerRetracted = c.power + i.power + h.passive + (this.cargoScoop.enabled? this.cargoScoop.c.power : 0);
- this.powerDeployed = this.powerRetracted + h.active;
- this.armourAdded = i.armouradd;
- this.shieldMultiplier = h.shieldmul;
- this.shieldStrength = sgSI != -1? calcShieldStrength(this.mass, this.shields, this.internal[sgSI].c, this.shieldMultiplier) : 0;
- this.armourTotal = this.armourAdded + this.armour;
-
- // Jump Range and total range calculations
- var fuelRemaining = this.fuelCapacity % fsd.maxfuel; // Fuel left after making N max jumps
- var jumps = this.fuelCapacity / fsd.maxfuel;
- this.unladenRange = calcJumpRange(this.unladenMass + fsd.maxfuel, fsd, this.fuelCapacity); // Include fuel weight for jump
- this.fullTankRange = calcJumpRange(this.unladenMass + this.fuelCapacity, fsd, this.fuelCapacity); // Full Tanke
- this.ladenRange = calcJumpRange(this.ladenMass, fsd, this.fuelCapacity);
- this.maxJumpCount = Math.ceil(jumps); // Number of full fuel jumps + final jump to empty tank
-
- // Going backwards, start with the last jump using the remaining fuel
- this.unladenTotalRange = fuelRemaining > 0? calcJumpRange(this.unladenMass + fuelRemaining, fsd, fuelRemaining): 0;
- this.ladenTotalRange = fuelRemaining > 0? calcJumpRange(this.unladenMass + this.cargoCapacity + fuelRemaining, fsd, fuelRemaining): 0;
-
- // For each max fuel jump, calculate the max jump range based on fuel left in the tank
- for (var j = 0, l = Math.floor(jumps); j < l; j++) {
- fuelRemaining += fsd.maxfuel;
- this.unladenTotalRange += calcJumpRange(this.unladenMass + fuelRemaining, fsd);
- this.ladenTotalRange += calcJumpRange(this.unladenMass + this.cargoCapacity + fuelRemaining, fsd);
- }
-
- // TODO: armor bonus / damage reduction for bulkheads
- // TODO: Damage / DPS total (for all weapons)
- };
-
- /**
- * Utilify function for summing the components properties
- *
- * @private
- * @param {object} sum Sum of cost, power, mass, capacity
- * @param {object} slot Slot object
- * @return {object} The mutated sum object
- */
- function optsSum(sum, slot) {
- var c = slot.c;
- if (c) { // The slot has a component installed
- sum.cost += (slot.incCost && c.cost)? c.cost : 0;
- sum.power += (slot.enabled && c.power)? c.power : 0;
- sum.mass += c.mass || 0;
- if (c.grp == 'ft') { // Internal Fuel Tank
- sum.fuel += c.capacity;
- }
- else if (c.grp == 'cr') { // Internal Cargo Rack
- sum.cargo += c.capacity;
- }
- sum.armouradd += c.armouradd || 0;
- }
- return sum;
- }
-
- /**
- * Utilify function for summing the hardpoint properties
- *
- * @private
- * @param {object} sum Sum of cost, power, etc
- * @param {object} slot Slot object
- * @return {object} The mutated sum object
- */
- function hpSum(sum, slot) {
- var c = slot.c;
- if (c) { // The slot has a component installed
- sum.cost += (slot.incCost && c.cost)? c.cost : 0;
- sum[c.passive? 'passive': 'active'] += slot.enabled? c.power : 0;
- sum.mass += c.mass || 0;
- sum.shieldmul += c.shieldmul || 0;
- }
- return sum;
- }
-
- Ship.prototype.useBulkhead = function(index) {
+ Ship.prototype.useBulkhead = function(index, preventUpdate) {
+ var oldBulkhead = this.bulkheads.c;
this.bulkheads.id = index;
this.bulkheads.c = Components.bulkheads(this.id, index);
- this.updateTotals(); // Update mass, range, shield strength, armor
+ this.updateStats(this.bulkheads, this.bulkheads.c, oldBulkhead, preventUpdate);
};
/**
* Update a slot with a the component if the id is different from the current id for this slot.
* Has logic handling components that you may only have 1 of (Shield Generator or Refinery).
*
- * @param {object} slot The component slot
- * @param {string} id Unique ID for the selected component
- * @param {object} component Properties for the selected component
+ * @param {object} slot The component slot
+ * @param {string} id Unique ID for the selected component
+ * @param {object} component Properties for the selected component
+ * @param {boolean} preventUpdate If true, do not update aggregated stats
*/
- Ship.prototype.use = function(slot, id, component) {
+ Ship.prototype.use = function(slot, id, component, preventUpdate) {
if (slot.id != id) { // Selecting a different component
var slotIndex = this.internal.indexOf(slot);
// Slot is an internal slot, is not being emptied, and the selected component group/type must be of unique
@@ -177,17 +133,20 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
// If another slot has an installed component with of the same type
if (similarSlotIndex != -1 && similarSlotIndex != slotIndex) {
// Empty the slot
- this.internal[similarSlotIndex].id = null;
- this.internal[similarSlotIndex].c = null;
+ var similarSlot = this.internal[similarSlotIndex];
+ this.updateStats(similarSlot, null, similarSlot.c, true); // Update stats but don't trigger a global update
+ similarSlot.id = null;
+ similarSlot.c = null;
}
}
- // Update slot with selected component (or empty)
+ var oldComponent = slot.c;
slot.id = id;
slot.c = component;
- this.updateTotals();
+ this.updateStats(slot, component, oldComponent, preventUpdate);
}
};
+
/**
* Calculate jump range using the installed FSD and the
* specified mass which can be more or less than ships actual mass
@@ -205,11 +164,179 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
* @param {string} group Component group/type
* @return {number} The index of the slot in ship.internal
*/
- Ship.prototype.findInternalByGroup = function(group) {
+ Ship.prototype.findInternalByGroup = function (group) {
return _.findIndex(this.internal, function (slot) {
return slot.c && slot.c.grp == group;
});
};
+ Ship.prototype.changePriority = function (slot, newPriority) {
+ if(newPriority >= 0 && newPriority < this.priorityBands.length) {
+ var oldPriority = slot.priority;
+ slot.priority = newPriority;
+
+ if (slot.enabled) {
+ var usage = (slot.c.passive || this.hardpoints.indexOf(slot) == -1)? 'retracted' : 'deployed';
+ this.priorityBands[oldPriority][usage] -= slot.c.power;
+ this.priorityBands[newPriority][usage] += slot.c.power;
+ this.updatePower();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ Ship.prototype.setCostIncluded = function (item, included) {
+ if (item.incCost != included && item.c) {
+ this.totalCost += included? item.c.cost : -item.c.cost;
+ }
+ item.incCost = included;
+ }
+
+ Ship.prototype.setSlotEnabled = function (slot, enabled) {
+ if (slot.enabled != enabled && slot.c) { // Enabled state is changing
+ var usage = (slot.c.passive || this.hardpoints.indexOf(slot) == -1)? 'retracted' : 'deployed';
+ this.priorityBands[slot.priority][usage] += enabled? slot.c.power : -slot.c.power;
+ this.updatePower();
+ }
+ slot.enabled = enabled;
+ }
+
+ Ship.prototype.getSlotStatus = function (slot, deployed) {
+ if(!slot.c) { // Empty Slot
+ return 0; // No Status (Not possible)
+ }
+ else if (!slot.enabled) {
+ return 1; // Disabled
+ }
+ else if (deployed) {
+ return this.priorityBands[slot.priority].deployedSum > this.powerAvailable? 2 : 3; // Offline : Online
+ }
+ else if (this.hardpoints.indexOf(slot) != -1 && !slot.c.passive) { // Active hardpoints have no retracted status
+ return 0; // No Status (Not possible)
+ }
+ return this.priorityBands[slot.priority].retractedSum > this.powerAvailable? 2 : 3; // Offline : Online
+ }
+
+ /**
+ * Updates the ship's cumulative and aggregated stats based on the component change.
+ */
+ Ship.prototype.updateStats = function(slot, n, old, preventUpdate) {
+ var isHardPoint = this.hardpoints.indexOf(slot) != -1;
+ var powerChange = slot == this.common[0];
+
+ if (old) { // Old component now being removed
+ switch (old.grp) {
+ case 'ft':
+ this.fuelCapacity -= old.capacity;
+ break;
+ case 'cr':
+ this.cargoCapacity -= old.capacity;
+ break;
+ case 'hr':
+ this.armourAdded -= old.armouradd;
+ break;
+ case 'sb':
+ this.shieldMultiplier -= old.shieldmul;
+ break;
+ }
+
+ if (slot.incCost && old.cost) {
+ this.totalCost -= old.cost;
+ }
+
+ if(old.power) {
+ this.priorityBands[slot.priority][(isHardPoint && !old.passive)? 'deployed' : 'retracted'] -= old.power;
+ powerChange = true;
+ }
+ this.unladenMass -= old.mass || 0;
+ }
+
+ if (n) {
+ switch (n.grp) {
+ case 'ft':
+ this.fuelCapacity += n.capacity;
+ break;
+ case 'cr':
+ this.cargoCapacity += n.capacity;
+ break;
+ case 't':
+ this.maxMass = n.maxmass;
+ break;
+ case 'hr':
+ this.armourAdded += n.armouradd;
+ break;
+ case 'sb':
+ this.shieldMultiplier += n.shieldmul;
+ break;
+ }
+
+ if (slot.incCost && n.cost) {
+ this.totalCost += n.cost;
+ }
+
+ if (n.power) {
+ this.priorityBands[slot.priority][(isHardPoint && !n.passive)? 'deployed' : 'retracted'] += n.power;
+ powerChange = true;
+ }
+ this.unladenMass += n.mass || 0;
+ }
+
+ this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity;
+ this.armourTotal = this.armourAdded + this.armour;
+
+ if(!preventUpdate) {
+ if (powerChange) {
+ this.updatePower();
+ }
+ this.updateJumpStats();
+ this.updateShieldStrength();
+ }
+ };
+
+ Ship.prototype.updatePower = function() {
+ var bands = this.priorityBands;
+ var prevRetracted = 0, prevDeployed = 0;
+
+ for(var i = 0, l = bands.length; i < l; i++) {
+ var band = bands[i];
+ prevRetracted = band.retractedSum = prevRetracted + band.retracted;
+ prevDeployed = band.deployedSum = prevDeployed + band.deployed + band.retracted;
+ }
+
+ this.powerAvailable = this.common[0].c.pGen;
+ this.powerRetracted = prevRetracted;
+ this.powerDeployed = prevDeployed;
+ };
+
+ Ship.prototype.updateShieldStrength = function() {
+ var sgSI = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any
+ this.shieldStrength = sgSI != -1? calcShieldStrength(this.mass, this.shields, this.internal[sgSI].c, this.shieldMultiplier) : 0;
+ };
+
+ /**
+ * Jump Range and total range calculations
+ */
+ Ship.prototype.updateJumpStats = function() {
+ var fsd = this.common[2].c; // Frame Shift Drive;
+ var fuelRemaining = this.fuelCapacity % fsd.maxfuel; // Fuel left after making N max jumps
+ var jumps = this.fuelCapacity / fsd.maxfuel;
+ this.unladenRange = calcJumpRange(this.unladenMass + fsd.maxfuel, fsd, this.fuelCapacity); // Include fuel weight for jump
+ this.fullTankRange = calcJumpRange(this.unladenMass + this.fuelCapacity, fsd, this.fuelCapacity); // Full Tanke
+ this.ladenRange = calcJumpRange(this.ladenMass, fsd, this.fuelCapacity);
+ this.maxJumpCount = Math.ceil(jumps); // Number of full fuel jumps + final jump to empty tank
+
+ // Going backwards, start with the last jump using the remaining fuel
+ this.unladenTotalRange = fuelRemaining > 0? calcJumpRange(this.unladenMass + fuelRemaining, fsd, fuelRemaining): 0;
+ this.ladenTotalRange = fuelRemaining > 0? calcJumpRange(this.unladenMass + this.cargoCapacity + fuelRemaining, fsd, fuelRemaining): 0;
+
+ // For each max fuel jump, calculate the max jump range based on fuel left in the tank
+ for (var j = 0, l = Math.floor(jumps); j < l; j++) {
+ fuelRemaining += fsd.maxfuel;
+ this.unladenTotalRange += calcJumpRange(this.unladenMass + fuelRemaining, fsd);
+ this.ladenTotalRange += calcJumpRange(this.unladenMass + this.cargoCapacity + fuelRemaining, fsd);
+ }
+ };
+
return Ship;
}]);
diff --git a/app/js/shipyard/module-shipyard.js b/app/js/shipyard/module-shipyard.js
index c7f45530..8702f923 100755
--- a/app/js/shipyard/module-shipyard.js
+++ b/app/js/shipyard/module-shipyard.js
@@ -19,41 +19,51 @@ angular.module('shipyard', ['ngLodash'])
'Sensors',
'Fuel Tank'
])
- .value('internalGroupMap', {
- fs:'Fuel Scoop',
+ // Map to lookup group labels/names for component grp
+ .value('GroupMap', {
+ // Common
+ pp:'Power Plant',
+ t:'Thrusters',
+ fsd:'Frame Shift Drive',
+ ls:'Life Support',
+ pd:'Power Distributor',
+ s:'Sensors',
ft:'Fuel Tank',
+
+ // Internal
+ fs:'Fuel Scoop',
sc:'Scanners',
- am:'Auto Field-Maintenance Unit',
+ am:'Auto Field-Maint. Unit',
cr:'Cargo Racks',
fi:'FSD Interdictor',
hb:'Hatch Breaker Limpet Ctrl',
hr:'Hull Reinforcement Package',
rf:'Refinery',
- sb:'Shield Cell Bank',
+ scb:'Shield Cell Bank',
sg:'Shield Generator',
dc:'Docking Computer',
fx:'Fuel Transfer Limpet Ctrl',
pc:'Prospector Limpet Ctrl',
- cc:'Collector Limpet Ctrl'
- })
- .value('hardpointsGroupMap', {
- 'bl': "Beam Laser",
- 'ul': "Burst Laser",
- 'c': "Cannon",
- 'cs': "Cargo Scanner",
- 'cm': "Countermeasure",
- 'fc': "Fragment Cannon",
- 'fs': "Frame Shift Wake Scanner",
- 'kw': "Kill Warrant Scanner",
- 'nl': "Mine Launcher",
- 'ml': "Mining Laser",
- 'mr': "Missile Rack",
- 'pa': "Plasma Accelerator",
- 'mc': "Multi-cannon",
- 'pl': "Pulse Laser",
- 'rg': "Rail Gun",
- 'sb': "Shield Booster",
- 'tp': "Torpedo Pylon"
+ cc:'Collector Limpet Ctrl',
+
+ // Hard Points
+ bl: "Beam Laser",
+ ul: "Burst Laser",
+ c: "Cannon",
+ cs: "Cargo Scanner",
+ cm: "Countermeasure",
+ fc: "Fragment Cannon",
+ ws: "Frame Shift Wake Scanner",
+ kw: "Kill Warrant Scanner",
+ nl: "Mine Launcher",
+ ml: "Mining Laser",
+ mr: "Missile Rack",
+ pa: "Plasma Accelerator",
+ mc: "Multi-cannon",
+ pl: "Pulse Laser",
+ rg: "Rail Gun",
+ sb: "Shield Booster",
+ tp: "Torpedo Pylon"
})
.value('shipPurpose', {
mp: 'Multi Purpose',
@@ -93,7 +103,7 @@ angular.module('shipyard', ['ngLodash'])
title: 'Speed',
props: ['speed', 'boost'],
lbls: ['Thrusters', 'Boost'],
- unit: 'M/s',
+ unit: 'm/s',
fmt: 'fRound'
},
{ // 2
@@ -105,13 +115,13 @@ angular.module('shipyard', ['ngLodash'])
{ // 3
title: 'Shields',
props: ['shieldStrength'],
- unit: 'Mj',
+ unit: 'MJ',
fmt: 'fRound'
},
{ // 4
title: 'Jump Range',
- props: ['unladenRange', 'ladenRange'],
- lbls: ['Unladen', 'Laden'],
+ props: ['unladenRange', 'fullTankRange', 'ladenRange'],
+ lbls: ['Max', 'Full Tank', 'Laden'],
unit: 'LY',
fmt: 'fRound'
},
@@ -146,7 +156,14 @@ angular.module('shipyard', ['ngLodash'])
props: ['totalCost'],
unit: 'CR',
fmt: 'fCrd'
- }
+ },
+ { // 10
+ title: 'Total Range',
+ props: ['unladenTotalRange', 'ladenTotalRange'],
+ lbls: ['Unladen', 'Laden'],
+ unit: 'LY',
+ fmt: 'fRound'
+ },
])
/**
* Calculate the maximum single jump range based on mass and a specific FSD
diff --git a/app/js/shipyard/service-components.js b/app/js/shipyard/service-components.js
index 17d7fd60..ac9c1e95 100755
--- a/app/js/shipyard/service-components.js
+++ b/app/js/shipyard/service-components.js
@@ -32,13 +32,26 @@ angular.module('shipyard').service('Components', ['lodash', 'ComponentsDB', 'Shi
return null;
};
+ /**
+ * Looks up the bulkhead component for a specific ship and bulkhead
+ * @param {string} shipId Unique ship Id/Key
+ * @param {number} bulkheadsId Id/Index for the specified bulkhead
+ * @return {object} The bulkhead component object
+ */
this.bulkheads = function(shipId, bulkheadsId) {
return C.bulkheads[shipId][bulkheadsId];
};
+ /**
+ * Creates a new ComponentSet that contains all available components
+ * that the specified ship is eligible to use.
+ *
+ * @param {string} shipId Unique ship Id/Key
+ * @return {ComponentSet} The set of components the ship can install
+ */
this.forShip = function (shipId) {
var ship = Ships[shipId];
- return new ComponentSet(C, ship.properties.mass, ship.slots.common, ship.slots.internal[0], ship.slots.hardpoints[0]);
+ return new ComponentSet(C, ship.properties.mass + 5, ship.slots.common, ship.slots.internal[0], ship.slots.hardpoints[0]);
};
}]);
\ No newline at end of file
diff --git a/app/views/page-outfit.html b/app/views/page-outfit.html
index 0ce52ba5..203b527c 100755
--- a/app/views/page-outfit.html
+++ b/app/views/page-outfit.html
@@ -53,7 +53,7 @@
{{fRound(ship.speed)}} m/s |
{{fRound(ship.boost)}} m/s |
{{ship.armourTotal}} ({{ship.armour}} + {{ship.armourAdded}}) |
- {{fRound(ship.shieldStrength)}} Mj ({{fRPct(ship.shieldMultiplier)}}) |
+ {{fRound(ship.shieldStrength)}} MJ ({{fRPct(ship.shieldMultiplier)}}) |
{{fRound(ship.unladenMass)}} T |
{{fRound(ship.ladenMass)}} T |
{{fRound(ship.cargoCapacity)}} T |
@@ -137,9 +137,9 @@
{{pd.id}} Power Distributor
{{pd.c.mass}} T
- Wep: {{pd.c.weaponcapacity}} Mj / {{pd.c.weaponrecharge}} MW
- Sys: {{pd.c.systemcapacity}} Mj / {{pd.c.systemrecharge}} MW
- Eng: {{pd.c.enginecapacity}} Mj / {{pd.c.enginerecharge}} MW
+ WEP: {{pd.c.weaponcapacity}} MJ / {{pd.c.weaponrecharge}} MW
+ SYS: {{pd.c.systemcapacity}} MJ / {{pd.c.systemrecharge}} MW
+ ENG: {{pd.c.enginecapacity}} MJ / {{pd.c.enginerecharge}} MW
@@ -149,7 +149,7 @@
{{ss.id}} Sensors
{{ss.c.mass}} T
- Range: {{ss.c.range}} KM
+ Range: {{ss.c.range}} km
@@ -166,7 +166,7 @@
Internal Compartments
-
+
@@ -176,7 +176,7 @@
HardPoints
-
+
@@ -186,7 +186,7 @@
Utility Mounts
-
+
@@ -194,68 +194,71 @@
-
Power Use
-
-
Generated
-
-
{{pp.c.class}}{{pp.c.rating}} Power Plant
{{fPwr(pp.c.pGen)}}
-
-
Standard
-
-
{{c.c.class}}{{c.c.rating}} {{CArr[$index]}}
{{fPwr(c.c.power)}}
-
-
-
1H Cargo Scoop
{{fPwr(ship.cargoScoop.c.power)}}
-
-
Hardpoints
-
-
{{c.c.class}}{{c.c.rating}} {{c.c.name || hgMap[c.c.grp]}}
{{fPwr(c.c.power)}}
-
-
Internal
-
-
{{c.c.class}}{{c.c.rating}} {{c.c.name || igMap[c.c.grp]}}
{{fPwr(c.c.power)}}
-
-
-
-
- | Retracted | Deployed |
+
+
+
+ | Component |
+ Type |
+ Pri |
+ Pwr |
+ Ret |
+ Dep |
+
+
- | {{fPwr(ship.powerRetracted)}} MW ({{fPct(ship.powerRetracted/ship.powerAvailable)}}) |
- {{fPwr(ship.powerDeployed)}} MW ({{fPct(ship.powerDeployed/ship.powerAvailable)}}) |
+ {{pp.c.class}}{{pp.c.rating}} |
+ Power Plant |
+ SYS |
+ 1 |
+ {{fPwr(pp.c.pGen)}} |
+ 100% |
+ |
+ |
+
+
|
+
+ | {{c.c.class}}{{c.c.rating}} |
+ |
+ |
+ ◀ {{c.priority + 1}} ▶ |
+ {{fPwr(c.c.power)}} |
+ {{fRPct(c.c.power/ship.powerAvailable)}} |
+ DISABLED |
+ {{STATUS[statusRetracted(c)]}} |
+ {{STATUS[statusDeployed(c)]}} |
+
-
Costs
-
-
-
{{ship.name}}
{{fCrd(ship.cost)}}
-
-
-
{{ship.bulkheads.c.name}}
{{fCrd(ship.bulkheads.c.cost)}}
-
-
-
{{c.c.class}}{{c.c.rating}} {{CArr[$index]}}
{{fCrd(c.c.cost)}}
-
-
-
{{c.c.class}}{{c.c.rating}} {{c.c.name || hgMap[c.c.grp]}}
{{fCrd(c.c.cost)}}
-
-
-
{{c.c.class}}{{c.c.rating}} {{c.c.name || igMap[c.c.grp]}}
{{fCrd(c.c.cost)}}
-
-
-
- | Insurance | Total |
+
+
+
+ | Component |
+ Credits |
+
+
-
- | {{fCrd(ship.totalCost * insurance.current.pct)}} CR |
- {{fCrd(ship.totalCost)}} CR |
+
+ | {{c.c.class}}{{c.c.rating}} |
+ |
+ {{fCrd(c.c.cost)}} CR |
+
+
+ | Total |
+ {{fCrd(ship.totalCost)}} CR |
+
+
+ | Insurance |
+ {{fCrd(ship.totalCost * insurance.current.pct)}} CR |
+
+