Added Outfitting section sub-menu

This commit is contained in:
Colin McLeod
2015-10-06 22:49:09 -07:00
parent 623be748c0
commit f42dc481df
8 changed files with 245 additions and 67 deletions

View File

@@ -1,3 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M10.063 26l1.8-6h8.274l1.8 6h3.551l-6-20h-6.976l-6 20h3.551zM14.863 10h2.274l1.8 6h-5.874l1.8-6z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 265 B

View File

@@ -1,6 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<path d="M18 23l3 3 10-10-10-10-3 3 7 7z"></path>
<path d="M14 9l-3-3-10 10 10 10 3-3-7-7z"></path>

Before

Width:  |  Height:  |  Size: 419 B

After

Width:  |  Height:  |  Size: 248 B

3
app/icons/equalizer.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<path d="M448 128v-16c0-26.4-21.6-48-48-48h-160c-26.4 0-48 21.6-48 48v16h-192v128h192v16c0 26.4 21.6 48 48 48h160c26.4 0 48-21.6 48-48v-16h576v-128h-576zM256 256v-128h128v128h-128zM832 432c0-26.4-21.6-48-48-48h-160c-26.4 0-48 21.6-48 48v16h-576v128h576v16c0 26.4 21.6 48 48 48h160c26.4 0 48-21.6 48-48v-16h192v-128h-192v-16zM640 576v-128h128v128h-128zM448 752c0-26.4-21.6-48-48-48h-160c-26.4 0-48 21.6-48 48v16h-192v128h192v16c0 26.4 21.6 48 48 48h160c26.4 0 48-21.6 48-48v-16h576v-128h-576v-16zM256 896v-128h128v128h-128z"></path>
</svg>

After

Width:  |  Height:  |  Size: 646 B

View File

@@ -1,3 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="724" height="1024" viewBox="0 0 1024 1024">
<path d="M0 1024c128-384 463-1024 1024-1024-263 211-384 704-576 704s-192 0-192 0l-192 320h-64z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 218 B

View File

