Compare commits

...

14 Commits

Author SHA1 Message Date
Colin McLeod
7c23fb3884 Bumping version to 0.14.0 2015-06-26 11:00:46 -07:00
Colin McLeod
b707015d9c Fix power plant toggle power bug 2015-06-26 11:00:11 -07:00
Colin McLeod
10d5611dcd Seperate discounts for ship and components, added discounts for 5,10,15,20,25 percent off 2015-06-25 20:26:22 -07:00
Colin McLeod
9009a2a434 Bumping to 0.13.4, fixing courier bulkhead mass 2015-06-24 13:17:23 -07:00
Colin McLeod
8b98a98faf lint fix 2015-06-23 23:47:27 -07:00
Colin McLeod
44de3e4bbc Minor CSS refactor, outfit page tweak 2015-06-23 23:45:30 -07:00
Colin McLeod
ba2e9a12b0 Tweak chart axis labels 2015-06-23 11:47:23 -07:00
Colin McLeod
57304f55c1 Merge pull request #62 from Maverick-JM/master
Fixes for Windows phone and IE
2015-06-23 11:47:09 -07:00
Maverick
1eea358c35 Typo in the comments :). 2015-06-23 23:10:04 +10:00
Maverick
f671b7c34f Fix for the top menu not working in IE 11 (and probably older IE too). 2015-06-23 23:04:07 +10:00
Maverick
211028d80d Fix for the whole app not working on Windows phones as a Windows phone, this was somewhat vexing :)). 2015-06-23 22:27:24 +10:00
Justin Murtagh
c79359ea2f Merge pull request #6 from cmmcleod/master
Update my fork.
2015-06-23 22:04:34 +10:00
Colin McLeod
93f92da1df Adding line chart, adding speed function 2015-06-19 19:04:45 -07:00
Justin Murtagh
6fb2247dd7 Merge pull request #5 from cmmcleod/master
Merge back post-responsive priority stuff.
2015-06-12 07:12:13 +10:00
16 changed files with 403 additions and 85 deletions

View File

