mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-08 22:33:24 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c23fb3884 | ||
|
|
b707015d9c | ||
|
|
10d5611dcd |
@@ -1,6 +1,6 @@
|
||||
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;
|
||||
// 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.
|
||||
@@ -40,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';
|
||||
|
||||
@@ -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;
|
||||
@@ -280,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);
|
||||
});
|
||||
|
||||
}]);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}]);
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -139,11 +139,6 @@ table.total {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: @warning-bg;
|
||||
}
|
||||
|
||||
|
||||
.smallTablet({
|
||||
width: 50%;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -240,26 +240,32 @@
|
||||
<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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "coriolis_shipyard",
|
||||
"version": "0.13.4",
|
||||
"version": "0.14.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cmmcleod/coriolis"
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user