@@ -13,6 +13,7 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
}
$scope.buildName = $p.bn;
$scope.ships = Ships;
$rootScope.title = ship.name + ($scope.buildName ? ' - ' + $scope.buildName : '');
$scope.ship = ship;
$scope.pp = ship.common[0]; // Power Plant
@@ -27,7 +28,7 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
$scope.costList = ship.costList;
$scope.powerList = ship.powerList;
$scope.priorityBands = ship.priorityBands;
$scope.availCS = Components.forShip(ship.id);
$scope.availCS = ship.getAvailableComponents();
$scope.selectedSlot = null;
$scope.savedCode = Persist.getBuild(ship.id, $scope.buildName);
$scope.canSave = Persist.isEnabled();
@@ -59,7 +60,7 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
yMax: ship.unladenRange,
yMin: 0,
func: function(cargo) { // X Axis is Cargo
return ship.jumpRangeWithMass(ship.unladenMass + $scope.fuel + cargo, $scope.fuel);
return ship.getJumpRangeForMass(ship.unladenMass + $scope.fuel + cargo, $scope.fuel);
}
};
$scope.jrChart = {
@@ -184,24 +185,28 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
}
};
$scope.resetBuild = function() {
ship.buildWith(data.defaults); // Populate with default components
updateState(null);
};
/**
* Strip ship to A-class and biggest A-class shield generator with military bulkheads
*/
$scope.aRatedBuild = function() {
for (var i = 0, l = ship.common.length - 1; i < l; i++) { // All except Fuel Tank
var id = ship.common[i].maxClass + 'A';
ship.use(ship.common[i], id, Components.common(i, id));
}
ship.hardpoints.forEach(function(slot) { ship.use(slot, null, null); });
ship.internal.forEach(function(slot) { ship.use(slot, null, null); });
ship
.useBulkhead(2) // Military Composite
.useCommon('A')
.emptyHardpoints()
.emptyInternal();
ship.internal.some(function(slot) {
if (typeof slot.eligible === 'undefined') { // Assuming largest slot can hold an eligible shield
id = Components.findInternalId('Shield Generator', slot.maxClass, 'A');
ship.use(slot, id, Components.internal(id));
if (!slot.eligible || slot.eligible.sg) { // Assuming largest slot can hold an eligible shield
var sg = Components.findInternal('sg', slot.maxClass, 'A');
ship.use(slot, sg.id, sg);
return true;
}
});
ship.useBulkhead(2);
updateState(Serializer.fromShip(ship));
};
@@ -210,21 +215,115 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
* without power management.
*/
$scope.optimizeMassBuild = function() {
var common = ship.common;
ship.hardpoints.forEach(function(slot) { ship.use(slot, null, null); });
ship.internal.forEach(function(slot) { ship.use(slot, null, null); });
ship.useBulkhead(0);
ship.use(common[2], common[2].maxClass + 'A', Components.common(2, common[2].maxClass + 'A')); // FSD
ship.use(common[3], common[3].maxClass + 'D', Components.common(3, common[3].maxClass + 'D')); // Life Support
ship.use(common[5], common[5].maxClass + 'D', Components.common(5, common[5].maxClass + 'D')); // Sensors
updateState(Serializer.fromShip(ship.optimizeMass()));
};
var pd = $scope.availCS.lightestPowerDist(ship.boostEnergy); // Find lightest Power Distributor that can still boost
ship.use(ship.common[4], pd, Components.common(4, pd));
var th = $scope.availCS.lightestThruster(ship.ladenMass); // Find lightest Thruster that still works for the ship at max mass
ship.use(ship.common[1], th, Components.common(1, th));
var pp = $scope.availCS.lightestPowerPlant(ship.powerRetracted); // Find lightest Power plant that can power the ship
ship.use(ship.common[0], pp, Components.common(0, pp));
/**
* Optimize for the lower mass build that can still boost and power the ship
* without power management.
*/
$scope.optimizeCommon = function() {
updateState(Serializer.fromShip(ship.useLightestCommon()));
};
$scope.useCommon = function(rating) {
updateState(Serializer.fromShip(ship.useCommon(rating)));
};
$scope.emptyInternal = function() {
updateState(Serializer.fromShip(ship.emptyInternal()));
};
$scope.emptyWeapons = function() {
updateState(Serializer.fromShip(ship.emptyWeapons()));
};
$scope.emptyUtility = function() {
updateState(Serializer.fromShip(ship.emptyUtility()));
};
$scope.fillWithCargo = function() {
ship.internal.forEach(function(slot) {
var id = Components.findInternalId('cr', slot.maxClass, 'E');
ship.use(slot, id, Components.internal(id));
});
updateState(Serializer.fromShip(ship));
};
/**
* Fill all internal slots with Cargo Racks, and optmize internal components.
* Hardpoints are not altered.
*/
$scope.optimizeCargo = function() {
ship.internal.forEach(function(slot) {
var id = Components.findInternalId('cr', slot.maxClass, 'E');
ship.use(slot, id, Components.internal(id));
});
ship.useLightestCommon();
updateState(Serializer.fromShip(ship));
};
/**
* Optimize common and internal components, hardpoints for exploration
*/
$scope.optimizeExplorer = function() {
var intLength = ship.internal.length,
heatSinkCount = 2, // Fit 2 heat sinks if possible
afmUnitCount = 2, // Fit 2 AFM Units if possible
sgSlot,
fuelScoopSlot,
sgId = $scope.availCS.lightestShieldGenerator(ship.hullMass),
sg = Components.internal(sgId);
ship.setSlotEnabled(ship.cargoHatch, false)
.use(ship.internal[--intLength], '2f', Components.internal('2f')) // Advanced Discovery Scanner
.use(ship.internal[--intLength], '2i', Components.internal('2i')); // Detailed Surface Scanner
for (var i = 0; i < intLength; i++) {
var slot = ship.internal[i];
var nextSlot = (i + 1) < intLength ? ship.internal[i + 1] : null;
if (!fuelScoopSlot && (!slot.eligible || slot.eligible.fs)) { // Fit best possible Fuel Scoop
var fuelScoopId = Components.findInternalId('fs', slot.maxClass, 'A');
fuelScoopSlot = slot;
ship.use(fuelScoopSlot, fuelScoopId, Components.internal(fuelScoopId));
ship.setSlotEnabled(fuelScoopSlot, true);
// Mount a Shield generator if possible AND an AFM Unit has been mounted already (Guarantees at least 1 AFM Unit)
} else if (!sgSlot && afmUnitCount < 2 && sg.class <= slot.maxClass && (!slot.eligible || slot.eligible.sg) && (!nextSlot || nextSlot.maxClass < sg.class)) {
sgSlot = slot;
ship.use(sgSlot, sgId, sg);
ship.setSlotEnabled(sgSlot, true);
} else if (afmUnitCount > 0 && (!slot.eligible || slot.eligible.am)) {
afmUnitCount--;
var id = Components.findInternalId('am', slot.maxClass, 'A'); // Best AFM Unit for slot
ship.use(slot, id, Components.internal(id));
ship.setSlotEnabled(slot, false); // Disabled power for AFM Unit
} else {
ship.use(slot, null, null);
}
}
ship.hardpoints.forEach(function(s) {
if (s.maxClass == 0 && heatSinkCount) { // Mount up to 2 heatsinks
ship.use(s, '02', Components.hardpoints('02'));
ship.setSlotEnabled(s, heatSinkCount == 2); // Only enable a single Heatsink
heatSinkCount--;
} else {
ship.use(s, null, null);
}
});
if (sgSlot) {
// The SG and Fuel scoop to not need to be powered at the same time
if (sgSlot.c.power > fuelScoopSlot.c.power) { // The Shield generator uses the most power
ship.setSlotEnabled(fuelScoopSlot, false);
} else { // The Fuel scoop uses the most power
ship.setSlotEnabled(sgSlot, false);
}
}
ship.useLightestCommon({ pd: '1D', ppRating: 'A' });
updateState(Serializer.fromShip(ship));
};
@@ -290,6 +389,15 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
ship.setCostIncluded(item, !item.incCost);
};
/**
* Toggle cost of the selected component for retrofitting comparison
* @param {object} item The component being toggled
*/
$scope.toggleRetrofitCost = function(item) {
retrofitShip.setCostIncluded(item, !item.incCost);
updateRetrofitCosts();
};
/**
* [sortCost description]
* @param {[type]} key [description]
@@ -353,6 +461,19 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
updateRetrofitCosts();
};
$scope.updateCostTab = function(tab) {
Persist.setCostTab(tab);
$scope.costTab = tab;
};
$scope.ppWarning = function(pp) {
return pp.pGen < ship.powerRetracted;
};
$scope.pdWarning = function(pd) {
return pd.enginecapacity < ship.boostEnergy;
};
// Utilify functions
function updateState(code) {
@@ -376,10 +497,13 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
buyName: ship.bulkheads.c.name,
sellClassRating: retrofitShip.bulkheads.c.class + retrofitShip.bulkheads.c.rating,
sellName: retrofitShip.bulkheads.c.name,
netCost: ship.bulkheads.discountedCost - retrofitShip.bulkheads.discountedCost
netCost: ship.bulkheads.discountedCost - retrofitShip.bulkheads.discountedCost,
retroItem: retrofitShip.bulkheads
};
costs.push(item);
total += item.netCost;
if (retrofitShip.bulkheads.incCost) {
total += item.netCost;
}
}
for (var g in { common: 1, internal: 1, hardpoints: 1 }) {
@@ -387,7 +511,7 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
var slotGroup = ship[g];
for (i = 0, l = slotGroup.length; i < l; i++) {
if (slotGroup[i].id != retroSlotGroup[i].id) {
item = { netCost: 0 };
item = { netCost: 0, retroItem: retroSlotGroup[i] };
if (slotGroup[i].id) {
item.buyName = slotGroup[i].c.name || slotGroup[i].c.grp;
item.buyClassRating = slotGroup[i].c.class + slotGroup[i].c.rating;
@@ -399,26 +523,15 @@ angular.module('app').controller('OutfitController', ['$window', '$rootScope', '
item.netCost -= retroSlotGroup[i].discountedCost;
}
costs.push(item);
total += item.netCost;
if (retroSlotGroup[i].incCost) {
total += item.netCost;
}
}
}
}
$scope.retrofitTotal = total;
}
$scope.updateCostTab = function(tab) {
Persist.setCostTab(tab);
$scope.costTab = tab;
};
$scope.ppWarning = function(pp) {
return pp.pGen < ship.powerRetracted;
};
$scope.pdWarning = function(pd) {
return pd.enginecapacity < ship.boostEnergy;
};
// Hide any open menu/slot/etc if the background is clicked
$scope.$on('close', function() {
$scope.selectedSlot = null;

View File

@@ -51,6 +51,31 @@
transform: scaleX(-1); /* standard */
}
.section-menu {
position: relative;
z-index: 1;
&.selected {
h1 {
background-color: @primary;
}
}
h1 {
cursor: pointer;
}
.icon {
float: right;
margin: 0.1em 0.3em 0 0;
}
.select {
box-sizing: border-box;
left: 0;
}
}
#build {
float: right;
line-height: 2em;