@@ -1,8 +1,14 @@
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', 'GroupMap', 'Persist',
function($rootScope, $location, $window, $doc, $state, CArr, shipPurpose, sz, hpc, GroupMap, Persist) {
.run(['$rootScope', '$location', '$window', '$document', '$state', 'commonArray', 'shipPurpose', 'shipSize', 'hardPointClass', 'GroupMap', 'Persist', 'Discounts',
function($rootScope, $location, $window, $doc, $state, CArr, shipPurpose, sz, hpc, GroupMap, Persist, Discounts) {
// App is running as a standalone web app on tablet/mobile
var isStandAlone = $window.navigator.standalone || ($window.external && $window.external.msIsSiteMode && $window.external.msIsSiteMode());
var isStandAlone;
// This was causing issues on Windows phones ($window.external was causing Angular js to throw an exception). Backup is to try this and set isStandAlone to false if this fails.
try {
isStandAlone = $window.navigator.standalone || ($window.external && $window.external.msIsSiteMode && $window.external.msIsSiteMode());
} catch (ex) {
isStandAlone = false;
}
// Redirect any state transition errors to the error controller/state
$rootScope.$on('$stateChangeError', function(e, toState, toParams, fromState, fromParams, error) {
@@ -34,7 +40,7 @@ function($rootScope, $location, $window, $doc, $state, CArr, shipPurpose, sz, hp
$rootScope.HPC = hpc;
$rootScope.GMAP = GroupMap;
$rootScope.insurance = { opts: [{ name: 'Standard', pct: 0.05 }, { name: 'Alpha', pct: 0.025 }, { name: 'Beta', pct: 0.035 }] };
$rootScope.discounts = { opts: [{ name: 'None', pct: 1 }, { name: 'Founders World - 10%', pct: 0.90 }] };
$rootScope. discounts = { opts: Discounts };
$rootScope.STATUS = ['', 'DISABLED', 'OFF', 'ON'];
$rootScope.STATUS_CLASS = ['', 'disabled', 'warning', 'secondary-disabled'];
$rootScope.title = 'Coriolis';

View File

@@ -1,4 +1,4 @@
angular.module('app').controller('OutfitController', ['$window', '$rootScope', '$scope', '$state', '$stateParams', 'ShipsDB', 'Ship', 'Components', 'Serializer', 'Persist', 'calcTotalRange', function($window, $rootScope, $scope, $state, $p, Ships, Ship, Components, Serializer, Persist, calcTotalRange) {
angular.module('app').controller('OutfitController', ['$window', '$rootScope', '$scope', '$state', '$stateParams', 'ShipsDB', 'Ship', 'Components', 'Serializer', 'Persist', 'calcTotalRange', 'calcSpeed', function($window, $rootScope, $scope, $state, $p, Ships, Ship, Components, Serializer, Persist, calcTotalRange, calcSpeed) {
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
@@ -11,6 +11,8 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
ship.buildWith(data.defaults); // Populate with default components
}
ship.applyDiscounts($rootScope.discounts.ship, $rootScope.discounts.components);
$scope.buildName = $p.bn;
$rootScope.title = ship.name + ($scope.buildName ? ' - ' + $scope.buildName : '');
$scope.ship = ship;
@@ -55,8 +57,7 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
title: 'Jump Range',
unit: 'LY'
}
},
watch: $scope.fsd
}
};
$scope.trSeries = {
@@ -78,8 +79,30 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
title: 'Total Range',
unit: 'LY'
}
},
watch: $scope.fsd
}
};
$scope.speedSeries = {
xMin: 0,
xMax: ship.cargoCapacity,
yMax: 500,
yMin: 0,
series: ['speed', 'boost'],
func: function(cargo) { // X Axis is Cargo
return calcSpeed(ship.unladenMass + $scope.fuel + cargo, ship.speed, ship.boost, $scope.th.c);
}
};
$scope.speedChart = {
labels: {
xAxis: {
title: 'Cargo',
unit: 'T'
},
yAxis: {
title: 'Speed',
unit: 'm/s'
}
}
};
/**
@@ -122,8 +145,7 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
ship.useBulkhead(id);
}
$scope.selectedSlot = null;
$scope.code = Serializer.fromShip(ship);
updateState();
updateState(Serializer.fromShip(ship));
}
};
@@ -133,8 +155,7 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
$scope.reloadBuild = function() {
if ($scope.buildName && $scope.savedCode) {
Serializer.toShip(ship, $scope.savedCode); // Repopulate with components from last save
$scope.code = $scope.savedCode;
updateState();
updateState($scope.savedCode);
}
};
@@ -149,8 +170,7 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
ship.hardpoints.forEach(function(slot) { ship.use(slot, null, null); });
ship.internal.forEach(function(slot) { ship.use(slot, null, null); });
ship.useBulkhead(0);
$scope.code = Serializer.fromShip(ship);
updateState();
updateState(Serializer.fromShip(ship));
};
/**
@@ -169,7 +189,7 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
if ($scope.code != $scope.savedCode) {
Persist.saveBuild(ship.id, $scope.buildName, $scope.code);
$scope.savedCode = $scope.code;
updateState();
updateState($scope.code);
}
};
@@ -218,21 +238,18 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
*/
$scope.togglePwr = function(c) {
ship.setSlotEnabled(c, !c.enabled);
$scope.code = Serializer.fromShip(ship);
updateState();
updateState(Serializer.fromShip(ship));
};
$scope.incPriority = function(c) {
if (ship.changePriority(c, c.priority + 1)) {
$scope.code = Serializer.fromShip(ship);
updateState();
updateState(Serializer.fromShip(ship));
}
};
$scope.decPriority = function(c) {
if (ship.changePriority(c, c.priority - 1)) {
$scope.code = Serializer.fromShip(ship);
updateState();
updateState(Serializer.fromShip(ship));
}
};
@@ -251,9 +268,10 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
// Utilify functions
function updateState() {
function updateState(code) {
$scope.code = code;
$state.go('outfit', { shipId: ship.id, code: $scope.code, bn: $scope.buildName }, { location: 'replace', notify: false });
$scope.trSeries.xMax = $scope.jrSeries.xMax = ship.cargoCapacity;
$scope.speedSeries.xMax = $scope.trSeries.xMax = $scope.jrSeries.xMax = ship.cargoCapacity;
$scope.jrSeries.yMax = ship.unladenRange;
$scope.trSeries.yMax = ship.unladenTotalRange;
win.triggerHandler('pwrchange');
@@ -264,4 +282,9 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
$scope.selectedSlot = null;
});
// Hide any open menu/slot/etc if the background is clicked
$scope.$on('discountChange', function() {
ship.applyDiscounts($rootScope.discounts.ship, $rootScope.discounts.components);
});
}]);

View File

@@ -13,8 +13,10 @@ angular.module('app').directive('shipyardHeader', ['lodash', '$rootScope', 'Pers
scope.bs = Persist.state;
var insIndex = _.findIndex($rootScope.insurance.opts, 'name', Persist.getInsurance());
var savedDiscounts = Persist.getDiscount() || [1, 1];
$rootScope.insurance.current = $rootScope.insurance.opts[insIndex != -1 ? insIndex : 0];
$rootScope.discounts.current = $rootScope.discounts.opts[Persist.getDiscount() || 0];
$rootScope.discounts.ship = savedDiscounts[0];
$rootScope.discounts.components = savedDiscounts[1];
// Close menus if a navigation change event occurs
$rootScope.$on('$stateChangeStart', function() {
@@ -38,7 +40,8 @@ angular.module('app').directive('shipyardHeader', ['lodash', '$rootScope', 'Pers
* Save selected discount option
*/
scope.updateDiscount = function() {
Persist.setDiscount($rootScope.discounts.opts.indexOf($rootScope.discounts.current));
Persist.setDiscount([$rootScope.discounts.ship, $rootScope.discounts.components]);
$rootScope.$broadcast('discountChange');
};
scope.openMenu = function(e, menu) {

View File

@@ -0,0 +1,190 @@
angular.module('app').directive('lineChart', ['$window', function($window) {
return {
restrict: 'A',
scope: {
config: '=',
series: '='
},
link: function(scope, element) {
var seriesConfig = scope.series,
series = seriesConfig.series,
color = d3.scale.ordinal().range([ '#ff8c0d', '#1fb0ff', '#a05d56', '#d0743c']),
config = scope.config,
labels = config.labels,
margin = { top: 15, right: 15, bottom: 35, left: 60 },
fmt = d3.format('.3r'),
fmtLong = d3.format('.2f'),
func = seriesConfig.func,
drag = d3.behavior.drag(),
dragging = false,
// Define Scales
x = d3.scale.linear(),
y = d3.scale.linear(),
// Define Axes
xAxis = d3.svg.axis().scale(x).outerTickSize(0).orient('bottom').tickFormat(d3.format('.2r')),
yAxis = d3.svg.axis().scale(y).ticks(6).outerTickSize(0).orient('left').tickFormat(fmt),
data = [];
// Create chart
var svg = d3.select(element[0]).append('svg');
var vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var lines = vis.append('g');
// Define Area
var line = d3.svg.line().y(function(d) { return y(d[1]); });
// Create Y Axis SVG Elements
var yTxt = vis.append('g').attr('class', 'y axis')
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -50)
.attr('dy', '.1em')
.style('text-anchor', 'middle')
.text(labels.yAxis.title + ' (' + labels.yAxis.unit + ')');
// Create X Axis SVG Elements
var xLbl = vis.append('g').attr('class', 'x axis');
var xTxt = xLbl.append('text')
.attr('y', 30)
.attr('dy', '.1em')
.style('text-anchor', 'middle')
.text(labels.xAxis.title + ' (' + labels.xAxis.unit + ')');
// Create and Add tooltip
var tipWidth = (Math.max(labels.yAxis.unit.length, labels.xAxis.unit.length) * 1.25) + 2;
var tips = vis.append('g').style('display', 'none');
var background = vis.append('rect') // Background to capture hover/drag
.attr('fill-opacity', 0)
.on('mouseover', showTip)
.on('mouseout', hideTip)
.on('mousemove', moveTip)
.call(drag);
drag
.on('dragstart', function() {
dragging = true;
moveTip.call(this);
showTip();
})
.on('dragend', function() {
dragging = false;
hideTip();
})
.on('drag', moveTip);
/**
* Watch for changes in the series data (mass changes, etc)
*/
scope.$watchCollection('series', render);
angular.element($window).bind('orientationchange resize render', render);
function render() {
var width = element[0].parentElement.offsetWidth,
height = width * 0.5,
xMax = seriesConfig.xMax,
xMin = seriesConfig.xMin,
yMax = seriesConfig.yMax,
yMin = seriesConfig.yMin,
w = width - margin.left - margin.right,
h = height - margin.top - margin.bottom,
s, val, yVal, delta;
data.length = 0; // Reset Data array
if (seriesConfig.xMax == seriesConfig.xMin) {
line.x(function(d, i) { return i * w; });
} else {
line.x(function(d) { return x(d[0]); });
}
if (series) {
for (s = 0; s < series.length; s++) {
data.push([]);
}
if (xMax == xMin) {
yVal = func(xMin);
for (s = 0; s < series.length; s++) {
data[s].push( [ xMin, yVal[ series[s] ] ], [ 1, yVal[ series[s] ] ]);
}
} else {
delta = (xMax - xMin) / 30; // Only render 30 points on the graph
for (val = xMin; val <= xMax; val += delta) {
yVal = func(val);
for (s = 0; s < series.length; s++) {
data[s].push([ val, yVal[ series[s] ] ]);
}
}
}
} else {
var seriesData = [];
if (xMax == xMin) {
yVal = func(xMin);
seriesData.push([ xMin, yVal ], [ 1, yVal ]);
} else {
delta = (xMax - xMin) / 30; // Only render 30 points on the graph
for (val = xMin; val <= xMax; val += delta) {
seriesData.push([val, func(val) ]);
}
}
data.push(seriesData);
}
// Update Chart Size
svg.attr('width', width).attr('height', height);
background.attr('height', h).attr('width', w);
// Update domain and scale for axes
x.range([0, w]).domain([xMin, xMax]).clamp(true);
xLbl.attr('transform', 'translate(0,' + h + ')');
xTxt.attr('x', w / 2);
y.range([h, 0]).domain([yMin, yMax]);
yTxt.attr('x', -h / 2);
vis.selectAll('.y.axis').call(yAxis);
vis.selectAll('.x.axis').call(xAxis);
lines.selectAll('path.line')
.data(data)
.attr('d', line) // Update existing series
.enter() // Add new series
.append('path')
.attr('class', 'line')
.attr('stroke', function(d, i) { return color(i); })
.attr('stroke-width', 2)
.attr('d', line);
var tip = tips.selectAll('g.tooltip').data(data).enter().append('g').attr('class', 'tooltip');
tip.append('rect').attr('width', tipWidth + 'em').attr('height', '2em').attr('x', '0.5em').attr('y', '-1em').attr('class', 'tip');
tip.append('circle').attr('class', 'marker').attr('r', 4);
tip.append('text').attr('class', 'label x').attr('y', '-0.25em');
tip.append('text').attr('class', 'label y').attr('y', '0.85em');
}
function showTip() {
tips.style('display', null);
}
function hideTip() {
if (!dragging) {
tips.style('display', 'none');
}
}
function moveTip() {
var xPos = d3.mouse(this)[0], x0 = x.invert(xPos), y0 = func(x0), flip = (x0 / x.domain()[1] > 0.65);
var tip = tips.selectAll('g.tooltip').attr('transform', function(d, i) { return 'translate(' + x(x0) + ',' + y(series ? y0[series[i]] : y0) + ')'; });
tip.selectAll('rect').attr('x', flip ? (-tipWidth - 0.5) + 'em' : '0.5em').style('text-anchor', flip ? 'end' : 'start');
tip.selectAll('text.label').attr('x', flip ? '-1em' : '1em').style('text-anchor', flip ? 'end' : 'start');
tip.selectAll('text.label.x').text(fmtLong(x0) + ' ' + labels.xAxis.unit);
tips.selectAll('text.label.y').text(function(d, i) { return fmtLong(series ? y0[series[i]] : y0) + ' ' + labels.yAxis.unit; });
}
scope.$on('$destroy', function() {
angular.element($window).unbind('orientationchange resize render', render);
});
}
};
}]);

View File

@@ -189,7 +189,7 @@ angular.module('app').service('Persist', ['$window', 'lodash', function($window,
*/
this.setDiscount = function(val) {
if (this.lsEnabled) {
return localStorage.setItem('discount', val);
return localStorage.setItem('discounts', angular.toJson(val));
}
};
@@ -199,7 +199,7 @@ angular.module('app').service('Persist', ['$window', 'lodash', function($window,
*/
this.getDiscount = function() {
if (this.lsEnabled) {
return localStorage.getItem('discount');
return angular.fromJson(localStorage.getItem('discounts'));
}
return null;
};

View File

@@ -28,7 +28,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
function Ship(id, properties, slots) {
this.id = id;
this.cargoScoop = { c: Components.cargoScoop(), type: 'SYS' };
this.bulkheads = { incCost: true, maxClass: 8, discount: 1 };
this.bulkheads = { incCost: true, maxClass: 8 };
for (var p in properties) { this[p] = properties[p]; } // Copy all base properties from shipData
@@ -36,10 +36,11 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
var slotGroup = slots[slotType];
var group = this[slotType] = []; // Initialize Slot group (Common, Hardpoints, Internal)
for (var i = 0; i < slotGroup.length; i++) {
group.push({ id: null, c: null, incCost: true, maxClass: slotGroup[i], discount: 1 });
group.push({ id: null, c: null, incCost: true, maxClass: slotGroup[i] });
}
}
this.c = { incCost: true, discount: 1, c: { name: this.name, cost: this.cost } }; // Make a 'Ship' component similar to other components
// 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.costList = _.union(this.internal, this.common, this.hardpoints);
this.costList.push(this.bulkheads); // Add The bulkheads
@@ -54,6 +55,9 @@ 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.priorityBands = [
{ deployed: 0, retracted: 0, retOnly: 0 },
{ deployed: 0, retracted: 0, retOnly: 0 },
@@ -81,7 +85,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.ladenMass = 0;
this.armourAdded = 0;
this.shieldMultiplier = 1;
this.totalCost = this.cost;
this.totalCost = this.c.incCost ? this.c.discountedCost : 0;
this.unladenMass = this.mass;
this.armourTotal = this.armour;
@@ -106,6 +110,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
common[i].priority = priorities ? priorities[i + 1] * 1 : 0;
common[i].type = 'SYS';
common[i].c = common[i].id = null; // Resetting 'old' component if there was one
common[i].discountedCost = 0;
this.use(common[i], comps.common[i], Components.common(i, comps.common[i]), true);
}
@@ -119,6 +124,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
hps[i].priority = priorities ? priorities[cl + i] * 1 : 0;
hps[i].type = hps[i].maxClass ? 'WEP' : 'SYS';
hps[i].c = hps[i].id = null; // Resetting 'old' component if there was one
hps[i].discountedCost = 0;
if (comps.hardpoints[i] !== 0) {
this.use(hps[i], comps.hardpoints[i], Components.hardpoints(comps.hardpoints[i]), true);
@@ -133,6 +139,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
internal[i].priority = priorities ? priorities[cl + i] * 1 : 0;
internal[i].type = 'SYS';
internal[i].id = internal[i].c = null; // Resetting 'old' component if there was one
internal[i].discountedCost = 0;
if (comps.internal[i] !== 0) {
this.use(internal[i], comps.internal[i], Components.internal(comps.internal[i]), true);
@@ -149,6 +156,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.updateStats(this.bulkheads, this.bulkheads.c, oldBulkhead, preventUpdate);
};
@@ -171,11 +179,13 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
if (!preventUpdate && similarSlot && similarSlot !== slot) {
this.updateStats(similarSlot, null, similarSlot.c, true); // Update stats but don't trigger a global update
similarSlot.id = similarSlot.c = null; // Empty the slot
similarSlot.discountedCost = 0;
}
}
var oldComponent = slot.c;
slot.id = id;
slot.c = component;
slot.discountedCost = (component && component.cost) ? component.cost * this.componentDiscount : 0;
this.updateStats(slot, component, oldComponent, preventUpdate);
}
};
@@ -232,7 +242,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
Ship.prototype.setCostIncluded = function(item, included) {
if (item.incCost != included && item.c) {
this.totalCost += included ? item.c.cost : -item.c.cost;
this.totalCost += included ? item.discountedCost : -item.discountedCost;
}
item.incCost = included;
};
@@ -292,7 +302,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
}
if (slot.incCost && old.cost) {
this.totalCost -= old.cost;
this.totalCost -= old.cost * this.componentDiscount;
}
if (old.power && slot.enabled) {
@@ -322,7 +332,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
}
if (slot.incCost && n.cost) {
this.totalCost += n.cost;
this.totalCost += n.cost * this.componentDiscount;
}
if (n.power && slot.enabled) {
@@ -377,5 +387,28 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.maxJumpCount = Math.ceil(this.fuelCapacity / fsd.maxfuel);
};
/**
* 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)
*/
Ship.prototype.applyDiscounts = function(shipDiscount, componentDiscount) {
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);
if (item.incCost) {
total += item.discountedCost;
}
}
}
this.shipDiscount = shipDiscount;
this.componentDiscount = componentDiscount;
this.totalCost = total;
};
return Ship;
}]);

View File

@@ -165,6 +165,19 @@ angular.module('shipyard', ['ngLodash'])
fmt: 'fRound'
}
])
/**
* Set of all available / theoretical discounts
*
* @type {Object}
*/
.value('Discounts', {
'None': 1,
'5%': 0.95,
'10%': 0.90,
'15%': 0.85,
'20%': 0.80,
'25%': 0.75
})
/**
* Calculate the maximum single jump range based on mass and a specific FSD
*
@@ -208,9 +221,6 @@ angular.module('shipyard', ['ngLodash'])
* @return {number} Approximate shield strengh in MJ
*/
.value('calcShieldStrength', function(mass, shields, sg, multiplier) {
if (!sg) {
return 0;
}
if (mass <= sg.minmass) {
return shields * multiplier * sg.minmul;
}
@@ -221,4 +231,20 @@ angular.module('shipyard', ['ngLodash'])
return shields * multiplier * (sg.optmul + (mass - sg.optmass) / (sg.maxmass - sg.optmass) * (sg.maxmul - sg.optmul));
}
return shields * multiplier * sg.maxmul;
})
/**
* Calculate the a ships speed based on mass, and thrusters. Currently Innacurate / Incomplete :(
*
* @private
* @param {number} mass Current mass of the ship
* @param {number} baseSpeed Base speed m/s for ship
* @param {number} baseBoost Base boost m/s for ship
* @param {object} thrusters The shield generator used
* @return {object} Approximate speed and boost speed in m/s
*/
.value('calcSpeed', function(mass, baseSpeed, baseBoost) { //, thrusters) {
//var speed = baseSpeed * (1 + ((thrusters.optmass / mass) * 0.1 ) ); // TODO: find thruser coefficient(s)
//var boost = baseBoost * (1 + ((thrusters.optmass / mass) * 0.1 ) );
return { boost: baseSpeed, speed: baseBoost };
});

View File

@@ -15,16 +15,6 @@
width: 100%;
});
.medPhone({
.axis {
font-size: 0.8em;
g.tick:nth-child(2n + 1) text {
display: none;
}
}
});
h3 {
text-align: center;

View File

@@ -39,6 +39,7 @@ header {
}
.smallTablet({
position: static;
position: initial;
});
}

