Refactoring of just about everything, interface beginning to come together

This commit is contained in:
Colin McLeod
2015-04-28 00:46:51 -07:00
parent 7d527cdfef
commit f736a078ec
112 changed files with 3626 additions and 1210 deletions

View File

@@ -1,12 +1,25 @@
angular.module('app', ['ngRoute','shipyard','ngLodash','app.templates'])
.config(['$routeProvider', function($routeProvider) {
//$locationProvider.html5Mode(true);
angular.module('app', ['ngRoute', 'shipyard', 'ngLodash', 'n3-line-chart', 'app.templates'])
.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider
.when('/:ship', { templateUrl: 'views/ship.html', controller: 'ShipController' })
.when('/:ship/:code', { templateUrl: 'views/ship.html', controller: 'ShipController' })
.when('/', { templateUrl: 'views/ships.html', controller: 'ShipyardController' });
}])
.run(['$rootScope','commonArray','shipPurpose', 'shipSize', 'hardPointClass', 'internalGroupMap', function ($rootScope, CArr, shipPurpose, sz, hpc, igMap) {
.run(['$rootScope','$document','$location','$route','commonArray','shipPurpose','shipSize','hardPointClass','internalGroupMap', function ($rootScope, $doc, $loc, $route, CArr, shipPurpose, sz, hpc, igMap) {
// Allow URL changes without reloading controllers/view
var original = $loc.path;
$loc.path = function (path, reload) {
if (reload === false) {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
}
return original.apply($loc, [path]);
};
// Global Reference variables
$rootScope.CArr = CArr;
@@ -19,19 +32,18 @@ angular.module('app', ['ngRoute','shipyard','ngLodash','app.templates'])
// Formatters
$rootScope.fCrd = d3.format(',.0f');
$rootScope.fPwr = d3.format(',.2f');
$rootScope.fMass = d3.format(',.2r');
$rootScope.fPct = d3.format(',.2%');
$rootScope.fRound = function(d) { return d3.round(d, 2) };
$rootScope.fPct = d3.format('.2%');
$rootScope.fRPct = d3.format('%');
$rootScope.fTime = function(d) { return Math.floor(d/60) + ":" + ("00" + (d%60)).substr(-2,2); };
$rootScope.calcJumpRange = function(mass, fsd, fuel) {
return Math.pow( (fuel || fsd.maxfuel) / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass;
};
// Global Event Listeners
$doc.bind('keyup', function (e) {
$rootScope.$broadcast('keyup', e);
});
// TODO: Load Saved Ships List from Local Storage
// TODO: Save Ship
// TODO: Load Ship
// TODO: Generate Link for Ship
$rootScope.bgClicked = function (e) {
$rootScope.$broadcast('bgClicked', e);
}
}]);

View File

@@ -1,10 +1,61 @@
angular.module('app')
.controller('ShipController', ['$scope', '$routeParams','ShipFactory', 'components', function ($scope, $p, ShipFactory, Components) {
.controller('ShipController', ['$rootScope','$scope', '$routeParams', '$location', 'ShipFactory', 'components', function ($rootScope, $scope, $p, $loc, ShipFactory, Components) {
$scope.shipId = $p.ship;
$scope.ship = ShipFactory($scope.shipId, DB.ships[$scope.shipId]);
$scope.availCS = Components.forShip($scope.shipId);
// TODO: show 404 if ship not found.
var ship = ShipFactory($scope.shipId, DB.ships[$scope.shipId], $p.code);
$scope.ship = ship;
$scope.pp = ship.common[0]; // Power Plant
$scope.th = ship.common[1]; // Thruster
$scope.fsd = ship.common[2]; // Frame Shrift Drive
$scope.ls = ship.common[3]; // Life Support
$scope.pd = ship.common[4]; // Power Distributor
$scope.ss = ship.common[5]; // Sensors
$scope.ft = ship.common[6]; // Fuel Tank
$scope.hps = ship.hardpoints;
$scope.internal = ship.internal;
$scope.availCS = Components.forShip($scope.shipId);
$scope.selectedSlot = null;
// for debugging
//window.ship = $scope.ship;
//window.availcs = $scope.availCS;
}]);
window.ship = ship;
window.availcs = $scope.availCS;
$scope.selectSlot = function(e, slot) {
e.stopPropagation();
if ($scope.selectedSlot == slot) {
$scope.selectedSlot = null;
} else {
$scope.selectedSlot = slot;
}
};
$scope.selectComponent = function(slot, id, component) {
ship.use(slot, id, component);
$scope.selectedSlot = null;
$loc.path(ship.id + '/' + ship.code, false).replace();
}
$scope.hideMenus = function() {
$scope.selectedSlot = null;
}
$rootScope.$on('keyup', function (e, keyEvent) {
if(keyEvent.keyCode == 27) { // on Escape
$scope.hideMenus();
$scope.$apply();
}
// TODO: CTRL+S -> Save
});
$rootScope.$on('bgClicked', function (e, keyEvent) {
$scope.hideMenus();
});
// TODO: Save build
// TODO: name build + save
// Push new url history in this case
// TODO: delete build
// TODO: reset to ship defaults
// TODO: revert to last save
}]);

View File

@@ -2,15 +2,14 @@ angular.module('app').directive('componentSelect', [ function() {
return {
restrict: 'A',
scope:{
opts: '=',
c: '=',
ship: '='
opts: '=', // Component Options object
slot: '=', // Slot Object
selectComponent: '&sc' // Select Component function
},
templateUrl: 'views/component_select.html',
link: function (scope) {
scope.use = function(id, componentData) {
scope.ship.use(scope.c, id, componentData);
// hide this shit;
scope.use = function(id, component) {
scope.selectComponent({s: scope.slot, id: id, c: component});
};
}
};

View File

@@ -0,0 +1,14 @@
angular.module('app').directive('hardpoint', ['$rootScope', function ($r) {
return {
restrict: 'A',
scope:{
hp: '=',
size: '=',
opts: '='
},
templateUrl: 'views/hardpoint.html',
link: function (scope) {
scope.$r = $r;
}
};
}]);

View File

@@ -6,6 +6,7 @@ angular.module('app').directive('costList', ['$rootScope', function ($r) {
},
templateUrl: 'views/costs.html',
link: function (scope) {
scope.expanded = false;
scope.$r = $r;
scope.insuranceOptions = {
Alpha: 0.975,
@@ -14,6 +15,10 @@ angular.module('app').directive('costList', ['$rootScope', function ($r) {
};
scope.insurance = scope.insuranceOptions.Standard;
scope.toggleExpand = function() {
scope.expanded = !scope.expanded;
}
scope.toggle = function(item) {
item.incCost = !item.incCost;
scope.ship.updateTotals();

View File

@@ -7,8 +7,13 @@ angular.module('app')
},
templateUrl: 'views/power.html',
link: function (scope) {
scope.expanded = false;
scope.$r = $r;
scope.toggleExpand = function() {
scope.expanded = !scope.expanded;
}
scope.toggle = function(slot) {
slot.enabled = !slot.enabled;
scope.ship.updateTotals();

View File

@@ -0,0 +1,77 @@
angular.module('app').directive('meter', function () {
return {
restrict: 'A',
scope: {
labels: '=',
keys: '=',
obj: '=',
max: '='
},
link: function (scope, element) {
var max = scope.max,
w = 90,
pLeft = 1,
pBottom = 2,
labelWidth = 45,
bHeight = 16,
bWidth = ((w - labelWidth) / max) - pLeft,
h = bHeight * scope.keys.length;
var data = [];
for(var i = 0; i < scope.keys.length; i++) {
data.push({name:scope.labels[i], val: scope.obj[scope.keys[i]]});
}
var svg = d3.select(element[0])
.append('svg')
.attr('width', w)
.attr('height', h)
.attr('viewBox', '0 0 ' + w + ' ' + h)
.attr('class', 'meter')
.attr('preserveAspectRatio', 'xMinYMin');
svg.selectAll("g").data(data)
.enter()
.append("g")
.attr('transform', function(d, i) {
return 'translate(' + labelWidth + ' ' + (i * bHeight) + ')';
})
.each(function(d, k) {
var g = d3.select(this);
for (var i = 0; i < max; i++) {
g.append('rect')
.attr("x", i * (bWidth + pLeft))
.attr("y", 0)
.attr("width", bWidth)
.attr("height", bHeight - pBottom);
}
});
svg.selectAll("text").data(data)
.enter()
.append('text')
.text(function(d) {
return d.name;
})
.attr("text-anchor", "end")
.attr("x", labelWidth - 3)
.attr("y", function(d, i) {
return (i * bHeight) + (bHeight) / 2;
});
function update() {
for(var i = 0; i < data.length; i++) {
data[i].val = scope.obj[scope.keys[i]];
}
svg.selectAll("g").data(data)
.selectAll('rect').attr('class', function(d, i) {
return (i + 1 <= d.val) ? 'active' : '';
});
}
scope.$watch('obj',update);
}
};
});

View File

@@ -0,0 +1,52 @@
angular.module('app').directive('shipRange', ['$rootScope','CalcJumpRange', function ($r, calcJumpRange) {
return {
restrict: 'A',
scope:{
ship: '='
},
templateUrl: 'views/ship-range.html',
link: function(scope, element) {
scope.$r = $r;
scope.expanded = false;
var fsd = scope.ship.common[2].c;
scope.toggleExpand = function() {
scope.expanded = !scope.expanded;
}
function ranges(fsd, unladenMass, ladenMass) {
var ranges = [];
for(var m = unladenMass; m <= ladenMass; m++) {
ranges.push({x:m, y: calcJumpRange(m, fsd)});
}
return ranges;
}
//var fDist = d3.format(',.2f');
//scope.data = ranges(fsd, scope.ship.unladenMass, scope.ship.ladenMass);
/*scope.options = {
axes: {
x: {key: 'x', type: 'linear', ticks: 10},
y: {type: 'linear', ticks: 5, }
},
series: [
{y: 'y', color: '#FF8C0D', thickness: '2px', type: 'area', striped: false, label: 'Range'}
],
lineMode: 'basis',
tension: 0.7,
tooltip: {
mode: 'scrubber',
formatter: function(x, y, series) {
return fDist(y) + ' Light Years';
}
},
drawLegend: false,
drawDots: false,
columnsHGap: 5
};*/
}
};
}]);

View File

@@ -1,4 +1,4 @@
angular.module('app').directive('slotDetails', function () {
angular.module('app').directive('slotDetails', ['$rootScope', function ($r) {
return {
restrict: 'A',
scope:{
@@ -6,6 +6,9 @@ angular.module('app').directive('slotDetails', function () {
lbl: '=',
opts: '='
},
templateUrl: 'views/slot.html'
templateUrl: 'views/slot.html',
link: function(scope) {
scope.$r = $r;
}
};
});
}]);

View File

@@ -65,6 +65,12 @@ angular.module('shipyard').factory('components', ['lodash', function (_) {
return {
forShip: function (shipId) {
return new ComponentSet(shipId);
},
findInternal: function(id) {
var c = _.find(C.internal, function(o) {
return o[id];
})
return c[id];
}
};

View File

@@ -1,4 +1,4 @@
angular.module('shipyard').factory('ShipFactory', ['components', 'lodash', function (Components, _) {
angular.module('shipyard').factory('ShipFactory', ['components', 'CalcShieldStrength', 'CalcJumpRange', 'lodash', function (Components, calcShieldStrength, calcJumpRange, _) {
/**
* Ship model used to track all ship components and properties.
@@ -13,26 +13,23 @@ angular.module('shipyard').factory('ShipFactory', ['components', 'lodash', funct
this.cargoScoop = { enabled: true, c: { name: 'Cargo Scoop', class: 1, rating: 'H', power: 0.6} };
this.sgSI = null; // Shield Generator Slot Index
// Copy all base properties from shipData
angular.forEach(shipData,function(o,k){
if(typeof o != 'object') {
this[k] = o;
} else if (k == 'slotCap') {
angular.forEach(o,function(arr,g){
this[g] = [];
for(var i = 0; i < arr.length; i++){
this[g].push({
enabled: true,
incCost: true,
maxClass: arr[i]
});
}
}.bind(this));
}
}.bind(this));
angular.forEach(shipData.slotCap, function (slots, slotGroup) { // Initialize all slots
this[slotGroup] = []; // Initialize Slot group (Common, Hardpoints, Internal)
for(var i = 0; i < slots.length; i++){
this[slotGroup].push({id: null, c: null, enabled: true, incCost: true, maxClass: slots[i]});
}
}.bind(this));
}
/**
* Reset the ship to the original purchase defaults.
* Reset the ship to the original 'manufacturer' defaults.
*/
Ship.prototype.clear = function() {
this.buildWith(DB.ships[this.id].defaultComponents);
@@ -58,7 +55,7 @@ angular.module('shipyard').factory('ShipFactory', ['components', 'lodash', funct
var availInternal = DB.components.internal;
var i,l;
this.bulkheads = { incCost: true, id: comps.bulkheads || 0, c: DB.components.bulkheads[this.id][comps.bulkheads || 0] };
this.bulkheads = { incCost: true, maxClass: 8, id: comps.bulkheads || 0, c: DB.components.bulkheads[this.id][comps.bulkheads || 0] };
for(i = 0, l = comps.common.length; i < l; i++) {
common[i].id = comps.common[i];
@@ -75,9 +72,13 @@ angular.module('shipyard').factory('ShipFactory', ['components', 'lodash', funct
for(i = 0, l = comps.internal.length; i < l; i++) {
if(comps.internal[i] !== 0) {
internal[i].id = comps.internal[i];
internal[i].c = availInternal[comps.internal[i]];
internal[i].c = Components.findInternal(comps.internal[i]);
if(internal[i].c.group == 'sg') {
this.sgSI = i;
}
}
}
this.code = this.toCode();
this.updateTotals();
};
@@ -105,7 +106,7 @@ angular.module('shipyard').factory('ShipFactory', ['components', 'lodash', funct
* @return {string} The id of the selected component or '-' if none selected
*/
function idToStr(slot) {
return slot.id === undefined? '-' : slot.id;
return (slot.id === null)? '-' : slot.id;
}
/**
@@ -126,97 +127,53 @@ angular.module('shipyard').factory('ShipFactory', ['components', 'lodash', funct
// TODO: improve...
for (var i = 1, c = 0, l = code.length; i < l; i++) {
if(code.charAt(i) != '-') {
if (c < commonCount) {
comps.common[c] = code.substring(i, i + 2);
} else if (c < hpCount) {
comps.hardpoints[c - commonCount] = code.substring(i, i + 2);
} else {
comps.internal[c - hpCount] = code.substring(i, i + 2);
}
var isNull = code.charAt(i) == '-';
if (c < commonCount) {
comps.common[c] = isNull? 0 : code.substring(i, i + 2);
} else if (c < hpCount) {
comps.hardpoints[c - commonCount] = isNull? 0 : code.substring(i, i + 2);
} else {
comps.internal[c - hpCount] = isNull? 0 : code.substring(i, i + 2);
}
if (!isNull) {
i++;
}
c++;
}
this.defaults = comps;
this.buildWidth(comps);
this.buildWith(comps);
};
/**
* Updates the ship totals based on currently selected component in each slot.
* 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, capacity: 0});
var i = _.reduce(this.internal, optsSum, {cost: 0, power: 0, mass: 0, capacity: 0});
var h = _.reduce(this.hardpoints, optsSum, {cost: 0, power: 0, mass: 0, capacity: 0});
var i = _.reduce(this.internal, optsSum, {cost: 0, power: 0, mass: 0, capacity: 0, armouradd: 0});
var h = _.reduce(this.hardpoints, optsSum, {cost: 0, power: 0, mass: 0, shieldmul: 1});
var fsd = this.common[2].c; // Frame Shift Drive;
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;
this.fuelCapacity = this.common[6].c.capacity;
this.maxMass = this.common[1].c.maxmass;
this.cargoCapacity = i.capacity;
this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity;
this.powerRetracted = c.power + i.power + (this.cargoScoop.enabled? this.cargoScoop.c.power : 0);
this.powerDeployed = this.powerRetracted + h.power;
// TODO: range
this.calcShieldStrength = this.sgSI !== null? calcShieldStrength(this.mass, this.shields, this.internal[this.sgSI], 1) : 0;
this.armourAdded = 0; // internal.armoradd TODO: Armour (reinforcement, bulkheads)
this.armorTotal = this.armourAdded + this.armour;
this.armourAdded = i.armouradd;
this.shieldMultiplier = h.shieldmul;
this.unladenJumpRange = calcJumpRange(this.unladenMass + fsd.maxfuel, fsd); // Include fuel weight for jump
this.ladenJumpRange = calcJumpRange(this.ladenMass, fsd);
this.shieldStrength = this.sgSI !== null? calcShieldStrength(this.mass, this.shields, this.internal[this.sgSI].c, this.shieldMultiplier) : 0;
this.armourTotal = this.armourAdded + this.armour;
// TODO: shield recharge rate
// TODO: armor bonus / damage reduction for bulkheads
// TODO: thermal load and weapon recharge rate
this.code = this.toCode();
};
/**
* Update a slot with a the component if the id is different from the current id for this slot.
* Frees the slot of the current component if the id matches the current id for the slot.
*
* @param {object} slot The component slot
* @param {string} id Unique ID for the selected component
* @param {object} component Properties for the selected component
*/
Ship.prototype.use = function(slot, id, component) {
if (slot.id != id) { // Selecting a different component
slot.id = id;
slot.c = component;
// Selected componnent is a Shield Generator
if(component.group == 'sg') {
var slotIndex = this.internal.indexOf(slot);
// You can only have one shield Generator
if (this.sgSI !== null && this.sgSI != slotIndex) {
// A shield generator is already selected in a different slot
this.internal[this.sgSI].id = null;
this.internal[this.sgSI].c = null;
}
this.sgSI = slotIndex;
}
this.updateTotals();
}
};
/**
* Calculate the a ships shield strength based on mass, shield generator and shield boosters used.
*
* @private
* @param {number} mass Current mass of the ship
* @param {number} shields Base Shield strength MJ for ship
* @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ
*/
function calcShieldStrength (mass, shields, sg, multiplier) {
if (mass <= sg.minmass) {
return shields * multiplier * sg.minmul;
}
if (mass < sg.optmass) {
return shields * multiplier * (sg.minmul + (mass - sg.minmass) / (sg.optmass - sg.minmass) * (sg.optmul - sg.minmul));
}
if (mass < sg.maxmass) {
return shields * multiplier * (sg.optmul + (mass - sg.optmass) / (sg.maxmass - sg.optmass) * (sg.maxmul - sg.optmul));
}
return shields * multiplier * sg.maxmul;
}
/**
* Utilify function for summing the components properties
*
@@ -226,15 +183,61 @@ angular.module('shipyard').factory('ShipFactory', ['components', 'lodash', funct
* @return {object} The mutated sum object
*/
function optsSum(sum, slot) {
if (slot.c) { // The slot has a component selected
sum.cost += (slot.incCost && slot.c.cost)? slot.c.cost : 0;
sum.power += (slot.enabled && slot.c.power)? slot.c.power : 0;
sum.mass += slot.c.mass || 0;
sum.capacity += slot.c.capacity || 0;
var c = slot.c
if (c) { // The slot has a component mounted
sum.cost += (slot.incCost && c.cost)? c.cost : 0;
sum.power += (slot.enabled && c.power)? c.power : 0;
sum.mass += c.mass || 0;
sum.capacity += c.capacity || 0;
sum.shieldmul += c.shieldmul || 0;
sum.armouradd += c.armouradd || 0;
}
return sum;
}
Ship.prototype.useBulkhead = function(index) {
this.bulkheads.id = index;
this.bulkheads.c = DB.components.bulkheads[this.id][index];
this.updateTotals(); // Update mass, range, shield strength, armor
}
/**
* Update a slot with a the component if the id is different from the current id for this slot.
* Frees the slot of the current component if the id matches the current id for the slot.
*
* @param {object} slot The component slot
* @param {string} id Unique ID for the selected component
* @param {object} component Properties for the selected component
*/
Ship.prototype.use = function(slot, id, component) {
// TODO: only single refinery allowed
if (slot.id != id) { // Selecting a different component
slot.id = id;
slot.c = component;
var slotIndex = this.internal.indexOf(slot);
if(slot.id == null) { // Slot has been emptied
if(this.sgSI == slotIndex) { // The slot containing the shield generator was emptied
this.sgSI = null;
}
} else {
// Selected component is a Shield Generator
if(component.group == 'sg') {
// You can only have one shield Generator
if (this.sgSI !== null && this.sgSI != slotIndex) {
// A shield generator is already selected in a different slot
this.internal[this.sgSI].id = null;
this.internal[this.sgSI].c = null;
}
this.sgSI = slotIndex;
// Replacing a shield generator with something else
} else if (this.sgSI == slotIndex) {
this.sgSI = null;
}
}
this.updateTotals();
}
};
/**
* Ship Factory function. Created a new instance of a ship based on the ship type.
*

View File

@@ -94,4 +94,40 @@ angular.module('shipyard', [])
}
return groupToLabel;
});
})
.factory('CalcJumpRange', function() {
/**
* Calculate the maximum single jump range based on mass and a specific FSD
* @param {number} mass Mass of a ship: laden, unlanden, partially laden, etc
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
* @param {number} fuel Optional - The fuel consumed during the jump (must be less than the drives max fuel per jump)
* @return {number} Distance in Light Years
*/
return function(mass, fsd, fuel) {
return Math.pow(Math.min(fuel || Infinity, fsd.maxfuel) / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass;
};
})
.factory('CalcShieldStrength', function() {
/**
* Calculate the a ships shield strength based on mass, shield generator and shield boosters used.
*
* @private
* @param {number} mass Current mass of the ship
* @param {number} shields Base Shield strength MJ for ship
* @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ
*/
return function (mass, shields, sg, multiplier) {
if (mass <= sg.minmass) {
return shields * multiplier * sg.minmul;
}
if (mass < sg.optmass) {
return shields * multiplier * (sg.minmul + (mass - sg.minmass) / (sg.optmass - sg.minmass) * (sg.optmul - sg.minmul));
}
if (mass < sg.maxmass) {
return shields * multiplier * (sg.optmul + (mass - sg.optmass) / (sg.maxmass - sg.optmass) * (sg.maxmul - sg.optmul));
}
return shields * multiplier * sg.maxmul;
}
});