View File

@@ -87,7 +87,7 @@ select {
.lc, .c {
border:1px solid @primary-disabled;
padding: 0.1em 0.2em;
padding: 0.1em 0.25em;
margin: 0.3em;
&.warning {

View File

@@ -13,15 +13,9 @@
<button class="danger" ng-click="deleteBuild()" ng-disabled="!savedCode">
<svg class="icon lg"><use xlink:href="#bin"></use></svg>
</button>
<button ui-sref="outfit({shipId: ship.id,code:null, bn: buildName})" ng-disabled="!code">
<button ng-click="resetBuild()" ng-disabled="!code">
<svg class="icon lg"><use xlink:href="#switch"></use></svg><span class="button-lbl" translate="reset"></span>
</button>
<button ng-click="aRatedBuild()">
<svg class="icon lg"><use xlink:href="#a"></use></svg><span class="button-lbl" translate="A-Rated"></span>
</button>
<button ng-click="optimizeMassBuild()">
<svg class="icon lg"><use xlink:href="#feather"></use></svg><span class="button-lbl" translate="optimize mass"></span>
</button>
<button ng-click="exportBuild($event)" ng-disabled="!buildName">
<svg class="icon lg"><use xlink:href="#download"></use></svg><span class="button-lbl" translate="export"></span>
</button>
@@ -60,7 +54,7 @@
</thead>
<tbody>
<tr>
<td class="cap" ng-bind="['','small','medium','large','capital'][ship.class] | translate"></td>
<td class="cap" ng-bind="SZM[ship.class] | translate"></td>
<td>{{ship.agility}}/10</td>
<td>
<span ng-if="th.c.maxmass >= ship.ladenMass">{{fCrd(ship.topSpeed)}} <u translate>m/s</u></span>
@@ -98,7 +92,28 @@
</div>
<div id="standard" class="group">
<h1 translate="standard"></h1>
<div class="section-menu" ng-class="{selected: selectedSlot=='standard'}" context-menu="optimizeCommon()" ng-click="selectSlot($event, 'standard')">
<h1>
{{'standard' | translate}}
<svg class="icon"><use xlink:href="#equalizer"></use></svg>
</h1>
<div class="select" ng-if="selectedSlot=='standard'">
<ul>
<li class="c" ng-click="useCommon('E')">E</li>
<li class="c" ng-click="useCommon('D')">D</li>
<li class="c" ng-click="useCommon('C')">C</li>
<li class="c" ng-click="useCommon('D')">B</li>
<li class="c" ng-click="useCommon('A')">A</li>
<li class="lc" ng-click="optimizeCommon()" translate="Optimize"></li>
</ul>
<div class="select-group cap" translate="builds / roles"></div>
<ul>
<li class="lc" ng-click="aRatedBuild()" translate="A-Rated"></li>
<li class="lc" ng-click="optimizeCargo()" translate="Trader"></li>
<li class="lc" ng-click="optimizeExplorer()" translate="Explorer"></li>
</ul>
</div>
</div>
<div class="slot" ng-click="selectSlot($event, ship.bulkheads)" ng-class="{selected: selectedSlot==ship.bulkheads}">
<div class="details">
<div class="sz"><span>8</span></div>
@@ -192,7 +207,18 @@
</div>
<div id="internal" class="group">
<h1 translate="internal compartments"></h1>
<div class="section-menu" ng-class="{selected: selectedSlot=='internal'}" context-menu="emptyInternal()" ng-click="selectSlot($event, 'internal')">
<h1>
{{'internal compartments' | translate}}
<svg class="icon"><use xlink:href="#equalizer"></use></svg>
</h1>
<div class="select" ng-if="selectedSlot=='internal'">
<ul>
<li class="lc" ng-click="emptyInternal()" translate="Empty"></li>
<li class="lc" ng-click="fillWithCargo()" translate="Cargo"></li>
</ul>
</div>
</div>
<div class="slot" ng-repeat="i in ship.internal" ng-click="selectSlot($event, i)" context-menu="select('i', i, $event, 'empty')" ng-class="{selected: selectedSlot==i}">
<div slot-internal class="details" slot="i" fuel="ship.fuelCapacity"></div>
<div class="select" ng-if="selectedSlot==i" ng-click="select('i',i,$event)">
@@ -202,7 +228,17 @@
</div>
<div id="hardpoints" class="group">
<h1 translate="hardpoints"></h1>
<div class="section-menu" ng-class="{selected: selectedSlot=='hardpoints'}" context-menu="emptyWeapons()" ng-click="selectSlot($event, 'hardpoints')">
<h1>
{{'hardpoints' | translate}}
<svg class="icon"><use xlink:href="#equalizer"></use></svg>
</h1>
<div class="select" ng-if="selectedSlot=='hardpoints'">
<ul>
<li class="lc" ng-click="emptyWeapons()" translate="Empty"></li>
</ul>
</div>
</div>
<div class="slot" ng-repeat="h in ship.hardpoints | filter:{maxClass: '!0'}" ng-click="selectSlot($event, h)" context-menu="select('h', h, $event, 'empty')" ng-class="{selected: selectedSlot==h}">
<div slot-hardpoint class="details" hp="h" size="HPC[h.maxClass]"></div>
<div class="select" ng-class="{hardpoint: h.maxClass > 0}" ng-if="selectedSlot==h" ng-click="select('h',h,$event)">
@@ -212,7 +248,17 @@
</div>
<div id="utility" class="group">
<h1 translate="utility mounts"></h1>
<div class="section-menu" ng-class="{selected: selectedSlot=='utility'}" context-menu="emptyUtility()" ng-click="selectSlot($event, 'utility')">
<h1>
{{'utility mounts' | translate}}
<svg class="icon"><use xlink:href="#equalizer"></use></svg>
</h1>
<div class="select" ng-if="selectedSlot=='utility'">
<ul>
<li class="lc" ng-click="emptyUtility()" translate="Empty"></li>
</ul>
</div>
</div>
<div class="slot" ng-repeat="h in ship.hardpoints | filter:{maxClass: '0'}" ng-click="selectSlot($event, h)" context-menu="select('h', h, $event, 'empty')" ng-class="{selected: selectedSlot==h}">
<div slot-hardpoint class="details" hp="h" size="HPC[h.maxClass]"></div>
<div class="select" ng-class="{hardpoint: h.maxClass > 0}" ng-if="selectedSlot==h" ng-click="select('h',h,$event)">
@@ -327,12 +373,12 @@
<tr ng-if="!retrofitList || retrofitList.length == 0">
<td colspan="5" style="padding: 3em 0;" translate="PHRASE_NO_RETROCH"></td>
</tr>
<tr class="highlight" ng-repeat="item in retrofitList | orderBy:retroPredicate:retroDesc">
<td style="width:1em;">{{item.sellClassRating}}</td>
<tr class="highlight" ng-repeat="item in retrofitList | orderBy:retroPredicate:retroDesc" ng-click="toggleRetrofitCost(item.retroItem)" ng-class="{disabled: !item.retroItem.incCost}">
<td style="width:1em;">{{item.sellClassRating}}</td>
<td class="le shorten cap">{{item.sellName | translate}}</td>
<td style="width:1em;">{{item.buyClassRating}}</td>
<td class="le shorten cap">{{item.buyName | translate}}</td>
<td class="ri" ng-class="item.netCost > 0 ? 'warning' : 'secondary-disabled'">{{ fCrd(item.netCost)}} <u translate>CR</u></td>
<td class="ri" ng-class="item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled'">{{ fCrd(item.netCost)}} <u translate>CR</u></td>
</tr>
</tbody>
</table>