View File

@@ -139,11 +139,6 @@ table.total {
font-weight: normal;
}
tbody tr:hover {
background-color: @warning-bg;
}
.smallTablet({
width: 50%;
});
@@ -152,7 +147,7 @@ table.total {
width: 100%;
});
&.dbl {
&.half {
width: 50%;
.tablet({
@@ -166,20 +161,18 @@ table.total {
});
}
&.semi {
width: 50%;
&.third {
width: 33%;
.smallTablet({
.axis {
font-size: 0.8em;
.axis.x {
g.tick:nth-child(2n + 1) text {
display: none;
}
}
});
.medPhone({
.largePhone({
width: 100% !important;
});
}

View File

@@ -43,7 +43,6 @@ thead {
}
}
tbody tr {
&.tr {
@@ -55,6 +54,12 @@ tbody tr {
}
}
&.highlight {
&:hover {
background-color: @warning-bg;
}
}
td {
line-height: 1.4em;
padding: 0 0.3em;

View File

@@ -53,8 +53,12 @@
<li><select ng-model="insurance.current" ng-options="ins.name for (i,ins) in insurance.opts" ng-change="updateInsurance()"></select></li>
</ul><br>
<ul>
Discount
<li><select ng-model="discounts.current" ng-options="d.name for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
Ship Discount
<li><select ng-model="discounts.ship" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
</ul><br>
<ul>
Component Discount
<li><select ng-model="discounts.components" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
</ul>
<hr />
<ul>

View File

@@ -196,7 +196,7 @@
</div>
</div>
<div class="group dbl" id="componentPriority">
<div class="group half" id="componentPriority">
<table style="width:100%">
<thead>
<tr class="main">
@@ -210,7 +210,7 @@
</thead>
<tbody>
<tr>
<td ng-click="togglePwr(c)">{{pp.c.class}}{{pp.c.rating}}</td>
<td>{{pp.c.class}}{{pp.c.rating}}</td>
<td class="le shorten">Power Plant</td>
<td><u>SYS</u></td>
<td>1</td>
@@ -220,7 +220,7 @@
<td></td>
</tr>
<tr><td style="line-height:0;" colspan="8"><hr style="margin: 0 0 3px;background: #ff8c0d;border: 0;height: 1px;" /></td></tr>
<tr ng-repeat="c in powerList | orderBy:pwrPredicate:pwrDesc" ng-if="c.c.power" ng-class="{disabled:!c.enabled}">
<tr class="highlight" ng-repeat="c in powerList | orderBy:pwrPredicate:pwrDesc" ng-if="c.c.power" ng-class="{disabled:!c.enabled}">
<td style="width:1em;" ng-click="togglePwr(c)">{{c.c.class}}{{c.c.rating}}</td>
<td class="le shorten" ng-click="togglePwr(c)" ng-bind="cName(c)"></td>
<td ng-click="togglePwr(c)"><u ng-bind="c.type"></u></td>
@@ -236,45 +236,58 @@
<div style="margin-top: 1em" power-bands bands="priorityBands" available="ship.powerAvailable"></div>
</div>
<div class="group dbl">
<div class="group half">
<table style="width:100%">
<thead>
<tr class="main">
<th colspan="2" class="sortable le" ng-click="sortCost(cName)">Component</th>
<th class="sortable le" ng-click="sortCost('c.cost')">Credits</th>
<th colspan="2" class="sortable le" ng-click="sortCost(cName)">
Component
<div class="r">
<u ng-if="discounts.ship < 1">[Ship {{fRPct(1 - discounts.ship)}} off]</u>
<u ng-if="discounts.components < 1">[Components {{fRPct(1 - discounts.components)}} off]</u>
</div>
</th>
<th class="sortable le" ng-click="sortCost('discountedCost')">Credits</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="c in costList | orderBy:costPredicate:costDesc" ng-if="c.c.cost > 0" ng-class="{disabled:!c.incCost}">
<td class="toggleable" style="width:1em;" ng-click="toggleCost(c)">{{c.c.class}}{{c.c.rating}}</td>
<td class="le toggleable shorten" ng-bind="cName(c)" ng-click="toggleCost(c)"></td>
<td class="ri toggleable" ng-click="toggleCost(c)">{{fCrd(discounts.current.pct * c.c.cost)}} <u>CR</u></td>
<tr class="highlight" ng-repeat="item in costList | orderBy:costPredicate:costDesc" ng-if="item.c.cost > 0" ng-class="{disabled:!item.incCost}">
<td class="toggleable" style="width:1em;" ng-click="toggleCost(item)">{{item.c.class}}{{item.c.rating}}</td>
<td class="le toggleable shorten" ng-click="toggleCost(item)">{{cName(item)}}</td>
<td class="ri toggleable" ng-click="toggleCost(item)">{{fCrd(item.discountedCost)}} <u>CR</u></td>
</tr>
</tbody>
</table>
<table class="total">
<tr class="ri">
<td class="lbl">Total</td>
<td>{{fCrd(ship.totalCost * discounts.current.pct)}} <u>CR</u></td>
<td>{{fCrd(ship.totalCost)}} <u>CR</u></td>
</tr>
<tr class="ri">
<td class="lbl">Insurance</td>
<td>{{fCrd(ship.totalCost * discounts.current.pct * insurance.current.pct)}} <u>CR</u></td>
<td>{{fCrd(ship.totalCost * insurance.current.pct)}} <u>CR</u></td>
</tr>
</table>
</div>
<div class="group semi">
<div class="group half">
<h1>Jump Range</h1>
<div area-chart config="jrChart" series="jrSeries"></div>
<div line-chart config="jrChart" series="jrSeries"></div>
</div>
<div class="group semi">
<div class="group half">
<h1>Total Range</h1>
<div area-chart config="trChart" series="trSeries"></div>
<div line-chart config="trChart" series="trSeries"></div>
</div>
<div class="group dbl">
<!-- TODO: Add back in once calcSpeed is dynamic and accurate
<div class="group third">
<h1>Thruster Speed</h1>
<div line-chart config="speedChart" series="speedSeries"></div>
</div>
-->
<div class="group half">
<div slider max="ship.fuelCapacity" unit="'T'" on-change="::fuelChange(val)" style="position:relative; margin: 0 auto;">
<svg class="icon xl primary-disabled" style="position:absolute;height: 100%;"><use xlink:href="#fuel"></use></svg>
</div>

View File

@@ -123,28 +123,28 @@
"class": 1,
"rating": "I",
"cost": 1017200,
"mass": 21
"mass": 4
},
{
"name": "Military Grade Composite",
"class": 1,
"rating": "I",
"cost": 2288600,
"mass": 42
"mass": 8
},
{
"name": "Mirrored Surface Composite",
"class": 1,
"rating": "I",
"cost": 5408800,
"mass": 42
"mass": 8
},
{
"name": "Reactive Surface Composite",
"class": 1,
"rating": "I",
"cost": 5993700,
"mass": 42
"mass": 8
}
],
"cobra_mk_iii": [

View File

@@ -1,6 +1,6 @@
{
"name": "coriolis_shipyard",
"version": "0.13.3",
"version": "0.14.0",
"repository": {
"type": "git",
"url": "https://github.com/cmmcleod/coriolis"

View File

@@ -68,6 +68,37 @@ describe("Ship Factory", function() {
}
});
it("discounts hull and components properly", function() {
var id = 'cobra_mk_iii';
var cobra = DB.ships[id];
var testShip = new Ship(id, cobra.properties, cobra.slots);
testShip.buildWith(cobra.defaults);
var originalHullCost = testShip.cost;
var originalTotalCost = testShip.totalCost;
var discount = 0.9;
expect(testShip.c.discountedCost).toEqual(originalHullCost, 'Hull cost does not match');
testShip.applyDiscounts(discount, discount);
// Floating point errors cause miniscule decimal places which are handled in the app by rounding/formatting
expect(Math.floor(testShip.c.discountedCost)).toEqual(Math.floor(originalHullCost * discount), 'Discounted Hull cost does not match');
expect(Math.floor(testShip.totalCost)).toEqual(Math.floor(originalTotalCost * discount), 'Discounted Total cost does not match');
testShip.applyDiscounts(1, 1); // No discount, 100% of cost
expect(testShip.c.discountedCost).toEqual(originalHullCost, 'Hull cost does not match');
expect(testShip.totalCost).toEqual(originalTotalCost, 'Total cost does not match');
testShip.applyDiscounts(discount, 1); // Only discount hull
expect(Math.floor(testShip.c.discountedCost)).toEqual(Math.round(originalHullCost * discount), 'Discounted Hull cost does not match');
expect(testShip.totalCost).toEqual(originalTotalCost - originalHullCost + testShip.c.discountedCost, 'Total cost does not match');
});
it("enforces a single shield generator", function() {
var id = 'anaconda';
var anacondaData = DB.ships[id];