Initial Commit for React

This commit is contained in:
Colin McLeod
2015-11-13 17:43:45 -08:00
parent c527a62ce6
commit ed637addb8
60 changed files with 3362 additions and 3091 deletions

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "data"]
path = data
url = https://github.com/cmmcleod/coriolis-data.git

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html ng-app="app" ng-strict-di="true" manifest="/coriolis.appcache">
<html manifest="/coriolis.appcache">
<head>
<title ng-bind="title">Coriolis</title>
<title>Coriolis</title>
<link rel="stylesheet" href="/app.css">
<!-- Standard headers -->
@@ -55,22 +55,15 @@
</head>
<body style="background-color:#000;">
<div style="height: 0; width: 0; overflow:hidden"><%= svgContent %></div>
<shipyard-header></shipyard-header>
<div id="main" ui-view ng-click="bgClicked($event)" ng-style="{'font-size': sizeRatio + 'em'}"></div>
<div ui-view="modal" ng-click="bgClicked($event)"></div>
<section id="coriolis"></section>
<footer>
<div class="right cap">
<a href="https://github.com/cmmcleod/coriolis/releases/" target="_blank" title="Coriolis Github Project"><span translate="version"></span> <%= version %> - <%= date %></a>
</div>
<div style="max-width:50%" class="l">
Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments and no employee of Frontier Developments was involved in the making of it.
</div>
</footer>
<script src="/lib.js" type="text/javascript"></script>
<script src="/app.js" type="text/javascript"></script>
<script src="/lib.js" type="text/javascript" crossorigin></script>
<script src="/app.js" type="text/javascript" crossorigin></script>
<% if (uaTracking) { %>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
@@ -82,6 +75,5 @@
var GAPI_KEY = '<%= gapiKey %>';
</script>
<% } %>
</body>
</html>

55
app/js/Coriolis.jsx Normal file
View File

@@ -0,0 +1,55 @@
import { Component } from 'react';
import Router from 'Router';
import ShipyardPage from 'pages/ShipyardPage';
import NotFoundPage from 'pages/NotFoundPage';
import Header from '../components/Header';
class Coriolis extends Component {
constructor(props) {
super(props);
this.setPage = this.setPage.bind(this);
this.state.standAlone = isStandAlone();
window.onerror = errorPage.bind(this);
Router('/', () => this.setPage(<ShipyardPage />));
// Router('/outfitting/:ship', outfitting);
// Router('/outfitting/:ship/:code', outfitting);
// Router('/compare/:name', compare);
// Router('/comparison/:code', comparison);
// Router('/settings', settings);
Router('*', () => this.setPage(null));
if (window.applicationCache) {
// Listen for appcache updated event, present refresh to update view
window.applicationCache.addEventListener('updateready', () => {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Browser downloaded a new app cache.
this.setState({appCacheUpdate: true});
}
}, false);
}
Router.start();
console.log('Root page created');
}
setPage(page) {
this.setState({ page: page });
}
onError(msg, scriptUrl, line, col, errObj) {
this.setPage(<div>Some errors occured!!</div>);
}
render() {
return (
<div>
{/* <Header appCacheUpdate={appCacheUpdate} /> */}
{this.state.page || <NotFoundPage />}
</div>
);
}
}
ReactDOM.render(<Coriolis />, document.getElementById('coriolis'));

333
app/js/Router.js Normal file
View File

@@ -0,0 +1,333 @@
import Persist from 'stores/Persist';
function isStandAlone() {
try {
return window.navigator.standalone || (window.external && window.external.msIsSiteMode && window.external.msIsSiteMode());
} catch (ex) {
return false;
}
}
/**
* Register `path` with callback `fn()`,
* or route `path`, or `Router.start()`.
*
* Router('*', fn);
* Router('/user/:id', load, user);
* Router('/user/' + user.id, { some: 'thing' });
* Router('/user/' + user.id);
* Router();
*
* @param {String} path
* @param {Function} fn...
* @api public
*/
function Router(path, fn) {
var route = new Route(path);
for (var i = 1; i < arguments.length; ++i) {
Router.callbacks.push(route.middleware(arguments[i]));
}
}
/**
* Callback functions.
*/
Router.callbacks = [];
/**
* Bind with the given `options`.
*
* Options:
*
* - `click` bind to click events [true]
* - `popstate` bind to popstate [true]
* - `dispatch` perform initial dispatch [true]
*
* @param {Object} options
* @api public
*/
Router.start = function(){
window.addEventListener('popstate', onpopstate, false);
if (isStandAlone()) {
var state = Persist.getState();
// If a previous state has been stored, load that state
if (state && state.name && state.params) {
Router(this.props.initialPath || '/');
//$state.go(state.name, state.params, { location: 'replace' });
} else {
Router('/');
}
} else {
var url = location.pathname + location.search + location.hash;
Router.replace(url, null, true, dispatch);
}
};
/**
* Show `path` with optional `state` object.
*
* @param {String} path
* @param {Object} state
* @return {Context}
* @api public
*/
Router.go = function(path, state) {
gaTrack(path);
var ctx = new Context(path, state);
if (false !== dispatch) Router.dispatch(ctx);
if (!ctx.unhandled) ctx.pushState();
return ctx;
};
/**
* Replace `path` with optional `state` object.
*
* @param {String} path
* @param {Object} state
* @return {Context}
* @api public
*/
Router.replace = function(path, state, init, dispatch) {
gaTrack(path);
var ctx = new Context(path, state);
ctx.init = init;
if (null == dispatch) dispatch = true;
if (dispatch) Router.dispatch(ctx);
ctx.save();
return ctx;
};
/**
* Dispatch the given `ctx`.
*
* @param {Object} ctx
* @api private
*/
Router.dispatch = function(ctx){
var i = 0;
function next() {
var fn = Router.callbacks[i++];
if (!fn) return unhandled(ctx);
fn(ctx, next);
}
next();
};
/**
* Unhandled `ctx`. When it's not the initial
* popstate then redirect. If you wish to handle
* 404s on your own use `Router('*', callback)`.
*
* @param {Context} ctx
* @api private
*/
function unhandled(ctx) {
var current = window.location.pathname + window.location.search;
if (current == ctx.canonicalPath) return;
window.location = ctx.canonicalPath;
}
/**
* Initialize a new "request" `Context`
* with the given `path` and optional initial `state`.
*
* @param {String} path
* @param {Object} state
* @api public
*/
function Context(path, state) {
var i = path.indexOf('?');
this.canonicalPath = path;
this.path = path || '/';
this.title = document.title;
this.state = state || {};
this.state.path = path;
this.querystring = ~i ? path.slice(i + 1) : '';
this.pathname = ~i ? path.slice(0, i) : path;
this.params = [];
// fragment
this.hash = '';
if (!~this.path.indexOf('#')) return;
var parts = this.path.split('#');
this.path = parts[0];
this.hash = parts[1] || '';
this.querystring = this.querystring.split('#')[0];
}
/**
* Expose `Context`.
*/
Router.Context = Context;
/**
* Push state.
*
* @api private
*/
Context.prototype.pushState = function(){
history.pushState(this.state, this.title, this.canonicalPath);
};
/**
* Save the context state.
*
* @api public
*/
Context.prototype.save = function(){
history.replaceState(this.state, this.title, this.canonicalPath);
};
/**
* Initialize `Route` with the given HTTP `path`,
* and an array of `callbacks` and `options`.
*
* Options:
*
* - `sensitive` enable case-sensitive routes
* - `strict` enable strict matching for trailing slashes
*
* @param {String} path
* @param {Object} options.
* @api private
*/
function Route(path, options) {
options = options || {};
this.path = path;
this.method = 'GET';
this.regexp = pathtoRegexp(path
, this.keys = []
, options.sensitive
, options.strict);
}
/**
* Expose `Route`.
*/
Router.Route = Route;
/**
* Return route middleware with
* the given callback `fn()`.
*
* @param {Function} fn
* @return {Function}
* @api public
*/
Route.prototype.middleware = function(fn){
var self = this;
return function(ctx, next){
if (self.match(ctx.path, ctx.params)) return fn(ctx, next);
next();
};
};
/**
* Check if this route matches `path`, if so
* populate `params`.
*
* @param {String} path
* @param {Array} params
* @return {Boolean}
* @api private
*/
Route.prototype.match = function(path, params){
var keys = this.keys
, qsIndex = path.indexOf('?')
, pathname = ~qsIndex ? path.slice(0, qsIndex) : path
, m = this.regexp.exec(decodeURIComponent(pathname));
if (!m) return false;
for (var i = 1, len = m.length; i < len; ++i) {
var key = keys[i - 1];
var val = 'string' == typeof m[i]
? decodeURIComponent(m[i])
: m[i];
if (key) {
params[key.name] = undefined !== params[key.name]
? params[key.name]
: val;
} else {
params.push(val);
}
}
return true;
};
function gaTrack(path) {
if (window.ga) {
window.ga('send', 'pageview', { page: path });
}
}
/**
* Normalize the given path string,
* returning a regular expression.
*
* An empty array should be passed,
* which will contain the placeholder
* key names. For example "/user/:id" will
* then contain ["id"].
*
* @param {String|RegExp|Array} path
* @param {Array} keys
* @param {Boolean} sensitive
* @param {Boolean} strict
* @return {RegExp}
* @api private
*/
function pathtoRegexp(path, keys, sensitive, strict) {
if (path instanceof RegExp) return path;
if (path instanceof Array) path = '(' + path.join('|') + ')';
path = path
.concat(strict ? '' : '/?')
.replace(/\/\(/g, '(?:/')
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
keys.push({ name: key, optional: !! optional });
slash = slash || '';
return ''
+ (optional ? '' : slash)
+ '(?:'
+ (optional ? slash : '')
+ (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
+ (optional || '');
})
.replace(/([\/.])/g, '\\$1')
.replace(/\*/g, '(.*)');
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
}
/**
* Handle "populate" events.
*/
function onpopstate(e) {
if (e.state) {
var path = e.state.path;
Router.replace(path, e.state);
}
}
export default Router;

255
app/js/Serializer.js Executable file
View File

@@ -0,0 +1,255 @@
import { ModuleGroupToName, MountMap } from './Constants';
import Ships from './Ships';
import Ship from './Ship';
import ModuleUtils from './ModuleUtils';
import LZString from 'LZString';
/**
* Service managing seralization and deserialization of models for use in URLs and persistene.
*/
/**
* Utility function to retrieve a safe string for selected component for a slot.
* Used for serialization to code only.
* TODO: comment on binding
* @private
* @param {object} slot The slot object.
* @return {string} The id of the selected component or '-' if none selected
*/
function mapGroup(slot) {
export function enabled.push(slot.enabled ? 1 : 0);
export function priorities.push(slot.priority);
return slot.id === null ? '-' : slot.id;
}
function decodeToArray(code, arr, codePos) {
for (let i = 0; i < arr.length; i++) {
if (code.charAt(codePos) == '-') {
arr[i] = 0;
codePos++;
} else {
arr[i] = code.substring(codePos, codePos + 2);
codePos += 2;
}
}
return codePos;
}
function slotToSchema(slot) {
if (slot.c) {
let o = {
class: slot.c.class,
rating: slot.c.rating,
enabled: Boolean(slot.enabled),
priority: slot.priority + 1,
group: ModuleGroupToName[slot.c.grp]
};
if (slot.c.name) {
o.name = slot.c.name;
}
if (slot.c.mode) {
o.mount = MountMap[slot.c.mode];
}
if (slot.c.missile) {
o.missile = slot.c.missile;
}
return o;
}
return null;
}
/**
* Serializes the ships selected components for all slots to a URL friendly string.
* @param {Ship} ship The ship to be serialized.
* @return {string} Encoded string of components
*/
export function fromShip(ship) {
let power = {
enabled: [ship.cargoHatch.enabled ? 1 : 0],
priorities: [ship.cargoHatch.priority]
};
let data = [
ship.bulkheads.id,
_.map(ship.standard, mapGroup, power),
_.map(ship.hardpoints, mapGroup, power),
_.map(ship.internal, mapGroup, power),
'.',
LZString.compressToBase64(power.enabled.join('')).replace(/\//g, '-'),
'.',
LZString.compressToBase64(power.priorities.join('')).replace(/\//g, '-')
];
return _.flatten(data).join('');
};
/**
* Updates an existing ship instance's slots with components determined by the
* code.
*
* @param {Ship} ship The ship instance to be updated
* @param {string} dataString The string to deserialize
*/
export function toShip(ship, dataString) {
var standard = new Array(ship.standard.length),
hardpoints = new Array(ship.hardpoints.length),
internal = new Array(ship.internal.length),
parts = dataString.split('.'),
priorities = null,
enabled = null,
code = parts[0];
if (parts[1]) {
enabled = LZString.decompressFromBase64(parts[1].replace(/-/g, '/')).split('');
}
if (parts[2]) {
priorities = LZString.decompressFromBase64(parts[2].replace(/-/g, '/')).split('');
}
decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, standard, 1)));
ship.buildWith(
{
bulkheads: code.charAt(0) * 1,
standard: standard,
hardpoints: hardpoints,
internal: internal
},
priorities,
enabled
);
};
export function toDetailedBuild(buildName, ship, code) {
var standard = ship.standard,
hardpoints = ship.hardpoints,
internal = ship.internal;
var data = {
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/2.json#',
name: buildName,
ship: ship.name,
references: [{
name: 'Coriolis.io',
url: $state.href('outfit', { shipId: ship.id, code: code, bn: buildName }, { absolute: true }),
code: code,
shipId: ship.id
}],
components: {
standard: {
bulkheads: ship.bulkheads.c.name,
cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 },
powerPlant: { class: standard[0].c.class, rating: standard[0].c.rating, enabled: Boolean(standard[0].enabled), priority: standard[0].priority + 1 },
thrusters: { class: standard[1].c.class, rating: standard[1].c.rating, enabled: Boolean(standard[1].enabled), priority: standard[1].priority + 1 },
frameShiftDrive: { class: standard[2].c.class, rating: standard[2].c.rating, enabled: Boolean(standard[2].enabled), priority: standard[2].priority + 1 },
lifeSupport: { class: standard[3].c.class, rating: standard[3].c.rating, enabled: Boolean(standard[3].enabled), priority: standard[3].priority + 1 },
powerDistributor: { class: standard[4].c.class, rating: standard[4].c.rating, enabled: Boolean(standard[4].enabled), priority: standard[4].priority + 1 },
sensors: { class: standard[5].c.class, rating: standard[5].c.rating, enabled: Boolean(standard[5].enabled), priority: standard[5].priority + 1 },
fuelTank: { class: standard[6].c.class, rating: standard[6].c.rating, enabled: Boolean(standard[6].enabled), priority: standard[6].priority + 1 }
},
hardpoints: _.map(_.filter(hardpoints, function(slot) { return slot.maxClass > 0; }), slotToSchema),
utility: _.map(_.filter(hardpoints, function(slot) { return slot.maxClass === 0; }), slotToSchema),
internal: _.map(internal, slotToSchema)
},
stats: {}
};
for (var stat in ship) {
if (!isNaN(ship[stat])) {
data.stats[stat] = Math.round(ship[stat] * 100) / 100;
}
}
return data;
};
export function fromDetailedBuild(detailedBuild) {
var shipId = _.findKey(ShipsDB, { properties: { name: detailedBuild.ship } });
if (!shipId) {
throw 'No such ship: ' + detailedBuild.ship;
}
var comps = detailedBuild.components;
var standard = comps.standard;
var priorities = [ standard.cargoHatch && standard.cargoHatch.priority !== undefined ? standard.cargoHatch.priority - 1 : 0 ];
var enabled = [ standard.cargoHatch && standard.cargoHatch.enabled !== undefined ? standard.cargoHatch.enabled : true ];
var shipData = ShipsDB[shipId];
var ship = new Ship(shipId, shipData.properties, shipData.slots);
var bulkheads = ModuleUtils.bulkheadIndex(standard.bulkheads);
if (bulkheads < 0) {
throw 'Invalid bulkheads: ' + standard.bulkheads;
}
var standardIds = _.map(
['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank'],
function(c) {
if (!standard[c].class || !standard[c].rating) {
throw 'Invalid value for ' + c;
}
priorities.push(standard[c].priority === undefined ? 0 : standard[c].priority - 1);
enabled.push(standard[c].enabled === undefined ? true : standard[c].enabled);
return standard[c].class + standard[c].rating;
}
);
var internal = _.map(comps.internal, function(c) { return c ? ModuleUtils.findInternalId(c.group, c.class, c.rating, c.name) : 0; });
var hardpoints = _.map(comps.hardpoints, function(c) {
return c ? ModuleUtils.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount], c.missile) : 0;
}).concat(_.map(comps.utility, function(c) {
return c ? ModuleUtils.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount]) : 0;
}));
// The ordering of these arrays must match the order in which they are read in Ship.buildWith
priorities = priorities.concat(_.map(comps.hardpoints, function(c) { return (!c || c.priority === undefined) ? 0 : c.priority - 1; }),
_.map(comps.utility, function(c) { return (!c || c.priority === undefined) ? 0 : c.priority - 1; }),
_.map(comps.internal, function(c) { return (!c || c.priority === undefined) ? 0 : c.priority - 1; }));
enabled = enabled.concat(_.map(comps.hardpoints, function(c) { return (!c || c.enabled === undefined) ? true : c.enabled * 1; }),
_.map(comps.utility, function(c) { return (!c || c.enabled === undefined) ? true : c.enabled * 1; }),
_.map(comps.internal, function(c) { return (!c || c.enabled === undefined) ? true : c.enabled * 1; }));
ship.buildWith({ bulkheads: bulkheads, standard: standardIds, hardpoints: hardpoints, internal: internal }, priorities, enabled);
return ship;
};
export function toDetailedExport(builds) {
var data = [];
for (var shipId in builds) {
for (var buildName in builds[shipId]) {
var code = builds[shipId][buildName];
var shipData = ShipsDB[shipId];
var ship = new Ship(shipId, shipData.properties, shipData.slots);
export function toShip(ship, code);
data.push(export function toDetailedBuild(buildName, ship, code));
}
}
return data;
};
export function fromComparison(name, builds, facets, predicate, desc) {
var shipBuilds = [];
builds.forEach(function(b) {
shipBuilds.push({ s: b.id, n: b.buildName, c: export function fromShip(b) });
}.bind(this));
return LZString.compressToBase64(angular.toJson({
n: name,
b: shipBuilds,
f: facets,
p: predicate,
d: desc ? 1 : 0
})).replace(/\//g, '-');
};
export function toComparison(code) {
return angular.fromJson(LZString.decompressFromBase64(code.replace(/-/g, '/')));
};

View File

@@ -0,0 +1,118 @@
import { Component } from 'react';
export default class Header extends Component {
render() {
let openedMenu = this.state.openedMenu;
if (this.props.appCacheUpdate) {
return <div id="app-update" onClick={window.location.reload}>{ 'PHRASE_UPDATE_RDY' | translate }</div>;
}
return (
<header>
<a class="l" ui-sref="shipyard" style="margin-right: 1em;" title="Ships"><svg class="icon xl"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#coriolis"></use></svg></a>
<div class="l menu">
<div class="menu-header" ng-class="{selected: openedMenu=='s'}" ng-click="openMenu($event,'s')">
<svg class="icon warning"><use xlink:href="#rocket"></use></svg><span class="menu-item-label"> {{'ships' | translate}}</span>
</div>
{openedMenu == 's' ? this.getShipsMenu() : null}
</div>
<div class="l menu">
<div class="menu-header" ng-class="{selected: openedMenu=='b', disabled: !bs.hasBuilds}" ng-click="openMenu($event,'b')">
<svg class="icon warning" ng-class="{'warning-disabled': !bs.hasBuilds}"><use xlink:href="#hammer"></use></svg><span class="menu-item-label"> {{'builds' | translate}}</span>
</div>
{openedMenu == 'b' ? this.getBuildsMenu() : null}
</div>
<div class="l menu">
<div class="menu-header" ng-class="{selected: openedMenu=='comp', disabled: !bs.hasBuilds}" ng-click="openMenu($event,'comp')">
<svg class="icon warning" ng-class="{'warning-disabled': !bs.hasBuilds}"><use xlink:href="#stats-bars"></use></svg><span class="menu-item-label"> {{'compare' | translate}}</span>
</div>
{openedMenu == 'comp' ? this.getComparisonsMenu() : null}
</div>
<div class="r menu">
<div class="menu-header" ng-class="{selected: openedMenu=='settings'}" ng-click="openMenu($event,'settings')">
<svg class="icon xl warning"><use xlink:href="#cogs"></use></svg><span class="menu-item-label"> {{'settings' | translate}}</span>
</div>
{openedMenu == 'settings' ? this.getSettingsMenu() : null}
</div>
</header>
);
}
getShipsMenu() {
return (<div class="menu-list dbl no-wrap">
<a class="block" ng-repeat="(shipId,ship) in ships" ui-sref-active="active" ui-sref="outfit({shipId:shipId, code:null, bn:null})">{{::ship.properties.name}}</a>
</div>);
}
getBuildsMenu() {
return (<div class="menu-list" ng-if="openedMenu=='b'" ng-click="$event.stopPropagation();">
<div class="dbl" >
<div><ul ng-repeat="shipId in buildsList">
{{ships[shipId].properties.name}}
<li ng-repeat="(i, name) in cleanedBuildList(shipId)">
<a ui-sref-active="active" class="name" ui-sref="outfit({shipId:shipId, code:allBuilds[shipId][name], bn:name})" ng-bind="name"></a>
</li>
</ul></div>
</div>
</div>);
}
getComparisonsMenu() {
return (<div class="menu-list" ng-if="openedMenu=='comp'" ng-click="$event.stopPropagation();" style="white-space: nowrap;">
<span class="cap" ng-if="!bs.hasComparisons" translate="none created"></span>
<a ng-repeat="(i, name) in allComparisons" ui-sref-active="active" class="block name" ui-sref="compare({name:name})" ng-bind="name"></a>
<hr />
<a ui-sref="compare({name: 'all'})" class="block cap" translate="compare all"></a>
<a ui-sref="compare({name: null})" class="block cap" translate="create new"></a>
</div>);
}
getSettingsMenu() {
return (<div class="menu-list no-wrap cap" ng-if="openedMenu=='settings'" ng-click="$event.stopPropagation();">
<ul>
{{'language' | translate}}
<li><select class="cap" ng-model="language.current" ng-options="langCode as langName for (langCode,langName) in language.opts" ng-change="changeLanguage()"></select></li>
</ul><br>
<ul>
{{'insurance' | translate}}
<li><select class="cap" ng-model="insurance.current" ng-options="ins.name | translate for (i,ins) in insurance.opts" ng-change="updateInsurance()"></select></li>
</ul><br>
<ul>
{{'ship' | translate}} {{'discount' | translate}}
<li><select class="cap" ng-model="discounts.ship" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
</ul><br>
<ul>
{{'component' | translate}} {{'discount' | translate}}
<li><select class="cap" ng-model="discounts.components" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
</ul>
<hr />
<ul>
{{'builds' | translate}} & {{'comparisons' | translate}}
<li><a href="#" class="block" ng-click="backup($event)" translate="backup"></a></li>
<li><a href="#" class="block" ng-click="detailedExport($event)" translate="detailed export"></a></li>
<li><a href="#" class="block" ui-sref="modal.import" translate="import"></a></li>
<li><a href="#" class="block" ui-sref="modal.delete" translate="delete all"></a></li>
</ul>
<hr />
<table style="width: 300px;background-color:transparent">
<tr>
<td style="width: 20px"><u>A</u></td>
<td slider min="0.65" def="sizeRatio" max="1.2" on-change="textSizeChange(val)" ignore-resize="true"></td>
<td style="width: 20px"><span style="font-size: 30px">A</span></td>
</tr>
<tr>
<td></td><td style="text-align:center" class="primary-disabled cap" ng-click="resetTextSize()" translate="reset"></td><td></td>
</tr>
</table>
<hr />
<a href="#" ui-sref="modal.about" class="block" translate="about"></a>
</div>);
}
}

View File

@@ -1,81 +0,0 @@
/**
* Sets up the routes and handlers before the Angular app is kicked off.
*/
angular.module('app').config(['$provide', '$stateProvider', '$urlRouterProvider', '$locationProvider', 'ShipsDB', function($provide, $stateProvider, $urlRouterProvider, $locationProvider, ships) {
// Use HTML5 push and replace state if possible
$locationProvider.html5Mode({ enabled: true, requireBase: false });
/**
* Set up all states and their routes.
*/
$stateProvider
.state('outfit', {
url: '/outfit/:shipId/:code?bn',
params: {
shipId: { value: 'sidewinder', squash: false }, // Allow 'shipId' parameter to default to sidewinder
code: { value: null, squash: true } // Allow 'code' parameter to be empty/optional
},
templateUrl: 'views/page-outfit.html',
controller: 'OutfitController',
resolve: {
shipId: ['$stateParams', function($p) { // Ensure ship exists before loading controller
if (!ships[$p.shipId]) {
throw { type: 'no-ship', message: $p.shipId };
}
}]
},
sticky: true
})
.state('compare', {
url: '/compare/:name',
params: {
name: { value: null, squash: true }
},
templateUrl: 'views/page-comparison.html',
controller: 'ComparisonController',
sticky: true
})
.state('comparison', {
url: '/comparison/:code',
templateUrl: 'views/page-comparison.html',
controller: 'ComparisonController',
sticky: true
})
.state('shipyard', { url: '/', templateUrl: 'views/page-shipyard.html', controller: 'ShipyardController', sticky: true })
.state('error', { params: { type: null, message: null, details: null }, templateUrl: 'views/page-error.html', controller: 'ErrorController', sticky: true })
// Modal States and views
.state('modal', { abstract: true, views: { 'modal': { templateUrl: 'views/_modal.html', controller: 'ModalController' } } })
.state('modal.about', { views: { 'modal-content': { templateUrl: 'views/modal-about.html' } } })
.state('modal.export', { params: { title: null, data: null, promise: null, description: null }, views: { 'modal-content': { templateUrl: 'views/modal-export.html', controller: 'ExportController' } } })
.state('modal.import', { params: { obj: null }, views: { 'modal-content': { templateUrl: 'views/modal-import.html', controller: 'ImportController' } } })
.state('modal.link', { params: { url: null }, views: { 'modal-content': { templateUrl: 'views/modal-link.html', controller: 'LinkController' } } })
.state('modal.delete', { views: { 'modal-content': { templateUrl: 'views/modal-delete.html', controller: 'DeleteController' } } });
// Redirects
$urlRouterProvider.when('/outfit', '/outfit/sidewinder');
/**
* 404 Handler - Keep current URL/ do not redirect, change to error state.
*/
$urlRouterProvider.otherwise(function($injector, $location) {
// Go to error state, reload the controller, keep the current URL
$injector.get('$state').go('error', { type: 404, message: null, details: null }, { location: false, reload: true });
return $location.path;
});
/**
* Global Error Handler. Decorates the existing error handler such that it
* redirects uncaught errors to the error page.
*
*/
$provide.decorator('$exceptionHandler', ['$delegate', '$injector', function($delegate, $injector) {
return function(err, cause) {
// Go to error state, reload the controller, keep the current URL
$injector.get('$state').go('error', { type: null, message: err.message, details: err.stack }, { location: false, reload: true });
$delegate(err, cause);
};
}]);
}]);

View File

@@ -1,59 +0,0 @@
angular.module('app').controller('ShipyardController', ['$rootScope', '$scope', 'ShipsDB', 'Ship', 'Components', function($rootScope, $scope, ShipsDB, Ship, Components) {
$rootScope.title = 'Coriolis - Shipyard';
$scope.shipPredicate = 'properties.name';
$scope.shipDesc = false;
function countHp(slot) {
this.hp[slot.maxClass]++;
this.hpCount++;
}
function countInt(slot) {
var crEligible = !slot.eligible || slot.eligible.cr;
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
this.intCount++;
this.maxCargo += crEligible ? Components.findInternal('cr', slot.maxClass, 'E').capacity : 0;
}
function shipSummary(shipId, shipData) {
var summary = angular.copy(shipData.properties);
var ship = new Ship(shipId, shipData.properties, shipData.slots);
summary.id = s;
summary.hpCount = 0;
summary.intCount = 0;
summary.maxCargo = 0;
summary.hp = [0, 0, 0, 0, 0]; // Utility, Small, Medium, Large, Huge
summary.int = [0, 0, 0, 0, 0, 0, 0, 0]; // Sizes 1 - 8
// Build Ship
ship.buildWith(shipData.defaults); // Populate with stock/default components
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
ship.optimizeMass({ th: ship.standard[1].maxClass + 'A' }); // Optmize mass with Max Thrusters
summary.topSpeed = ship.topSpeed;
summary.topBoost = ship.topBoost;
return summary;
}
/* Initialization */
if (!$rootScope.shipsOverview) { // Only generate this once
$rootScope.shipsOverview = [];
for (var s in ShipsDB) {
$scope.shipsOverview.push(shipSummary(s, ShipsDB[s]));
}
}
/**
* Sort ships
* @param {object} key Sort predicate
*/
$scope.sortShips = function(key) {
$scope.shipDesc = $scope.shipPredicate == key ? !$scope.shipDesc : $scope.shipDesc;
$scope.shipPredicate = key;
};
}]);

View File

@@ -1,14 +0,0 @@
angular.module('app').directive('contextMenu', ['$parse', function($parse) {
return function(scope, element, attrs) {
var fn = $parse(attrs.contextMenu);
element.bind('contextmenu', function(e) {
if (!e.shiftKey) {
scope.$apply(function() {
e.preventDefault();
fn(scope, { $event: e });
});
}
});
};
}]);

View File

@@ -1,65 +0,0 @@
/**
* BBCode Generator functions for embedding in the Elite Dangerous Forums
*/
angular.module('app').factory('Utils', ['$window', '$state', '$http', '$q', '$translate', '$rootScope', function($window, $state, $http, $q, $translate, $rootScope) {
var shortenAPI = 'https://www.googleapis.com/urlshortener/v1/url?key=';
function shortenUrl(url) {
if ($window.navigator.onLine) {
return $http.post(shortenAPI + GAPI_KEY, { longUrl: url }).then(function(response) {
return response.data.id;
});
} else {
return $q.reject({ statusText: 'Not Online' });
}
}
function comparisonBBCode(facets, builds, link) {
var colCount = 2, b, i, j, k, f, fl, p, pl, l = [];
for (i = 0; i < facets.length; i++) {
if (facets[i].active) {
f = facets[i];
p = f.props;
if (p.length == 1) {
l.push('[th][B][COLOR=#FF8C0D]', $translate.instant(f.title).toUpperCase(), '[/COLOR][/B][/th]');
colCount++;
} else {
for (j = 0; j < p.length; j++) {
l.push('[th][B][COLOR=#FF8C0D]', $translate.instant(f.title).toUpperCase(), '\n', $translate.instant(f.lbls[j]).toUpperCase(), '[/COLOR][/B][/th]');
colCount++;
}
}
}
}
l.push('[/tr]\n');
for (i = 0; i < builds.length; i++) {
b = builds[i];
//var href = $state.href('outfit',{shipId: b.id, code: b.code, bn: b.buildName}, {absolute: true});
l.push('[tr][td]', b.name, '[/td][td]', b.buildName, '[/td]');
for (j = 0, fl = facets.length; j < fl; j++) {
if (facets[j].active) {
f = facets[j];
p = f.props;
for (k = 0, pl = p.length; k < pl; k++) {
l.push('[td="align: right"]', $rootScope[f.fmt](b[p[k]]), ' [size=-2]', $translate.instant(f.unit), '[/size][/td]');
}
}
}
l.push('[/tr]\n');
}
l.push('[tr][td="align: center, colspan:', colCount, '"][size=-3]\n[url=', link, ']Interactive Comparison at Coriolis.io[/url][/td][/tr]\n[/size][/table]');
l.unshift('[table="width:', colCount * 90, ',align: center"]\n[tr][th][B][COLOR=#FF8C0D]Ship[/COLOR][/B][/th][th][B][COLOR="#FF8C0D"]Build[/COLOR][/B][/th]');
return l.join('');
}
return {
comparisonBBCode: comparisonBBCode,
shortenUrl: shortenUrl
};
}]);

52
app/js/i18n/Language.js Normal file
View File

@@ -0,0 +1,52 @@
import EN from 'en';
import DE from 'de';
import ES from 'es';
import FR from 'fr';
import IT from 'it';
import RU from 'RU';
import d3 from 'd3';
let fallbackTerms = EN.terms;
let currentLanguage;
let currentTerms;
let format = {
rPct: d3.format('%')
};
export format;
export function setLanguage(langCode) {
let lang;
switch (langCode) {
case 'de': lang = DE; break;
case 'es': lang = ES; break;
case 'fr': lang = FR; break;
case 'it': lang = IT; break;
case 'ru': lang = RU; break;
default: lang = EN;
}
currentTerms = lang.terms;
d3Locale = d3.locale(lang.formats);
format.gen = d3Locale.numberFormat('n');
format.crd = d3Locale.numberFormat(',.0f');
format.pwr = d3Locale.numberFormat(',.2f');
format.round = (d) => format.gen(d3.round(d, 2));
format.pct = d3Locale.numberFormat('.2%');
format.pct1 = d3Locale.numberFormat('.1%');
}
export const Languages = {
en: 'English',
de: 'Deutsh',
it: 'Italiano',
es: 'Español',
fr: 'Français',
ru: 'ру́сский'
};
export function term(t) {
return currentTerms[t] || fallbackTerms[t];
}

View File

@@ -1,6 +1,4 @@
angular.module('app').config(['$translateProvider', 'localeFormatProvider', function($translateProvider, localeFormatProvider) {
// Declare number format settings
localeFormatProvider.addFormat('de', {
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
@@ -13,8 +11,9 @@ angular.module('app').config(['$translateProvider', 'localeFormatProvider', func
shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
});
$translateProvider.translations('de', {
};
export const terms = {
PHRASE_EXPORT_DESC: 'Ein detaillierter JSON-Export Ihrer Konfiguration für die Verwendung in anderen Websites und Tools',
'A-Rated': 'A-Klasse',
about: 'Über',
@@ -216,5 +215,4 @@ angular.module('app').config(['$translateProvider', 'localeFormatProvider', func
WEP: 'WAF',
yes: 'Ja',
PHRASE_BACKUP_DESC: 'Export aller Coriolis-Daten, um sie zu sichern oder oder um sie zu einem anderen Browser/Gerät zu übertragen.'
});
}]);
};

View File

@@ -1,5 +1,19 @@
angular.module('app').config(['$translateProvider', function($translateProvider) {
$translateProvider.translations('en', {
export const formats = {
decimal: '.',
thousands: ',',
grouping: [3],
currency: ['$', ''],
dateTime: '%a %b %e %X %Y',
date: '%m/%d/%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
};
export const terms = {
PHRASE_EXPORT_DESC: 'A detailed JSON export of your build for use in other sites and tools',
'A-Rated': 'A-Rated',
about: 'about',
@@ -213,5 +227,4 @@ angular.module('app').config(['$translateProvider', function($translateProvider)
WEP: 'WEP',
yes: 'yes',
PHRASE_BACKUP_DESC: 'Backup of all Coriolis data to save or transfer to another browser/device'
});
}]);
};

View File

@@ -1,7 +1,4 @@
angular.module('app').config(['$translateProvider', 'localeFormatProvider', function($translateProvider, localeFormatProvider) {
// Declare number format settings
localeFormatProvider.addFormat('es', {
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
@@ -14,9 +11,9 @@ angular.module('app').config(['$translateProvider', 'localeFormatProvider', func
shortDays: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'],
months: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'],
shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
});
};
$translateProvider.translations('es', {
export const terms = {
'PHRASE_EXPORT_DESC': 'Una detallada exportaci\u00f3n JSON de tu construcci\u00f3n para usarlo en otros sitios web y herramientas',
'A-Rated': 'Calidad-A',
'about': 'Acerca',
@@ -208,5 +205,4 @@ angular.module('app').config(['$translateProvider', 'localeFormatProvider', func
'WEP': 'ARM',
'yes': 'si',
'PHRASE_BACKUP_DESC': 'Copia de seguridad de todos los datos de Coriolis para guardarlos o transferirlos a otro navegador\/dispositivo'
});
}]);
};

View File

@@ -1,6 +1,4 @@
angular.module('app').config(['$translateProvider', 'localeFormatProvider', function($translateProvider, localeFormatProvider) {
// Declare number format settings
localeFormatProvider.addFormat('fr', {
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
@@ -13,8 +11,9 @@ angular.module('app').config(['$translateProvider', 'localeFormatProvider', func
shortDays: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
months: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],
shortMonths: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.']
});
$translateProvider.translations('fr', {
};
export const terms = {
PHRASE_EXPORT_DESC: 'Export détaillé en JSON de votre configuration pour utilisation sur d\'autres sites et outils',
'A-Rated': 'Classe-A ',
about: 'à propos',
@@ -196,5 +195,4 @@ angular.module('app').config(['$translateProvider', 'localeFormatProvider', func
WEP: 'ARM',
yes: 'oui',
PHRASE_BACKUP_DESC: 'Exportation détaillée des données de Coriolis pour l\'utilisation dans d\'autres sites et outils'
});
}]);
};

View File

@@ -1,7 +1,4 @@
angular.module('app').config(['$translateProvider', 'localeFormatProvider', function($translateProvider, localeFormatProvider) {
// Declare number format settings
localeFormatProvider.addFormat('es', {
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
@@ -14,9 +11,9 @@ angular.module('app').config(['$translateProvider', 'localeFormatProvider', func
shortDays: ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'],
months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
shortMonths: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic']
});
};
$translateProvider.translations('it', {
export const terms = {
PHRASE_EXPORT_DESC: 'Un export dettagliato in formato JSON della tua configurazione per essere usato in altri siti o tools',
'A-Rated': 'Classe A',
about: 'Info su Coriolis',
@@ -129,5 +126,4 @@ angular.module('app').config(['$translateProvider', 'localeFormatProvider', func
version: 'versione',
yes: 'sì',
PHRASE_BACKUP_DESC: 'Esportazione di tutti i dati su Coriolis per salvarli o trasferirli in un altro Browser/dispositivo'
});
}]);
};

View File

@@ -1,23 +0,0 @@
angular.module('app').config(['$translateProvider', function($translateProvider) {
$translateProvider
.useSanitizeValueStrategy('escapeParameters')
.useStorage('Persist')
.fallbackLanguage('en') // Use English as default/fallback language
.registerAvailableLanguageKeys(['en', 'de', 'es', 'fr', 'it', 'ru'], {
'en*': 'en',
'de*': 'de',
'es*': 'es',
'fr*': 'fr',
'it*': 'it',
'ru*': 'ru'
})
.determinePreferredLanguage();
}])
.value('Languages', {
en: 'English',
de: 'Deutsh',
it: 'Italiano',
es: 'Español',
fr: 'Français',
ru: 'ру́сский'
});

View File

@@ -1,7 +1,4 @@
angular.module('app').config(['$translateProvider', 'localeFormatProvider', function($translateProvider, localeFormatProvider) {
// Declare number format settings
localeFormatProvider.addFormat('ru', {
export const formats = {
decimal: ',',
thousands: '\xa0',
grouping: [3],
@@ -14,9 +11,9 @@ angular.module('app').config(['$translateProvider', 'localeFormatProvider', func
shortDays: ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'],
months: ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'],
shortMonths: ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек']
});
};
$translateProvider.translations('ru', {
export const terms = {
PHRASE_EXPORT_DESC: 'Подробный экспорта JSON вашего телосложения для использования в других местах и инструментов',
'A-Rated': 'А-Класса',
about: 'О сайте',
@@ -230,5 +227,4 @@ angular.module('app').config(['$translateProvider', 'localeFormatProvider', func
WEP: 'ОРУ',
yes: 'Да',
PHRASE_BACKUP_DESC: 'Сохраните все данные перед переносом в другой браузер или устройство'
});
}]);
};

View File

@@ -0,0 +1,12 @@
import { Component } from 'react';
export default class ShipyardPage extends Component {
constructor(props) {
super(props);
}
render() {
return <div>Page {this.props.path} Not Found</div>;
}
}

180
app/js/pages/ShipyardPage.jsx Executable file
View File

@@ -0,0 +1,180 @@
import { Component } from 'react';
import { Ships, Components } from 'coriolis-data';
import cn from 'classnames';
import Ship from 'Ship';
function countHp(slot) {
this.hp[slot.maxClass]++;
this.hpCount++;
}
function countInt(slot) {
var crEligible = !slot.eligible || slot.eligible.cr;
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
this.intCount++;
this.maxCargo += crEligible ? Components.findInternal('cr', slot.maxClass, 'E').capacity : 0;
}
function shipSummary(shipId, shipData) {
let summary = {
id: shipId,
hpCount: 0,
intCount: 0,
maxCargo: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
int: [0, 0, 0, 0, 0, 0, 0, 0] // Sizes 1 - 8
};
Object.assign(summary, shipData.properties);
let ship = new Ship(shipId, shipData.properties, shipData.slots);
// Build Ship
ship.buildWith(shipData.defaults); // Populate with stock/default components
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
ship.optimizeMass({ th: ship.standard[1].maxClass + 'A' }); // Optmize mass with Max Thrusters
summary.topSpeed = ship.topSpeed;
summary.topBoost = ship.topBoost;
return summary;
}
let shipSummaries = [];
for (var s in Ships) {
shipSummaries.push(shipSummary(s, Ships[s]));
}
export default class ShipyardPage extends Component {
constructor(props) {
super(props);
this.state = {
title: 'Coriolis - Shipyard',
shipPredicate: 'properties.name',
shipDesc = false
};
}
shouldComponentUpdate(nextProps, nextState) {
// Only on language change. Context?
return false;
}
/**
* Sort ships
* @param {object} key Sort predicate
*/
_sortShips(shipPredicate, shipPredicateIndex) {
let shipDesc = this.state.shipPredicate == shipPredicate ? !this.state.shipDesc : this.state.shipDesc;
this.setState({ shipPredicate, shipDesc, shipPredicateIndex });
};
render() {
let sortShips = this._sortShips.bind(this);
let shipPredicate = this.state.shipPredicate;
let shipPredicateIndex = this.state.shipPredicateIndex;
let shipRows = [];
// Sort shipsOverview
shipSummaries.sort((a, b) => {
let valA = a[shipPredicate], valB = b[shipPredicate];
if (shipPredicateIndex != undefined) {
valA = valA[shipPredicateIndex];
valB = valB[shipPredicateIndex];
}
return this.state.shipDesc ? (valA > valB) : (valB > valA);
});
for (s of shipSummaries) {
shipRows.push(
<tr className={'highlight'}>
<td className={'le'}><a ui-sref='outfit({shipId: s.id})'>{s.name}</a></td>
<td className={'le'}>{s.manufacturer}</td>
<td className={'cap'}>{SZM[s.class] | translate}</td>
<td className={'ri'}>{{fCrd(s.speed)}} <u translate>m/s</u></td>
<td className={'ri'}>{{fCrd(s.boost)}} <u translate>m/s</u></td>
<td className={'ri'}>{s.baseArmour}</td>
<td className={'ri'}>{{fCrd(s.baseShieldStrength)}} <u translate>Mj</u></td>
<td className={'ri'}>{{fCrd(s.topSpeed)}} <u translate>m/s</u></td>
<td className={'ri'}>{{fCrd(s.topBoost)}} <u translate>m/s</u></td>
<td className={'ri'}>{{fRound(s.maxJumpRange)}} <u translate>LY</u></td>
<td className={'ri'}>{{fCrd(s.maxCargo)}} <u translate>T</u></td>
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
<td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td>
<td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td>
<td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td>
<td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td>
<td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td>
<td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td>
<td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td>
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
<td className={'ri'}>{fCrd(s.hullMass)} <u translate>T</u></td>
<td className={'ri'}>{s.masslock}</td>
<td className={'ri'}>{fCrd(s.retailCost)} <u translate>CR</u></td>
</tr>
);
}
return (
<div id='shipyard'>
<div className={'scroll-x'}>
<table style={{ fontSize:'0.85em', whiteSpace:'nowrap' }}>
<thead>
<tr className={'main'}>
<th rowspan='2' className={'sortable le'} onClick={sortShips('name')} translate='ship'></th>
<th rowspan='2' className={'sortable'} onClick={sortShips('manufacturer')} translate='manufacturer'></th>
<th rowspan='2' className={'sortable'} onClick={sortShips('class')} translate='size'></th>
<th colspan='4' translate='base'></th>
<th colspan='4' translate='max'></th>
<th colspan='5' className={'sortable'} onClick={sortShips('hpCount')} translate='hardpoints'></th>
<th colspan='8' className={'sortable'} onClick={sortShips('intCount')} translate='internal compartments'></th>
<th rowspan='2' className={'sortable'} onClick={sortShips('hullMass')} translate='hull'></th>
<th rowspan='2' className={'sortable'} onClick={sortShips('masslock')} translate='MLF'></th>
<th rowspan='2' className={'sortable'} onClick={sortShips('retailCost')} translate='cost'></th>
</tr>
<tr>
{/* Base */}
<th className={'sortable lft'} onClick={sortShips('speed')} translate='speed'></th>
<th className={'sortable'} onClick={sortShips('boost')} translate='boost'></th>
<th className={'sortable'} onClick={sortShips('baseArmour')} translate='armour'></th>
<th className={'sortable'} onClick={sortShips('baseShieldStrength')} translate='shields'></th>
{/* Max */}
<th className={'sortable lft'} onClick={sortShips('topSpeed')} translate='speed'></th>
<th className={'sortable'} onClick={sortShips('topBoost')} translate='boost'></th>
<th className={'sortable'} onClick={sortShips('maxJumpRange')} translate='jump'></th>
<th className={'sortable'} onClick={sortShips('maxCargo')} translate='cargo'></th>
{/* Hardpoints */}
<th className={'sortable lft'} onClick={sortShips('hp',1)} translate='S'></th>
<th className={'sortable'} onClick={sortShips('hp', 2)} translate='M'></th>
<th className={'sortable'} onClick={sortShips('hp', 3)} translate='L'></th>
<th className={'sortable'} onClick={sortShips('hp', 4)} translate='H'></th>
<th className={'sortable'} onClick={sortShips('hp', 0)} translate='U'></th>
{/* Internal */}
<th className={'sortable lft'} onClick={sortShips('int', 0)} >1</th>
<th className={'sortable'} onClick={sortShips('int', 1)} >2</th>
<th className={'sortable'} onClick={sortShips('int', 2)} >3</th>
<th className={'sortable'} onClick={sortShips('int', 3)} >4</th>
<th className={'sortable'} onClick={sortShips('int', 4)} >5</th>
<th className={'sortable'} onClick={sortShips('int', 5)} >6</th>
<th className={'sortable'} onClick={sortShips('int', 6)} >7</th>
<th className={'sortable'} onClick={sortShips('int', 7)} >8</th>
</tr>
</thead>
<tbody>
{shipRows}
</tbody>
</table>
</div>
</div>
);
}
}

View File

@@ -1,36 +0,0 @@
angular.module('app').provider('localeFormat', function localeFormatProvider() {
var formats = {
en: {
decimal: '.',
thousands: ',',
grouping: [3],
currency: ['$', ''],
dateTime: '%a %b %e %X %Y',
date: '%m/%d/%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
}
};
function LocaleFormat(formatMap) {
this.formatMap = formatMap;
this.get = function(lang) {
return this.formatMap[lang] ? this.formatMap[lang] : this.formatMap.en;
};
}
this.addFormat = function(langCode, formatDetails) {
formats[langCode] = formatDetails;
};
this.$get = [function() {
return new LocaleFormat(formats);
}];
});

View File

@@ -1,314 +0,0 @@
/**
* [description]
*/
angular.module('app').service('Persist', ['$window', 'lodash', function($window, _) {
var LS_KEY_BUILDS = 'builds';
var LS_KEY_COMPARISONS = 'comparisons';
var LS_KEY_LANG = 'NG_TRANSLATE_LANG_KEY';
var LS_KEY_COST_TAB = 'costTab';
var LS_KEY_INSURANCE = 'insurance';
var LS_KEY_DISCOUNTS = 'discounts';
var localStorage = $window.localStorage;
var buildJson = null;
var comparisonJson = null;
// Safe check to determine if localStorage is enabled
try {
localStorage.setItem('s', 1);
localStorage.removeItem('s');
buildJson = localStorage.getItem(LS_KEY_BUILDS);
comparisonJson = localStorage.getItem(LS_KEY_COMPARISONS);
this.lsEnabled = true;
} catch(e) {
this.lsEnabled = false;
}
this.builds = buildJson ? angular.fromJson(buildJson) : {};
this.comparisons = comparisonJson ? angular.fromJson(comparisonJson) : {};
var buildCount = Object.keys(this.builds).length;
this.state = {
buildCount: buildCount,
hasBuilds: buildCount > 0,
hasComparisons: Object.keys(this.comparisons).length > 0
};
this.put = function(name, value) {
if (!this.lsEnabled) {
return;
}
localStorage.setItem(name, value);
};
this.get = function(name) {
return this.lsEnabled ? localStorage.getItem(name) : null;
};
this.getLangCode = function() {
return this.lsEnabled ? localStorage.getItem(LS_KEY_LANG) : null;
};
/**
* Persist a ship build in local storage.
*
* @param {string} shipId The unique id for a model of ship
* @param {string} name The name of the build
* @param {string} code The serialized code
*/
this.saveBuild = function(shipId, name, code) {
if (!this.lsEnabled) {
return;
}
if (!this.builds[shipId]) {
this.builds[shipId] = {};
}
if (!this.builds[shipId][name]) {
this.state.buildCount++;
this.state.hasBuilds = true;
}
this.builds[shipId][name] = code;
// Persist updated build collection to localStorage
localStorage.setItem(LS_KEY_BUILDS, angular.toJson(this.builds));
};
/**
* Get the serialized code/string for a build. Returns null if a
* build is not found.
*
* @param {string} shipId The unique id for a model of ship
* @param {string} name The name of the build
* @return {string} The serialized build string.
*/
this.getBuild = function(shipId, name) {
if (this.builds[shipId] && this.builds[shipId][name]) {
return this.builds[shipId][name];
}
return null;
};
/**
* Delete a build from local storage. It will also delete the ship build collection if
* it becomes empty
*
* @param {string} shipId The unique id for a model of ship
* @param {string} name The name of the build
*/
this.deleteBuild = function(shipId, name) {
if (this.lsEnabled && this.builds[shipId][name]) {
delete this.builds[shipId][name];
if (Object.keys(this.builds[shipId]).length === 0) {
delete this.builds[shipId];
this.state.buildCount--;
this.state.hasBuilds = this.state.buildCount > 0;
}
// Persist updated build collection to localStorage
localStorage.setItem(LS_KEY_BUILDS, angular.toJson(this.builds));
// Check if the build was used in existing comparisons
var comps = this.comparisons;
for (var c in comps) {
for (var i = 0; i < comps[c].builds.length; i++) { // For all builds in the current comparison
if (comps[c].builds[i].shipId == shipId && comps[c].builds[i].buildName == name) {
comps[c].builds.splice(i, 1);
break; // A build is unique ber comparison
}
}
}
localStorage.setItem(LS_KEY_COMPARISONS, angular.toJson(this.comparisons));
}
};
/**
* Persist a comparison in localstorage.
*
* @param {string} name The name of the comparison
* @param {array} builds Array of builds
* @param {array} facets Array of facet indices
*/
this.saveComparison = function(name, builds, facets) {
if (!this.lsEnabled) {
return;
}
if (!this.comparisons[name]) {
this.comparisons[name] = {};
}
this.comparisons[name] = {
facets: facets,
builds: _.map(builds, function(b) { return { shipId: b.id || b.shipId, buildName: b.buildName }; })
};
localStorage.setItem(LS_KEY_COMPARISONS, angular.toJson(this.comparisons));
this.state.hasComparisons = true;
};
/**
* [getComparison description]
* @param {string} name [description]
* @return {object} Object containing array of facets and ship id + build names
*/
this.getComparison = function(name) {
if (this.comparisons[name]) {
return this.comparisons[name];
}
return null;
};
/**
* Removes the comparison from localstorage.
* @param {string} name Comparison name
*/
this.deleteComparison = function(name) {
if (this.lsEnabled && this.comparisons[name]) {
delete this.comparisons[name];
localStorage.setItem(LS_KEY_COMPARISONS, angular.toJson(this.comparisons));
this.state.hasComparisons = Object.keys(this.comparisons).length > 0;
}
};
/**
* Delete all builds and comparisons from localStorage
*/
this.deleteAll = function() {
angular.copy({}, this.builds); // Empty object but keep original instance
angular.copy({}, this.comparisons);
this.state.hasBuilds = false;
this.state.buildCount = 0;
if (this.lsEnabled) {
localStorage.removeItem(LS_KEY_BUILDS);
localStorage.removeItem(LS_KEY_COMPARISONS);
}
};
this.getAll = function() {
var data = {};
data[LS_KEY_BUILDS] = this.builds;
data[LS_KEY_COMPARISONS] = this.comparisons;
data[LS_KEY_INSURANCE] = this.getInsurance();
data[LS_KEY_DISCOUNTS] = this.getDiscount();
return data;
};
/**
* Get the saved insurance type
* @return {string} The name of the saved insurance type of null
*/
this.getInsurance = function() {
if (this.lsEnabled) {
return localStorage.getItem(LS_KEY_INSURANCE);
}
return null;
};
/**
* Persist selected insurance type
* @param {string} name Insurance type name
*/
this.setInsurance = function(name) {
if (this.lsEnabled) {
return localStorage.setItem(LS_KEY_INSURANCE, name);
}
};
/**
* Persist selected discount
* @param {number} val Discount value/amount
*/
this.setDiscount = function(val) {
if (this.lsEnabled) {
return localStorage.setItem(LS_KEY_DISCOUNTS, angular.toJson(val));
}
};
/**
* Get the saved discount
* @return {number} val Discount value/amount
*/
this.getDiscount = function() {
if (this.lsEnabled) {
return angular.fromJson(localStorage.getItem(LS_KEY_DISCOUNTS));
}
return null;
};
/**
* Persist selected cost tab
* @param {number} val Discount value/amount
*/
this.setCostTab = function(tabName) {
if (this.lsEnabled) {
return localStorage.setItem(LS_KEY_COST_TAB, tabName);
}
};
/**
* Get the saved discount
* @return {number} val Discount value/amount
*/
this.getCostTab = function() {
if (this.lsEnabled) {
return localStorage.getItem(LS_KEY_COST_TAB);
}
return null;
};
/**
* Retrieve the last router state from local storage
* @return {object} state State object containing state name and params
*/
this.getState = function() {
if (this.lsEnabled) {
var state = localStorage.getItem('state');
if (state) {
return angular.fromJson(state);
}
}
return null;
};
/**
* Save the current router state to localstorage
* @param {object} state State object containing state name and params
*/
this.setState = function(state) {
if (this.lsEnabled) {
localStorage.setItem('state', angular.toJson(state));
}
};
/**
* Retrieve the last router state from local storage
* @return {number} size Ratio
*/
this.getSizeRatio = function() {
if (this.lsEnabled) {
var ratio = localStorage.getItem('sizeRatio');
if (!isNaN(ratio) && ratio > 0.6) {
return ratio;
}
}
return 1;
};
/**
* Save the current size ratio to localstorage
* @param {number} sizeRatio
*/
this.setSizeRatio = function(sizeRatio) {
if (this.lsEnabled) {
localStorage.setItem('sizeRatio', sizeRatio);
}
};
/**
* Check if localStorage is enabled/active
* @return {Boolean} True if localStorage is enabled
*/
this.isEnabled = function() {
return this.lsEnabled;
};
}]);

View File

@@ -1,245 +0,0 @@
/**
* Service managing seralization and deserialization of models for use in URLs and persistene.
*/
angular.module('app').service('Serializer', ['lodash', 'GroupMap', 'MountMap', 'ShipsDB', 'Ship', 'Components', '$state', function(_, GroupMap, MountMap, ShipsDB, Ship, Components, $state) {
/**
* Serializes the ships selected components for all slots to a URL friendly string.
* @param {Ship} ship The ship to be serialized.
* @return {string} Encoded string of components
*/
this.fromShip = function(ship) {
var power = {
enabled: [ship.cargoHatch.enabled ? 1 : 0],
priorities: [ship.cargoHatch.priority]
};
var data = [
ship.bulkheads.id,
_.map(ship.standard, mapGroup, power),
_.map(ship.hardpoints, mapGroup, power),
_.map(ship.internal, mapGroup, power),
'.',
LZString.compressToBase64(power.enabled.join('')).replace(/\//g, '-'),
'.',
LZString.compressToBase64(power.priorities.join('')).replace(/\//g, '-')
];
return _.flatten(data).join('');
};
/**
* Updates an existing ship instance's slots with components determined by the
* code.
*
* @param {Ship} ship The ship instance to be updated
* @param {string} dataString The string to deserialize
*/
this.toShip = function(ship, dataString) {
var standard = new Array(ship.standard.length),
hardpoints = new Array(ship.hardpoints.length),
internal = new Array(ship.internal.length),
parts = dataString.split('.'),
priorities = null,
enabled = null,
code = parts[0];
if (parts[1]) {
enabled = LZString.decompressFromBase64(parts[1].replace(/-/g, '/')).split('');
}
if (parts[2]) {
priorities = LZString.decompressFromBase64(parts[2].replace(/-/g, '/')).split('');
}
decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, standard, 1)));
ship.buildWith(
{
bulkheads: code.charAt(0) * 1,
standard: standard,
hardpoints: hardpoints,
internal: internal
},
priorities,
enabled
);
};
this.toDetailedBuild = function(buildName, ship, code) {
var standard = ship.standard,
hardpoints = ship.hardpoints,
internal = ship.internal;
var data = {
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/2.json#',
name: buildName,
ship: ship.name,
references: [{
name: 'Coriolis.io',
url: $state.href('outfit', { shipId: ship.id, code: code, bn: buildName }, { absolute: true }),
code: code,
shipId: ship.id
}],
components: {
standard: {
bulkheads: ship.bulkheads.c.name,
cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 },
powerPlant: { class: standard[0].c.class, rating: standard[0].c.rating, enabled: Boolean(standard[0].enabled), priority: standard[0].priority + 1 },
thrusters: { class: standard[1].c.class, rating: standard[1].c.rating, enabled: Boolean(standard[1].enabled), priority: standard[1].priority + 1 },
frameShiftDrive: { class: standard[2].c.class, rating: standard[2].c.rating, enabled: Boolean(standard[2].enabled), priority: standard[2].priority + 1 },
lifeSupport: { class: standard[3].c.class, rating: standard[3].c.rating, enabled: Boolean(standard[3].enabled), priority: standard[3].priority + 1 },
powerDistributor: { class: standard[4].c.class, rating: standard[4].c.rating, enabled: Boolean(standard[4].enabled), priority: standard[4].priority + 1 },
sensors: { class: standard[5].c.class, rating: standard[5].c.rating, enabled: Boolean(standard[5].enabled), priority: standard[5].priority + 1 },
fuelTank: { class: standard[6].c.class, rating: standard[6].c.rating, enabled: Boolean(standard[6].enabled), priority: standard[6].priority + 1 }
},
hardpoints: _.map(_.filter(hardpoints, function(slot) { return slot.maxClass > 0; }), slotToSchema),
utility: _.map(_.filter(hardpoints, function(slot) { return slot.maxClass === 0; }), slotToSchema),
internal: _.map(internal, slotToSchema)
},
stats: {}
};
for (var stat in ship) {
if (!isNaN(ship[stat])) {
data.stats[stat] = Math.round(ship[stat] * 100) / 100;
}
}
return data;
};
this.fromDetailedBuild = function(detailedBuild) {
var shipId = _.findKey(ShipsDB, { properties: { name: detailedBuild.ship } });
if (!shipId) {
throw 'No such ship: ' + detailedBuild.ship;
}
var comps = detailedBuild.components;
var standard = comps.standard;
var priorities = [ standard.cargoHatch && standard.cargoHatch.priority !== undefined ? standard.cargoHatch.priority - 1 : 0 ];
var enabled = [ standard.cargoHatch && standard.cargoHatch.enabled !== undefined ? standard.cargoHatch.enabled : true ];
var shipData = ShipsDB[shipId];
var ship = new Ship(shipId, shipData.properties, shipData.slots);
var bulkheads = Components.bulkheadIndex(standard.bulkheads);
if (bulkheads < 0) {
throw 'Invalid bulkheads: ' + standard.bulkheads;
}
var standardIds = _.map(
['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank'],
function(c) {
if (!standard[c].class || !standard[c].rating) {
throw 'Invalid value for ' + c;
}
priorities.push(standard[c].priority === undefined ? 0 : standard[c].priority - 1);
enabled.push(standard[c].enabled === undefined ? true : standard[c].enabled);
return standard[c].class + standard[c].rating;
}
);
var internal = _.map(comps.internal, function(c) { return c ? Components.findInternalId(c.group, c.class, c.rating, c.name) : 0; });
var hardpoints = _.map(comps.hardpoints, function(c) {
return c ? Components.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount], c.missile) : 0;
}).concat(_.map(comps.utility, function(c) {
return c ? Components.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount]) : 0;
}));
// The ordering of these arrays must match the order in which they are read in Ship.buildWith
priorities = priorities.concat(_.map(comps.hardpoints, function(c) { return (!c || c.priority === undefined) ? 0 : c.priority - 1; }),
_.map(comps.utility, function(c) { return (!c || c.priority === undefined) ? 0 : c.priority - 1; }),
_.map(comps.internal, function(c) { return (!c || c.priority === undefined) ? 0 : c.priority - 1; }));
enabled = enabled.concat(_.map(comps.hardpoints, function(c) { return (!c || c.enabled === undefined) ? true : c.enabled * 1; }),
_.map(comps.utility, function(c) { return (!c || c.enabled === undefined) ? true : c.enabled * 1; }),
_.map(comps.internal, function(c) { return (!c || c.enabled === undefined) ? true : c.enabled * 1; }));
ship.buildWith({ bulkheads: bulkheads, standard: standardIds, hardpoints: hardpoints, internal: internal }, priorities, enabled);
return ship;
};
this.toDetailedExport = function(builds) {
var data = [];
for (var shipId in builds) {
for (var buildName in builds[shipId]) {
var code = builds[shipId][buildName];
var shipData = ShipsDB[shipId];
var ship = new Ship(shipId, shipData.properties, shipData.slots);
this.toShip(ship, code);
data.push(this.toDetailedBuild(buildName, ship, code));
}
}
return data;
};
this.fromComparison = function(name, builds, facets, predicate, desc) {
var shipBuilds = [];
builds.forEach(function(b) {
shipBuilds.push({ s: b.id, n: b.buildName, c: this.fromShip(b) });
}.bind(this));
return LZString.compressToBase64(angular.toJson({
n: name,
b: shipBuilds,
f: facets,
p: predicate,
d: desc ? 1 : 0
})).replace(/\//g, '-');
};
this.toComparison = function(code) {
return angular.fromJson(LZString.decompressFromBase64(code.replace(/-/g, '/')));
};
/**
* Utility function to retrieve a safe string for selected component for a slot.
* Used for serialization to code only.
*
* @private
* @param {object} slot The slot object.
* @return {string} The id of the selected component or '-' if none selected
*/
function mapGroup(slot) {
this.enabled.push(slot.enabled ? 1 : 0);
this.priorities.push(slot.priority);
return slot.id === null ? '-' : slot.id;
}
function decodeToArray(code, arr, codePos) {
for (var i = 0; i < arr.length; i++) {
if (code.charAt(codePos) == '-') {
arr[i] = 0;
codePos++;
} else {
arr[i] = code.substring(codePos, codePos + 2);
codePos += 2;
}
}
return codePos;
}
function slotToSchema(slot) {
if (slot.c) {
var o = { class: slot.c.class, rating: slot.c.rating, enabled: Boolean(slot.enabled), priority: slot.priority + 1, group: GroupMap[slot.c.grp] };
if (slot.c.name) {
o.name = slot.c.name;
}
if (slot.c.mode) {
o.mount = MountMap[slot.c.mode];
}
if (slot.c.missile) {
o.missile = slot.c.missile;
}
return o;
}
return null;
}
}]);

View File

@@ -0,0 +1,84 @@
/**
* 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
*/
export function jumpRange(mass, fsd, fuel) {
return Math.pow(Math.min(fuel === undefined ? fsd.maxfuel : fuel, fsd.maxfuel) / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass;
}
/**
* Calculate the total range based on mass and a specific FSD, and all fuel available
*
* @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 The total fuel available
* @return {number} Distance in Light Years
*/
export function totalRange(mass, fsd, fuel) {
var fuelRemaining = fuel % fsd.maxfuel; // Fuel left after making N max jumps
var jumps = Math.floor(fuel / fsd.maxfuel);
mass += fuelRemaining;
// Going backwards, start with the last jump using the remaining fuel
var totalRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass : 0;
// For each max fuel jump, calculate the max jump range based on fuel mass left in the tank
for (var j = 0; j < jumps; j++) {
mass += fsd.maxfuel;
totalRange += Math.pow(fsd.maxfuel / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass;
}
return totalRange;
};
/**
* Calculate the a ships shield strength based on mass, shield generator and shield boosters used.
*
* @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
*/
export function shieldStrength(mass, shields, sg, multiplier) {
var opt;
if (mass < sg.minmass) {
return shields * multiplier * sg.minmul;
}
if (mass > sg.maxmass) {
return shields * multiplier * sg.maxmul;
}
if (mass < sg.optmass) {
opt = (sg.optmass - mass) / (sg.optmass - sg.minmass);
opt = 1 - Math.pow(1 - opt, 0.87);
return shields * multiplier * ((opt * sg.minmul) + ((1 - opt) * sg.optmul));
} else {
opt = (sg.optmass - mass) / (sg.maxmass - sg.optmass);
opt = -1 + Math.pow(1 + opt, 2.425);
return shields * multiplier * ( (-1 * opt * sg.maxmul) + ((1 + opt) * sg.optmul) );
}
}
/**
* Calculate the a ships speed based on mass, and thrusters.
*
* @param {number} mass Current mass of the ship
* @param {number} baseSpeed Base speed m/s for ship
* @param {number} baseBoost Base boost speed m/s for ship
* @param {object} thrusters The Thrusters used
* @param {number} pipSpeed Speed pip multiplier
* @return {object} Approximate speed by pips
*/
export function speed(mass, baseSpeed, baseBoost, thrusters, pipSpeed) {
var multiplier = mass > thrusters.maxmass ? 0 : ((1 - thrusters.M) + (thrusters.M * Math.pow(3 - (2 * Math.max(0.5, mass / thrusters.optmass)), thrusters.P)));
var speed = baseSpeed * multiplier;
return {
'0 Pips': speed * (1 - (pipSpeed * 4)),
'2 Pips': speed * (1 - (pipSpeed * 2)),
'4 Pips': speed,
'boost': baseBoost * multiplier
};
}

183
app/js/shipyard/Constants.js Executable file
View File

@@ -0,0 +1,183 @@
export const ArmourMultiplier = [
1, // Lightweight
1.4, // Reinforced
1.945, // Military
1.945, // Mirrored
1.945 // Reactive
];
export const SizeMap = ['', 'small', 'medium', 'large', 'capital'];
// Map to lookup group labels/names for component grp, used for JSON Serialization
const ModuleGroupToName = {
// Standard
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: 'Scanner',
am: 'Auto Field-Maintenance Unit',
cr: 'Cargo Rack',
fi: 'Frame Shift Drive Interdictor',
hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package',
rf: 'Refinery',
scb: 'Shield Cell Bank',
sg: 'Shield Generator',
psg: 'Prismatic Shield Generator',
dc: 'Docking Computer',
fx: 'Fuel Transfer Limpet Controller',
pc: 'Prospector Limpet Controller',
cc: 'Collector Limpet Controller',
// 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'
};
let GrpNameToCodeMap = {};
for (let grp in ModuleGroupToName) {
GrpNameToCodeMap[ModuleGroupToName[grp]] = grp;
}
export ModuleGroupToName;
export const ModuleNameToGroup = GrpNameToCodeMap;
export const MountMap = {
'F': 'Fixed',
'G': 'Gimballed',
'T': 'Turret',
'Fixed': 'F',
'Gimballed': 'G',
'Turret': 'T'
};
export const BulkheadNames = [
'Lightweight Alloy',
'Reinforced Alloy',
'Military Grade Composite',
'Mirrored Surface Composite',
'Reactive Surface Composite'
];
/**
* Array of all Ship properties (facets) organized into groups
* used for ship comparisons.
*
* @type {Array}
*/
export const ShipFacets = [
{ // 0
title: 'agility',
props: ['agility'],
unit: '',
fmt: 'fCrd'
},
{ // 1
title: 'speed',
props: ['topSpeed', 'topBoost'],
lbls: ['thrusters', 'boost'],
unit: 'm/s',
fmt: 'fCrd'
},
{ // 2
title: 'armour',
props: ['armour'],
unit: '',
fmt: 'fCrd'
},
{ // 3
title: 'shields',
props: ['shieldStrength'],
unit: 'MJ',
fmt: 'fRound'
},
{ // 4
title: 'jump range',
props: ['unladenRange', 'fullTankRange', 'ladenRange'],
lbls: ['max', 'full tank', 'laden'],
unit: 'LY',
fmt: 'fRound'
},
{ // 5
title: 'mass',
props: ['unladenMass', 'ladenMass'],
lbls: ['unladen', 'laden'],
unit: 'T',
fmt: 'fRound'
},
{ // 6
title: 'cargo',
props: ['cargoCapacity'],
unit: 'T',
fmt: 'fRound'
},
{ // 7
title: 'fuel',
props: ['fuelCapacity'],
unit: 'T',
fmt: 'fRound'
},
{ // 8
title: 'power',
props: ['powerRetracted', 'powerDeployed', 'powerAvailable'],
lbls: ['retracted', 'deployed', 'available'],
unit: 'MW',
fmt: 'fPwr'
},
{ // 9
title: 'cost',
props: ['totalCost'],
unit: 'CR',
fmt: 'fCrd'
},
{ // 10
title: 'total range',
props: ['unladenTotalRange', 'ladenTotalRange'],
lbls: ['unladen', 'laden'],
unit: 'LY',
fmt: 'fRound'
},
{ // 11
title: 'DPS',
props: ['totalDps'],
lbls: ['DPS'],
unit: '',
fmt: 'fRound'
}
];
/**
* Set of all available / theoretical discounts
*/
export const Discounts = {
'0%': 1,
'5%': 0.95,
'10%': 0.90,
'15%': 0.85,
'20%': 0.80,
'25%': 0.75
};

139
app/js/shipyard/ModuleSet.js Executable file
View File

@@ -0,0 +1,139 @@
function filter(arr, maxClass, minClass, mass) {
return arr.filter(m => m.class <= maxClass && m.class >= minClass && (m.maxmass === undefined || mass <= m.maxmass));
}
function filterToArray(data, maxClass, minClass, mass) {
let arr = [];
for (let id in data) {
let m = data[id];
if (m.class <= maxClass && m.class >= minClass && (m.maxmass === undefined || mass <= m.maxmass)) {
arr.push(m);
}
}
return arr;
}
export default class ModuleSet {
constructor(modules, mass, maxStandardArr, maxInternal, maxHardPoint) {
this.mass = mass;
this.standard = {};
this.internal = {};
this.hardpoints = {};
this.hpClass = {};
this.intClass = {};
this.standard[0] = filterToArray(modules.standard[0], maxStandardArr[0], 0, mass); // Power Plant
this.standard[2] = filterToArray(modules.standard[2], maxStandardArr[2], 0, mass); // FSD
this.standard[4] = filterToArray(modules.standard[4], maxStandardArr[4], 0, mass); // Power Distributor
this.standard[6] = filterToArray(modules.standard[6], maxStandardArr[6], 0, mass); // Fuel Tank
// Thrusters, filter modules by class only (to show full list of ratings for that class)
let ths = modules.standard[1];
let minThrusterClass = Object.keys(modules.standard[1]).reduce(
(clazz, thId) => (ths[thId].maxmass >= mass && ths[thId].class < clazz) ? ths[thId].class : clazz,
maxStandardArr[1]
);
this.standard[1] = filterToArray(modules.standard[1], maxStandardArr[1], minThrusterClass, 0); // Thrusters
// Slots where module class must be equal to slot class
this.standard[3] = filterToArray(modules.standard[3], maxStandardArr[3], maxStandardArr[3], 0); // Life Supprt
this.standard[5] = filterToArray(modules.standard[5], maxStandardArr[5], maxStandardArr[5], mass); // Sensors
for (let h in modules.hardpoints) {
this.hardpoints[h] = filter(modules.hardpoints[h], maxHardPoint, 0, mass);
}
for (let g in modules.internal) {
this.internal[g] = filter(modules.internal[g], maxInternal, 0, mass);
}
}
/**
* Determine the modules that areeligible for an internal slot
* @param {integer} c The max class module that can be mounted in the slot
* @param {Object} eligible) The map of eligible internal groups
* @return {object} A map of all eligible modules by group
*/
getInts(c, eligible) {
let o = {};
for (let key in this.internal) {
if (eligible && !eligible[key]) {
continue;
}
let data = filter(this.internal[key], c, 0, this.mass);
if (data.length) { // If group is not empty
o[key] = data;
}
}
return o;
}
/**
* Determining the modules that are eligible for an hardpoint slot
* @param {integer} c The max class module that can be mounted in the slot
* @param {Object} eligible) The map of eligible hardpoint groups
* @return {object} A map of all eligible modules by group
*/
getHps(c, eligible) {
var o = {};
for (var key in this.hardpoints) {
if (eligible && !eligible[key]) {
continue;
}
var data = filter(this.hardpoints[key], c, c ? 1 : 0, this.mass);
if (data.length) { // If group is not empty
o[key] = data;
}
}
return o;
}
lightestPowerDist(boostEnergy) {
var pd = this.standard[4][0];
for (let p of this.standard[4]) {
if (p.mass < pd.mass && p.enginecapacity >= boostEnergy) {
pd = p;
}
}
return pd.class + pd.rating;
};
lightestThruster(ladenMass) {
var th = this.standard[1][0];
for (t of this.standard[1]) {
if (t.mass < th.mass && t.maxmass >= ladenMass) {
th = t;
}
}
return th.class + th.rating;
};
lightestShieldGenerator(hullMass) {
var sg = this.internal.sg[0];
for (let s of this.internal.sg) {
if (s.mass < sg.mass && s.minmass <= hullMass && s.maxmass > hullMass) {
sg = s;
}
}
return sg.id;
};
lightestPowerPlant(powerUsed, rating) {
var pp = this.standard[0][0];
for (let p of this.standard[0]) {
if (p.mass < pp.mass && p.pGen >= powerUsed) {
pp = p;
}
}
return pp.class + (pp.rating != 'D' || rating == 'A' ? 'A' : 'D'); // Use A rated if C,E
}
}

175
app/js/shipyard/ModuleUtils.js Executable file
View File

@@ -0,0 +1,175 @@
import { ModuleNameToGroup, BulkheadNames } from 'Constants';
import ModuleSet from 'ModuleSet';
import { Ships, Modules } from 'coriolis-data';
export function cargoHatch() {
return { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 };
};
export function standard(typeIndex, componentId) {
return Modules.standard[typeIndex][componentId];
};
export function hardpoints(id) {
for (let n in Modules.hardpoints) {
let group = Modules.hardpoints[n];
for (let i = 0; i < group.length; i++) {
if (group[i].id == id) {
return group[i];
}
}
}
return null;
};
export function internal(id) {
for (let n in Modules.internal) {
let group = Modules.internal[n];
for (let i = 0; i < group.length; i++) {
if (group[i].id == id) {
return group[i];
}
}
}
return null;
};
/**
* Finds an internal Component based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique component name must be provided
*
* @param {string} groupName [Optional] Full name or abbreviated name for component group
* @param {integer} clss Component Class
* @param {string} rating Component Rating
* @param {string} name [Optional] Long/unique name for component -e.g. 'Advanced Discover Scanner'
* @return {String} The id of the component if found, null if not found
*/
export function findInternal(groupName, clss, rating, name) {
let groups = {};
if (groupName) {
if (Modules.internal[groupName]) {
groups[groupName] = Modules.internal[groupName];
} else {
let grpCode = ModuleNameToGroup[groupName];
if (grpCode && Modules.internal[grpCode]) {
groups[grpCode] = Modules.internal[grpCode];
}
}
} else if (name) {
groups = Modules.internal;
}
for (let g in groups) {
let group = groups[g];
for (let i = 0, l = group.length; i < l; i++) {
if (group[i].class == clss && group[i].rating == rating && ((!name && !group[i].name) || group[i].name == name)) {
return group[i];
}
}
}
return null;
}
/**
* Finds an internal Component ID based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique component name must be provided
*
* @param {string} groupName [Optional] Full name or abbreviated name for component group
* @param {integer} clss Component Class
* @param {string} rating Component Rating
* @param {string} name [Optional] Long/unique name for component -e.g. 'Advanced Discover Scanner'
* @return {String} The id of the component if found, null if not found
*/
export function findInternalId(groupName, clss, rating, name) {
let i = this.findInternal(groupName, clss, rating, name);
return i ? i.id : 0;
}
/**
* Finds a hardpoint Component based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique component name must be provided
*
* @param {string} groupName [Optional] Full name or abbreviated name for component group
* @param {integer} clss Component Class
* @param {string} rating [Optional] Component Rating
* @param {string} name [Optional] Long/unique name for component -e.g. 'Heat Sink Launcher'
* @param {string} mount Mount type - [F]ixed, [G]imballed, [T]urret
* @param {string} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the component if found, null if not found
*/
export function findHardpoint(groupName, clss, rating, name, mount, missile) {
let groups = {};
if (groupName) {
if (Modules.hardpoints[groupName]) {
groups[groupName] = Modules.hardpoints[groupName];
} else {
let grpCode = ModuleNameToGroup[groupName];
if (grpCode && Modules.hardpoints[grpCode]) {
groups[grpCode] = Modules.hardpoints[grpCode];
}
}
} else if (name) {
groups = Modules.hardpoints;
}
for (let g in groups) {
let group = groups[g];
for (let i = 0, l = group.length; i < l; i++) {
if (group[i].class == clss && (!rating || group[i].rating == rating) && group[i].mount == mount
&& ((!name && !group[i].name) || group[i].name == name)
&& ((!missile && !group[i].missile) || group[i].missile == missile)
) {
return group[i];
}
}
}
return null;
}
/**
* Finds a hardpoint Component ID based on Class, Rating, Group and/or name.
* At least one of Group name or unique component name must be provided
*
* @param {string} groupName [Optional] Full name or abbreviated name for component group
* @param {integer} clss Component Class
* @param {string} rating Component Rating
* @param {string} name [Optional] Long/unique name for component -e.g. 'Heat Sink Launcher'
* @param {string} mount Mount type - [F]ixed, [G]imballed, [T]urret
* @param {string} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the component if found, null if not found
*/
export function findHardpointId(groupName, clss, rating, name, mount, missile) {
let h = this.findHardpoint(groupName, clss, rating, name, mount, missile);
return h ? h.id : 0;
}
/**
* 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
*/
export function bulkheads(shipId, bulkheadsId) {
return Modules.bulkheads[shipId][bulkheadsId];
}
export function bulkheadIndex(bulkheadName) {
return Bulkheads.indexOf(bulkheadName);
}
/**
* Creates a new ModuleSet that contains all available components
* that the specified ship is eligible to use.
*
* @param {string} shipId Unique ship Id/Key
* @return {ModuleSet} The set of components the ship can install
*/
export function forShip(shipId) {
let ship = Ships[shipId];
let maxInternal = isNaN(ship.slots.internal[0]) ? ship.slots.internal[0].class : ship.slots.internal[0];
return new ModuleSet(Modules, ship.minMassFilter || ship.properties.hullMass + 5, ship.slots.standard, maxInternal, ship.slots.hardpoints[0]);
}

View File

@@ -1,71 +1,74 @@
angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength', 'calcJumpRange', 'calcTotalRange', 'calcSpeed', 'lodash', 'ArmourMultiplier', function(Components, calcShieldStrength, calcJumpRange, calcTotalRange, calcSpeed, _, ArmourMultiplier) {
import { ArmourMultiplier } from 'Constants';
import Calc from 'Calculations';
import ModuleUtils from 'ModuleUtils';
/**
* Returns the power usage type of a slot and it's particular component
const UNIQUE_MODULES = ['psg', 'sg', 'rf', 'fs'];
/**
* Returns the power usage type of a slot and it's particular modul
* @param {object} slot The Slot
* @param {object} component The component in the slot
* @param {object} modul The modul in the slot
* @return {string} The key for the power usage type
*/
function powerUsageType(slot, component) {
if (component) {
if (component.passive) {
function powerUsageType(slot, modul) {
if (modul) {
if (modul.passive) {
return 'retracted';
}
}
return slot.cat != 1 ? 'retracted' : 'deployed';
}
}
/**
* Ship model used to track all ship ModuleUtils and properties.
*/
export default class Ship {
/**
* Ship model used to track all ship components and properties.
*
* @param {string} id Unique ship Id / Key
* @param {object} properties Basic ship properties such as name, manufacturer, mass, etc
* @param {object} slots Collection of slot groups (standard/standard, internal, hardpoints) with their max class size.
*/
function Ship(id, properties, slots) {
constructor(id, properties, slots) {
this.id = id;
this.cargoHatch = { c: Components.cargoHatch(), type: 'SYS' };
this.cargoHatch = { m: ModuleUtils.cargoHatch(), type: 'SYS' };
this.bulkheads = { incCost: true, maxClass: 8 };
this.availCS = Components.forShip(id);
this.availCS = ModuleUtils.forShip(id);
for (var p in properties) { this[p] = properties[p]; } // Copy all base properties from shipData
for (var slotType in slots) { // Initialize all slots
var slotGroup = slots[slotType];
var group = this[slotType] = []; // Initialize Slot group (Standard, Hardpoints, Internal)
for (var i = 0; i < slotGroup.length; i++) {
if (typeof slotGroup[i] == 'object') {
group.push({ id: null, c: null, incCost: true, maxClass: slotGroup[i].class, eligible: slotGroup[i].eligible });
for (let slot of slotGroup) {
if (typeof slot == 'object') {
group.push({ id: null, m: null, incCost: true, maxClass: slot.class, eligible: slot.eligible });
} else {
group.push({ id: null, c: null, incCost: true, maxClass: slotGroup[i] });
group.push({ id: null, m: null, incCost: true, maxClass: slot });
}
}
}
// Make a Ship 'slot'/item similar to other slots
this.c = { incCost: true, type: 'SHIP', discountedCost: this.hullCost, c: { name: this.name, cost: this.hullCost } };
this.costList = _.union(this.internal, this.standard, 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.cargoHatch);
this.powerList.unshift(this.standard[1]); // Add Thrusters
this.powerList.unshift(this.standard[5]); // Add Sensors
this.powerList.unshift(this.standard[4]); // Add Power Distributor
this.powerList.unshift(this.standard[3]); // Add Life Support
this.powerList.unshift(this.standard[2]); // Add FSD
this.powerList.unshift(this.standard[0]); // Add Power Plant
this.m = { incCost: true, type: 'SHIP', discountedCost: this.hullCost, m: { name: this.name, cost: this.hullCost } };
this.costList = this.internal.concat(this.m, this.standard, this.hardpoints, this.bulkheads);
this.powerList = this.internal.concat(
this.cargoHatch,
this.standard[0], // Add Power Plant
this.standard[2], // Add FSD
this.standard[1], // Add Thrusters
this.standard[4], // Add Power Distributor
this.standard[5], // Add Sensors
this.standard[3], // Add Life Support
this.hardpoints
];
this.shipCostMultiplier = 1;
this.componentCostMultiplier = 1;
this.modulCostMultiplier = 1;
this.priorityBands = [
{ deployed: 0, retracted: 0, retOnly: 0 },
{ deployed: 0, retracted: 0, retOnly: 0 },
{ deployed: 0, retracted: 0, retOnly: 0 },
{ deployed: 0, retracted: 0, retOnly: 0 },
{ deployed: 0, retracted: 0, retOnly: 0 }
{ deployed: 0, retracted: 0, },
{ deployed: 0, retracted: 0, },
{ deployed: 0, retracted: 0, },
{ deployed: 0, retracted: 0, },
{ deployed: 0, retracted: 0, }
];
}
@@ -73,58 +76,49 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
// GETTERS //
//*********//
Ship.prototype.getAvailableComponents = function() {
getAvailableModules() {
return this.availCS;
};
}
Ship.prototype.getSlotStatus = function(slot, deployed) {
if (!slot.c) { // Empty Slot
getSlotStatus(slot, deployed) {
if (!slot.m) { // Empty Slot
return 0; // No Status (Not possible to be active in this state)
} else if (!slot.enabled) {
return 1; // Disabled
} else if (deployed) {
return this.priorityBands[slot.priority].deployedSum >= this.powerAvailable ? 2 : 3; // Offline : Online
// Active hardpoints have no retracted status
} else if ((slot.cat === 1 && !slot.c.passive)) {
} else if ((slot.cat === 1 && !slot.m.passive)) {
return 0; // No Status (Not possible to be active in this state)
}
return this.priorityBands[slot.priority].retractedSum >= this.powerAvailable ? 2 : 3; // Offline : Online
};
}
/**
/**
* Calculate jump range using the installed FSD and the
* specified mass which can be more or less than ships actual mass
* @param {number} mass Mass in tons
* @param {number} fuel Fuel available in tons
* @return {number} Jump range in Light Years
*/
Ship.prototype.getJumpRangeForMass = function(mass, fuel) {
return calcJumpRange(mass, this.standard[2].c, fuel);
};
getJumpRangeForMass(mass, fuel) {
return Calc.jumpRange(mass, this.standard[2].m, fuel);
}
/**
* Find an internal slot that has an installed component of the specific group.
* Find an internal slot that has an installed modul of the specific group.
*
* @param {string} group Component group/type
* @param {string} group Module group/type
* @return {number} The index of the slot in ship.internal
*/
Ship.prototype.findInternalByGroup = function(group) {
findInternalByGroup(group) {
var index;
if (group == 'sg' || group == 'psg') {
index = _.findIndex(this.internal, function(slot) {
return slot.c && (slot.c.grp == 'sg' || slot.c.grp == 'psg');
});
return this.internal.find(slot => slot.m && (slot.m.grp == 'sg' || slot.m.grp == 'psg');
} else {
index = _.findIndex(this.internal, function(slot) {
return slot.c && slot.c.grp == group;
});
return this.internal.find(slot => slot.m && slot.m.grp == group);
}
if (index !== -1) {
return this.internal[index];
}
return null;
};
//**********************//
// Mutate / Update Ship //
@@ -133,32 +127,32 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
/**
* Recalculate all item costs and total based on discounts.
* @param {number} shipCostMultiplier Ship cost multiplier discount (e.g. 0.9 === 10% discount)
* @param {number} componentCostMultiplier Component cost multiplier discount (e.g. 0.75 === 25% discount)
* @param {number} modulCostMultiplier Module cost multiplier discount (e.g. 0.75 === 25% discount)
*/
Ship.prototype.applyDiscounts = function(shipCostMultiplier, componentCostMultiplier) {
applyDiscounts(shipCostMultiplier, modulCostMultiplier) {
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' ? shipCostMultiplier : componentCostMultiplier);
if (item.m && item.m.cost) {
item.discountedCost = item.m.cost * (item.type == 'SHIP' ? shipCostMultiplier : modulCostMultiplier);
if (item.incCost) {
total += item.discountedCost;
}
}
}
this.shipCostMultiplier = shipCostMultiplier;
this.componentCostMultiplier = componentCostMultiplier;
this.modulCostMultiplier = modulCostMultiplier;
this.totalCost = total;
return this;
};
}
/**
* Builds/Updates the ship instance with the components[comps] passed in.
* @param {object} comps Collection of components used to build the ship
* Builds/Updates the ship instance with the ModuleUtils[comps] passed in.
* @param {object} comps Collection of ModuleUtils used to build the ship
*/
Ship.prototype.buildWith = function(comps, priorities, enabled) {
buildWith(comps, priorities, enabled) {
var internal = this.internal,
standard = this.standard,
hps = this.hardpoints,
@@ -173,11 +167,11 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.armourAdded = 0;
this.armourMultiplier = 1;
this.shieldMultiplier = 1;
this.totalCost = this.c.incCost ? this.c.discountedCost : 0;
this.totalCost = this.m.incCost ? this.m.discountedCost : 0;
this.unladenMass = this.hullMass;
this.totalDps = 0;
this.bulkheads.c = null;
this.bulkheads.m = null;
this.useBulkhead(comps && comps.bulkheads ? comps.bulkheads : 0, true);
this.cargoHatch.priority = priorities ? priorities[0] * 1 : 0;
this.cargoHatch.enabled = enabled ? enabled[0] * 1 : true;
@@ -185,11 +179,10 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
for (i = 0, l = this.priorityBands.length; i < l; i++) {
this.priorityBands[i].deployed = 0;
this.priorityBands[i].retracted = 0;
this.priorityBands[i].retOnly = 0;
}
if (this.cargoHatch.enabled) {
bands[this.cargoHatch.priority].retracted += this.cargoHatch.c.power;
bands[this.cargoHatch.priority].retracted += this.cargoHatch.m.power;
}
for (i = 0; i < cl; i++) {
@@ -197,11 +190,11 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
standard[i].enabled = enabled ? enabled[i + 1] * 1 : true;
standard[i].priority = priorities && priorities[i + 1] ? priorities[i + 1] * 1 : 0;
standard[i].type = 'SYS';
standard[i].c = standard[i].id = null; // Resetting 'old' component if there was one
standard[i].m = standard[i].id = null; // Resetting 'old' modul if there was one
standard[i].discountedCost = 0;
if (comps) {
this.use(standard[i], comps.standard[i], Components.standard(i, comps.standard[i]), true);
this.use(standard[i], comps.standard[i], ModuleUtils.standard(i, comps.standard[i]), true);
}
}
@@ -214,11 +207,11 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
hps[i].enabled = enabled ? enabled[cl + i] * 1 : true;
hps[i].priority = priorities && priorities[cl + i] ? 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].m = hps[i].id = null; // Resetting 'old' modul if there was one
hps[i].discountedCost = 0;
if (comps && comps.hardpoints[i] !== 0) {
this.use(hps[i], comps.hardpoints[i], Components.hardpoints(comps.hardpoints[i]), true);
this.use(hps[i], comps.hardpoints[i], ModuleUtils.hardpoints(comps.hardpoints[i]), true);
}
}
@@ -229,11 +222,11 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
internal[i].enabled = enabled ? enabled[cl + i] * 1 : true;
internal[i].priority = priorities && priorities[cl + i] ? 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].id = internal[i].m = null; // Resetting 'old' modul if there was one
internal[i].discountedCost = 0;
if (comps && comps.internal[i] !== 0) {
this.use(internal[i], comps.internal[i], Components.internal(comps.internal[i]), true);
this.use(internal[i], comps.internal[i], ModuleUtils.internal(comps.internal[i]), true);
}
}
@@ -246,85 +239,85 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
}
return this;
};
}
Ship.prototype.emptyHardpoints = function() {
emptyHardpoints() {
for (var i = this.hardpoints.length; i--; ) {
this.use(this.hardpoints[i], null, null);
}
return this;
};
}
Ship.prototype.emptyInternal = function() {
emptyInternal() {
for (var i = this.internal.length; i--; ) {
this.use(this.internal[i], null, null);
}
return this;
};
}
Ship.prototype.emptyUtility = function() {
emptyUtility() {
for (var i = this.hardpoints.length; i--; ) {
if (!this.hardpoints[i].maxClass) {
this.use(this.hardpoints[i], null, null);
}
}
return this;
};
}
Ship.prototype.emptyWeapons = function() {
emptyWeapons() {
for (var i = this.hardpoints.length; i--; ) {
if (this.hardpoints[i].maxClass) {
this.use(this.hardpoints[i], null, null);
}
}
return this;
};
}
/**
* Optimize for the lower mass build that can still boost and power the ship
* without power management.
* @param {object} c Standard Component overrides
* @param {object} m Standard Module overrides
*/
Ship.prototype.optimizeMass = function(c) {
return this.emptyHardpoints().emptyInternal().useLightestStandard(c);
};
optimizeMass(m) {
return this.emptyHardpoints().emptyInternal().useLightestStandard(m);
}
Ship.prototype.setCostIncluded = function(item, included) {
if (item.incCost != included && item.c) {
setCostIncluded(item, included) {
if (item.incCost != included && item.m) {
this.totalCost += included ? item.discountedCost : -item.discountedCost;
}
item.incCost = included;
return this;
};
}
Ship.prototype.setSlotEnabled = function(slot, enabled) {
setSlotEnabled(slot, enabled) {
if (slot.enabled != enabled) { // Enabled state is changing
slot.enabled = enabled;
if (slot.c) {
this.priorityBands[slot.priority][powerUsageType(slot, slot.c)] += enabled ? slot.c.power : -slot.c.power;
if (slot.m) {
this.priorityBands[slot.priority][powerUsageType(slot, slot.m)] += enabled ? slot.m.power : -slot.m.power;
if (slot.c.grp == 'sg' || slot.c.grp == 'psg') {
if (slot.m.grp == 'sg' || slot.m.grp == 'psg') {
this.updateShieldStrength();
} else if (slot.c.grp == 'sb') {
this.shieldMultiplier += slot.c.shieldmul * (enabled ? 1 : -1);
} else if (slot.m.grp == 'sb') {
this.shieldMultiplier += slot.m.shieldmul * (enabled ? 1 : -1);
this.updateShieldStrength();
} else if (slot.c.dps) {
this.totalDps += slot.c.dps * (enabled ? 1 : -1);
} else if (slot.m.dps) {
this.totalDps += slot.m.dps * (enabled ? 1 : -1);
}
this.updatePower();
}
}
return this;
};
}
/**
* Updates the ship's cumulative and aggregated stats based on the component change.
* Updates the ship's cumulative and aggregated stats based on the modul change.
*/
Ship.prototype.updateStats = function(slot, n, old, preventUpdate) {
updateStats(slot, n, old, preventUpdate) {
var powerChange = slot == this.standard[0];
if (old) { // Old component now being removed
if (old) { // Old modul now being removed
switch (old.grp) {
case 'ft':
this.fuelCapacity -= old.capacity;
@@ -341,7 +334,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
}
if (slot.incCost && old.cost) {
this.totalCost -= old.cost * this.componentCostMultiplier;
this.totalCost -= old.cost * this.modulCostMultiplier;
}
if (old.power && slot.enabled) {
@@ -372,7 +365,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
}
if (slot.incCost && n.cost) {
this.totalCost += n.cost * this.componentCostMultiplier;
this.totalCost += n.cost * this.modulCostMultiplier;
}
if (n.power && slot.enabled) {
@@ -398,82 +391,82 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.updateShieldStrength();
}
return this;
};
}
Ship.prototype.updatePower = function() {
updatePower() {
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 + band.retOnly;
prevRetracted = band.retractedSum = prevRetracted + band.retracted;
prevDeployed = band.deployedSum = prevDeployed + band.deployed + band.retracted;
}
this.powerAvailable = this.standard[0].c.pGen;
this.powerAvailable = this.standard[0].m.pGen;
this.powerRetracted = prevRetracted;
this.powerDeployed = prevDeployed;
return this;
};
Ship.prototype.updateTopSpeed = function() {
var speeds = calcSpeed(this.unladenMass + this.fuelCapacity, this.speed, this.boost, this.standard[1].c, this.pipSpeed);
updateTopSpeed() {
var speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.boost, this.standard[1].m, this.pipSpeed);
this.topSpeed = speeds['4 Pips'];
this.topBoost = speeds.boost;
return this;
};
}
Ship.prototype.updateShieldStrength = function() {
updateShieldStrength() {
var sgSlot = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any
this.shieldStrength = sgSlot && sgSlot.enabled ? calcShieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.c, this.shieldMultiplier) : 0;
this.shieldStrength = sgSlot && sgSlot.enabled ? Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, this.shieldMultiplier) : 0;
return this;
};
}
/**
* Jump Range and total range calculations
*/
Ship.prototype.updateJumpStats = function() {
var fsd = this.standard[2].c; // Frame Shift Drive;
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.unladenTotalRange = calcTotalRange(this.unladenMass, fsd, this.fuelCapacity);
this.ladenTotalRange = calcTotalRange(this.unladenMass + this.cargoCapacity, fsd, this.fuelCapacity);
updateJumpStats() {
var fsd = this.standard[2].m; // Frame Shift Drive;
this.unladenRange = Calc.jumpRange(this.unladenMass + fsd.maxfuel, fsd, this.fuelCapacity); // Include fuel weight for jump
this.fullTankRange = Calc.jumpRange(this.unladenMass + this.fuelCapacity, fsd, this.fuelCapacity); // Full Tanke
this.ladenRange = Calc.jumpRange(this.ladenMass, fsd, this.fuelCapacity);
this.unladenTotalRange = Calc.totalRange(this.unladenMass, fsd, this.fuelCapacity);
this.ladenTotalRange = Calc.totalRange(this.unladenMass + this.cargoCapacity, fsd, this.fuelCapacity);
this.maxJumpCount = Math.ceil(this.fuelCapacity / fsd.maxfuel);
return this;
};
}
/**
* 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).
* Update a slot with a the modul if the id is different from the current id for this slot.
* Has logic handling ModuleUtils 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 modul slot
* @param {string} id Unique ID for the selected module
* @param {object} modul Properties for the selected module
* @param {boolean} preventUpdate If true, do not update aggregated stats
*/
Ship.prototype.use = function(slot, id, component, preventUpdate) {
if (slot.id != id) { // Selecting a different component
// Slot is an internal slot, is not being emptied, and the selected component group/type must be of unique
if (slot.cat == 2 && component && _.includes(['psg', 'sg', 'rf', 'fs'], component.grp)) {
use(slot, id, modul, preventUpdate) {
if (slot.id != id) { // Selecting a different modul
// Slot is an internal slot, is not being emptied, and the selected modul group/type must be of unique
if (slot.cat == 2 && modul && UNIQUE_MODULES.includes(modul.grp)) {
// Find another internal slot that already has this type/group installed
var similarSlot = this.findInternalByGroup(component.grp);
// If another slot has an installed component with of the same type
var similarSlot = this.findInternalByGroup(modul.grp);
// If another slot has an installed modul with of the same type
if (!preventUpdate && similarSlot && similarSlot !== slot) {
this.updateStats(similarSlot, null, similarSlot.c);
similarSlot.id = similarSlot.c = null; // Empty the slot
this.updateStats(similarSlot, null, similarSlot.m);
similarSlot.id = similarSlot.m = null; // Empty the slot
similarSlot.discountedCost = 0;
}
}
var oldComponent = slot.c;
var oldModule = slot.m;
slot.id = id;
slot.c = component;
slot.discountedCost = (component && component.cost) ? component.cost * this.componentCostMultiplier : 0;
this.updateStats(slot, component, oldComponent, preventUpdate);
slot.m = modul;
slot.discountedCost = (modul && modul.cost) ? modul.cost * this.modulCostMultiplier : 0;
this.updateStats(slot, modul, oldModule, preventUpdate);
}
return this;
};
}
/**
* [useBulkhead description]
@@ -481,98 +474,98 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
* @param {[type]} preventUpdate [description]
* @return {[type]} [description]
*/
Ship.prototype.useBulkhead = function(index, preventUpdate) {
var oldBulkhead = this.bulkheads.c;
useBulkhead(index, preventUpdate) {
var oldBulkhead = this.bulkheads.m;
this.bulkheads.id = index;
this.bulkheads.c = Components.bulkheads(this.id, index);
this.bulkheads.discountedCost = this.bulkheads.c.cost * this.componentCostMultiplier;
this.bulkheads.m = ModuleUtils.bulkheads(this.id, index);
this.bulkheads.discountedCost = this.bulkheads.m.cost * this.modulCostMultiplier;
this.armourMultiplier = ArmourMultiplier[index];
this.updateStats(this.bulkheads, this.bulkheads.c, oldBulkhead, preventUpdate);
this.updateStats(this.bulkheads, this.bulkheads.m, oldBulkhead, preventUpdate);
return this;
};
}
/**
* [useStandard description]
* @param {[type]} rating [description]
* @return {[type]} [description]
*/
Ship.prototype.useStandard = function(rating) {
useStandard(rating) {
for (var i = this.standard.length - 1; i--; ) { // All except Fuel Tank
var id = this.standard[i].maxClass + rating;
this.use(this.standard[i], id, Components.standard(i, id));
this.use(this.standard[i], id, ModuleUtils.standard(i, id));
}
return this;
};
}
/**
* Use the lightest standard components unless otherwise specified
* @param {object} c Component overrides
* Use the lightest standard ModuleUtils unless otherwise specified
* @param {object} m Module overrides
*/
Ship.prototype.useLightestStandard = function(c) {
c = c || {};
useLightestStandard(m) {
m = m || {};
var standard = this.standard,
pd = c.pd || this.availCS.lightestPowerDist(this.boostEnergy), // Find lightest Power Distributor that can still boost;
fsd = c.fsd || standard[2].maxClass + 'A',
ls = c.ls || standard[3].maxClass + 'D',
s = c.s || standard[5].maxClass + 'D',
pd = m.pd || this.availCS.lightestPowerDist(this.boostEnergy), // Find lightest Power Distributor that can still boost;
fsd = m.fsd || standard[2].maxClass + 'A',
ls = m.ls || standard[3].maxClass + 'D',
s = m.s || standard[5].maxClass + 'D',
updated;
this.useBulkhead(0)
.use(standard[2], fsd, Components.standard(2, fsd)) // FSD
.use(standard[3], ls, Components.standard(3, ls)) // Life Support
.use(standard[5], s, Components.standard(5, s)) // Sensors
.use(standard[4], pd, Components.standard(4, pd)); // Power Distributor
.use(standard[2], fsd, ModuleUtils.standard(2, fsd)) // FSD
.use(standard[3], ls, ModuleUtils.standard(3, ls)) // Life Support
.use(standard[5], s, ModuleUtils.standard(5, s)) // Sensors
.use(standard[4], pd, ModuleUtils.standard(4, pd)); // Power Distributor
// Thrusters and Powerplant must be determined after all other components are mounted
// Thrusters and Powerplant must be determined after all other ModuleUtils are mounted
// Loop at least once to determine absolute lightest PD and TH
do {
updated = false;
// Find lightest Thruster that still works for the ship at max mass
var th = c.th || this.availCS.lightestThruster(this.ladenMass);
var th = m.th || this.availCS.lightestThruster(this.ladenMass);
if (th != standard[1].id) {
this.use(standard[1], th, Components.standard(1, th));
this.use(standard[1], th, ModuleUtils.standard(1, th));
updated = true;
}
// Find lightest Power plant that can power the ship
var pp = c.pp || this.availCS.lightestPowerPlant(Math.max(this.powerRetracted, this.powerDeployed), c.ppRating);
var pp = m.pp || this.availCS.lightestPowerPlant(Math.max(this.powerRetracted, this.powerDeployed), m.ppRating);
if (pp != standard[0].id) {
this.use(standard[0], pp, Components.standard(0, pp));
this.use(standard[0], pp, ModuleUtils.standard(0, pp));
updated = true;
}
} while (updated);
return this;
};
}
Ship.prototype.useUtility = function(group, rating, clobber) {
var component = Components.findHardpoint(group, 0, rating);
useUtility(group, rating, clobber) {
var modul = ModuleUtils.findHardpoint(group, 0, rating);
for (var i = this.hardpoints.length; i--; ) {
if ((clobber || !this.hardpoints[i].c) && !this.hardpoints[i].maxClass) {
this.use(this.hardpoints[i], component.id, component);
if ((clobber || !this.hardpoints[i].m) && !this.hardpoints[i].maxClass) {
this.use(this.hardpoints[i], modul.id, modul);
}
}
return this;
};
}
Ship.prototype.useWeapon = function(group, mount, clobber, missile) {
useWeapon(group, mount, clobber, missile) {
var hps = this.hardpoints;
for (var i = hps.length; i--; ) {
if (hps[i].maxClass) {
var size = hps[i].maxClass, component;
var size = hps[i].maxClass, modul;
do {
component = Components.findHardpoint(group, size, null, null, mount, missile);
if ((clobber || !hps[i].c) && component) {
this.use(hps[i], component.id, component);
modul = ModuleUtils.findHardpoint(group, size, null, null, mount, missile);
if ((clobber || !hps[i].m) && modul) {
this.use(hps[i], modul.id, modul);
break;
}
} while (!component && (--size > 0));
} while (!modul && (--size > 0));
}
}
return this;
};
}
/**
* Will change the priority of the specified slot if the new priority is valid
@@ -580,21 +573,19 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
* @param {number} newPriority The new priority to be set
* @return {boolean} Returns true if the priority was changed (within range)
*/
Ship.prototype.changePriority = function(slot, newPriority) {
changePriority(slot, newPriority) {
if (newPriority >= 0 && newPriority < this.priorityBands.length) {
var oldPriority = slot.priority;
slot.priority = newPriority;
if (slot.enabled) { // Only update power if the slot is enabled
var usage = powerUsageType(slot, slot.c);
this.priorityBands[oldPriority][usage] -= slot.c.power;
this.priorityBands[newPriority][usage] += slot.c.power;
var usage = powerUsageType(slot, slot.m);
this.priorityBands[oldPriority][usage] -= slot.m.power;
this.priorityBands[newPriority][usage] += slot.m.power;
this.updatePower();
}
return true;
}
return false;
};
return Ship;
}]);
}
}

View File

@@ -1,145 +0,0 @@
angular.module('shipyard').factory('ComponentSet', ['lodash', function(_) {
function filter(data, maxClass, minClass, mass) {
return _.filter(data, function(c) {
return c.class <= maxClass && c.class >= minClass && (c.maxmass === undefined || mass <= c.maxmass);
});
}
function getKey(maxClass, eligible) {
if (eligible) {
return maxClass + Object.keys(eligible).join('-');
}
return maxClass;
}
function ComponentSet(components, mass, maxStandardArr, maxInternal, maxHardPoint) {
this.mass = mass;
this.standard = {};
this.internal = {};
this.hardpoints = {};
this.hpClass = {};
this.intClass = {};
this.standard[0] = filter(components.standard[0], maxStandardArr[0], 0, mass); // Power Plant
this.standard[2] = filter(components.standard[2], maxStandardArr[2], 0, mass); // FSD
this.standard[4] = filter(components.standard[4], maxStandardArr[4], 0, mass); // Power Distributor
this.standard[6] = filter(components.standard[6], maxStandardArr[6], 0, mass); // Fuel Tank
// Thrusters, filter components by class only (to show full list of ratings for that class)
var minThrusterClass = _.reduce(components.standard[1], function(minClass, thruster) {
return (thruster.maxmass >= mass && thruster.class < minClass) ? thruster.class : minClass;
}, maxStandardArr[1]);
this.standard[1] = filter(components.standard[1], maxStandardArr[1], minThrusterClass, 0); // Thrusters
// Slots where component class must be equal to slot class
this.standard[3] = filter(components.standard[3], maxStandardArr[3], maxStandardArr[3], 0); // Life Supprt
this.standard[5] = filter(components.standard[5], maxStandardArr[5], maxStandardArr[5], mass); // Sensors
for (var h in components.hardpoints) {
this.hardpoints[h] = filter(components.hardpoints[h], maxHardPoint, 0, mass);
}
for (var g in components.internal) {
this.internal[g] = filter(components.internal[g], maxInternal, 0, mass);
}
/**
* Create a memoized function for determining the components that are
* eligible for an internal slot
* @param {integer} c The max class component that can be mounted in the slot
* @param {Object} eligible) The map of eligible internal groups
* @return {object} A map of all eligible components by group
*/
this.getInts = _.memoize(
function(c, eligible) {
var o = {};
for (var key in this.internal) {
if (eligible && !eligible[key]) {
continue;
}
var data = filter(this.internal[key], c, 0, this.mass);
if (data.length) { // If group is not empty
o[key] = data;
}
}
return o;
},
getKey
);
/**
* Create a memoized function for determining the components that are
* eligible for an hardpoint slot
* @param {integer} c The max class component that can be mounted in the slot
* @param {Object} eligible) The map of eligible hardpoint groups
* @return {object} A map of all eligible components by group
*/
this.getHps = _.memoize(
function(c, eligible) {
var o = {};
for (var key in this.hardpoints) {
if (eligible && !eligible[key]) {
continue;
}
var data = filter(this.hardpoints[key], c, c ? 1 : 0, this.mass);
if (data.length) { // If group is not empty
o[key] = data;
}
}
return o;
},
getKey
);
}
ComponentSet.prototype.lightestPowerDist = function(boostEnergy) {
var pds = this.standard[4];
var pd = pds[0];
for (var i = 1; i < pds.length; i++) {
if (pds[i].mass < pd.mass && pds[i].enginecapacity >= boostEnergy) {
pd = pds[i];
}
}
return pd.class + pd.rating;
};
ComponentSet.prototype.lightestThruster = function(ladenMass) {
var ths = this.standard[1];
var th = ths[0];
for (var i = 1; i < ths.length; i++) {
if (ths[i].mass < th.mass && ths[i].maxmass >= ladenMass) {
th = ths[i];
}
}
return th.class + th.rating;
};
ComponentSet.prototype.lightestShieldGenerator = function(hullMass) {
var sg = null;
_.forEach(this.internal.sg, function(s) {
if (sg == null || (s.mass < sg.mass && s.minmass <= hullMass && s.maxmass > hullMass)) {
sg = s;
}
});
return sg.id;
};
ComponentSet.prototype.lightestPowerPlant = function(powerUsed, rating) {
var pps = this.standard[0];
var pp = null;
for (var i = 0; i < pps.length; i++) {
if (pp == null || (pps[i].mass < pp.mass && pps[i].pGen >= powerUsed)) {
pp = pps[i];
}
}
return pp.class + (pp.rating != 'D' || rating == 'A' ? 'A' : 'D'); // Use A rated if C,E
};
return ComponentSet;
}]);

View File

@@ -1,252 +0,0 @@
/**
* This module contains all of the logic and models corresponding to
* information or behavoir in Elite Dangerous.
*
* This file contains values and functions that can be reused across the app.
*
* @requires ngLodash
*/
angular.module('shipyard', ['ngLodash'])
// Create 'angularized' references to DB. This will aid testing
.constant('ShipsDB', DB.ships)
.constant('ComponentsDB', DB.components)
.constant('ArmourMultiplier', [
1, // Lightweight
1.4, // Reinforced
1.945, // Military
1.945, // Mirrored
1.945 // Reactive
])
.constant('SizeMap', ['', 'small', 'medium', 'large', 'capital'])
// Map to lookup group labels/names for component grp, used for JSON Serialization
.constant('GroupMap', {
// Standard
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: 'Scanner',
am: 'Auto Field-Maintenance Unit',
cr: 'Cargo Rack',
fi: 'Frame Shift Drive Interdictor',
hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package',
rf: 'Refinery',
scb: 'Shield Cell Bank',
sg: 'Shield Generator',
psg: 'Prismatic Shield Generator',
dc: 'Docking Computer',
fx: 'Fuel Transfer Limpet Controller',
pc: 'Prospector Limpet Controller',
cc: 'Collector Limpet Controller',
// 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'
})
.constant('MountMap', {
'F': 'Fixed',
'G': 'Gimballed',
'T': 'Turret',
'Fixed': 'F',
'Gimballed': 'G',
'Turret': 'T'
})
/**
* Array of all Ship properties (facets) organized into groups
* used for ship comparisons.
*
* @type {Array}
*/
.constant('ShipFacets', [
{ // 0
title: 'agility',
props: ['agility'],
unit: '',
fmt: 'fCrd'
},
{ // 1
title: 'speed',
props: ['topSpeed', 'topBoost'],
lbls: ['thrusters', 'boost'],
unit: 'm/s',
fmt: 'fCrd'
},
{ // 2
title: 'armour',
props: ['armour'],
unit: '',
fmt: 'fCrd'
},
{ // 3
title: 'shields',
props: ['shieldStrength'],
unit: 'MJ',
fmt: 'fRound'
},
{ // 4
title: 'jump range',
props: ['unladenRange', 'fullTankRange', 'ladenRange'],
lbls: ['max', 'full tank', 'laden'],
unit: 'LY',
fmt: 'fRound'
},
{ // 5
title: 'mass',
props: ['unladenMass', 'ladenMass'],
lbls: ['unladen', 'laden'],
unit: 'T',
fmt: 'fRound'
},
{ // 6
title: 'cargo',
props: ['cargoCapacity'],
unit: 'T',
fmt: 'fRound'
},
{ // 7
title: 'fuel',
props: ['fuelCapacity'],
unit: 'T',
fmt: 'fRound'
},
{ // 8
title: 'power',
props: ['powerRetracted', 'powerDeployed', 'powerAvailable'],
lbls: ['retracted', 'deployed', 'available'],
unit: 'MW',
fmt: 'fPwr'
},
{ // 9
title: 'cost',
props: ['totalCost'],
unit: 'CR',
fmt: 'fCrd'
},
{ // 10
title: 'total range',
props: ['unladenTotalRange', 'ladenTotalRange'],
lbls: ['unladen', 'laden'],
unit: 'LY',
fmt: 'fRound'
},
{ // 11
title: 'DPS',
props: ['totalDps'],
lbls: ['DPS'],
unit: '',
fmt: 'fRound'
}
])
/**
* Set of all available / theoretical discounts
*/
.constant('Discounts', {
'0%': 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
*
* @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
*/
.value('calcJumpRange', function(mass, fsd, fuel) {
return Math.pow(Math.min(fuel === undefined ? fsd.maxfuel : fuel, fsd.maxfuel) / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass;
})
/**
* Calculate the total range based on mass and a specific FSD, and all fuel available
*
* @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 The total fuel available
* @return {number} Distance in Light Years
*/
.value('calcTotalRange', function(mass, fsd, fuel) {
var fuelRemaining = fuel % fsd.maxfuel; // Fuel left after making N max jumps
var jumps = Math.floor(fuel / fsd.maxfuel);
mass += fuelRemaining;
// Going backwards, start with the last jump using the remaining fuel
var totalRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass : 0;
// For each max fuel jump, calculate the max jump range based on fuel mass left in the tank
for (var j = 0; j < jumps; j++) {
mass += fsd.maxfuel;
totalRange += Math.pow(fsd.maxfuel / fsd.fuelmul, 1 / fsd.fuelpower ) * fsd.optmass / mass;
}
return totalRange;
})
/**
* Calculate the a ships shield strength based on mass, shield generator and shield boosters used.
*
* @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
*/
.value('calcShieldStrength', function(mass, shields, sg, multiplier) {
var opt;
if (mass < sg.minmass) {
return shields * multiplier * sg.minmul;
}
if (mass > sg.maxmass) {
return shields * multiplier * sg.maxmul;
}
if (mass < sg.optmass) {
opt = (sg.optmass - mass) / (sg.optmass - sg.minmass);
opt = 1 - Math.pow(1 - opt, 0.87);
return shields * multiplier * ((opt * sg.minmul) + ((1 - opt) * sg.optmul));
} else {
opt = (sg.optmass - mass) / (sg.maxmass - sg.optmass);
opt = -1 + Math.pow(1 + opt, 2.425);
return shields * multiplier * ( (-1 * opt * sg.maxmul) + ((1 + opt) * sg.optmul) );
}
})
/**
* Calculate the a ships speed based on mass, and thrusters.
*
* @param {number} mass Current mass of the ship
* @param {number} baseSpeed Base speed m/s for ship
* @param {number} baseBoost Base boost speed m/s for ship
* @param {object} thrusters The Thrusters used
* @param {number} pipSpeed Speed pip multiplier
* @return {object} Approximate speed by pips
*/
.value('calcSpeed', function(mass, baseSpeed, baseBoost, thrusters, pipSpeed) {
var multiplier = mass > thrusters.maxmass ? 0 : ((1 - thrusters.M) + (thrusters.M * Math.pow(3 - (2 * Math.max(0.5, mass / thrusters.optmass)), thrusters.P)));
var speed = baseSpeed * multiplier;
return {
'0 Pips': speed * (1 - (pipSpeed * 4)),
'2 Pips': speed * (1 - (pipSpeed * 2)),
'4 Pips': speed,
'boost': baseBoost * multiplier
};
});

View File

@@ -1,181 +0,0 @@
angular.module('shipyard').service('Components', ['lodash', 'ComponentsDB', 'ShipsDB', 'ComponentSet', 'GroupMap', function(_, C, Ships, ComponentSet, GroupMap) {
var GrpNameToCodeMap = {};
for (var grp in GroupMap) {
GrpNameToCodeMap[GroupMap[grp]] = grp;
}
this.cargoHatch = function() {
return { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 };
};
this.standard = function(typeIndex, componentId) {
return C.standard[typeIndex][componentId];
};
this.hardpoints = function(id) {
for (var n in C.hardpoints) {
var group = C.hardpoints[n];
for (var i = 0; i < group.length; i++) {
if (group[i].id == id) {
return group[i];
}
}
}
return null;
};
this.internal = function(id) {
for (var n in C.internal) {
var group = C.internal[n];
for (var i = 0; i < group.length; i++) {
if (group[i].id == id) {
return group[i];
}
}
}
return null;
};
/**
* Finds an internal Component based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique component name must be provided
*
* @param {string} groupName [Optional] Full name or abbreviated name for component group
* @param {integer} clss Component Class
* @param {string} rating Component Rating
* @param {string} name [Optional] Long/unique name for component -e.g. 'Advanced Discover Scanner'
* @return {String} The id of the component if found, null if not found
*/
this.findInternal = function(groupName, clss, rating, name) {
var groups = {};
if (groupName) {
if (C.internal[groupName]) {
groups[groupName] = C.internal[groupName];
} else {
var grpCode = GrpNameToCodeMap[groupName];
if (grpCode && C.internal[grpCode]) {
groups[grpCode] = C.internal[grpCode];
}
}
} else if (name) {
groups = C.internal;
}
for (var g in groups) {
var group = groups[g];
for (var i = 0, l = group.length; i < l; i++) {
if (group[i].class == clss && group[i].rating == rating && ((!name && !group[i].name) || group[i].name == name)) {
return group[i];
}
}
}
return null;
};
/**
* Finds an internal Component ID based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique component name must be provided
*
* @param {string} groupName [Optional] Full name or abbreviated name for component group
* @param {integer} clss Component Class
* @param {string} rating Component Rating
* @param {string} name [Optional] Long/unique name for component -e.g. 'Advanced Discover Scanner'
* @return {String} The id of the component if found, null if not found
*/
this.findInternalId = function(groupName, clss, rating, name) {
var i = this.findInternal(groupName, clss, rating, name);
return i ? i.id : 0;
};
/**
* Finds a hardpoint Component based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique component name must be provided
*
* @param {string} groupName [Optional] Full name or abbreviated name for component group
* @param {integer} clss Component Class
* @param {string} rating [Optional] Component Rating
* @param {string} name [Optional] Long/unique name for component -e.g. 'Heat Sink Launcher'
* @param {string} mode Mount mode/type - [F]ixed, [G]imballed, [T]urret
* @param {string} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the component if found, null if not found
*/
this.findHardpoint = function(groupName, clss, rating, name, mode, missile) {
var groups = {};
if (groupName) {
if (C.hardpoints[groupName]) {
groups[groupName] = C.hardpoints[groupName];
} else {
var grpCode = GrpNameToCodeMap[groupName];
if (grpCode && C.hardpoints[grpCode]) {
groups[grpCode] = C.hardpoints[grpCode];
}
}
} else if (name) {
groups = C.hardpoints;
}
for (var g in groups) {
var group = groups[g];
for (var i = 0, l = group.length; i < l; i++) {
if (group[i].class == clss && (!rating || group[i].rating == rating) && group[i].mode == mode
&& ((!name && !group[i].name) || group[i].name == name)
&& ((!missile && !group[i].missile) || group[i].missile == missile)
) {
return group[i];
}
}
}
return null;
};
/**
* Finds a hardpoint Component ID based on Class, Rating, Group and/or name.
* At least one of Group name or unique component name must be provided
*
* @param {string} groupName [Optional] Full name or abbreviated name for component group
* @param {integer} clss Component Class
* @param {string} rating Component Rating
* @param {string} name [Optional] Long/unique name for component -e.g. 'Heat Sink Launcher'
* @param {string} mode Mount mode/type - [F]ixed, [G]imballed, [T]urret
* @param {string} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the component if found, null if not found
*/
this.findHardpointId = function(groupName, clss, rating, name, mode, missile) {
var h = this.findHardpoint(groupName, clss, rating, name, mode, missile);
return h ? h.id : 0;
};
/**
* 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];
};
this.bulkheadIndex = function(bulkheadName) {
return ['Lightweight Alloy', 'Reinforced Alloy', 'Military Grade Composite', 'Mirrored Surface Composite', 'Reactive Surface Composite'].indexOf(bulkheadName);
};
/**
* 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];
var maxInternal = isNaN(ship.slots.internal[0]) ? ship.slots.internal[0].class : ship.slots.internal[0];
return new ComponentSet(C, ship.minMassFilter || ship.properties.hullMass + 5, ship.slots.standard, maxInternal, ship.slots.hardpoints[0]);
};
}]);

308
app/js/stores/Persist.js Normal file
View File

@@ -0,0 +1,308 @@
import { EventEmitter } from 'fbemitter';
const LS_KEY_BUILDS = 'builds';
const LS_KEY_COMPARISONS = 'comparisons';
const LS_KEY_LANG = 'NG_TRANSLATE_LANG_KEY';
const LS_KEY_COST_TAB = 'costTab';
const LS_KEY_INSURANCE = 'insurance';
const LS_KEY_DISCOUNTS = 'discounts';
const LS_KEY_DISCOUNTS = 'state';
const LS_KEY_SIZE_RATIO = 'sizeRatio';
let LS;
// Safe check to determine if localStorage is enabled
try {
localStorage.setItem('s', 1);
localStorage.removeItem('s');
LS = localStorage;
} catch(e) {
LS = null;
}
function _put(key, value) {
if (LS) {
LS.setItem(key, typeof value != "string" ? JSON.stringify(value) : value);
}
}
function _getString(key) {
return LS.getItem(key) : null;
}
function _get(key) {
let str = _getString(key);
return str ? JSON.parse(str) : null;
}
function _delete(key) {
if (LS) {
LS.removeItem(key);
}
}
/**
* [description]
*/
class Persist extends EventEmitter {
constructor() {
let buildJson = _get(LS_KEY_BUILDS);
let comparisonJson = _get(LS_KEY_COMPARISONS);
this.builds = buildJson ? JSON.parse(buildJson) : {};
this.comparisons = comparisonJson ? JSON.parse(comparisonJson) : {};
this.buildCount = Object.keys(this.builds).length;
this.langCode = _getString(LS_KEY_LANG) || 'en';
this.insurance = _getString(LS_KEY_INSURANCE);
this.discount = _getString(LS_KEY_DISCOUNTS);
this.costTab = _getString(LS_KEY_COST_TAB);
this.state = _get(LS_KEY_STATE);
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
}
getLangCode() {
return this.langCode;
};
setLangCode(langCode) {
this.langCode = langCode;
_put(LS_KEY_LANG, langCode);
this.emit('language', langCode);
}
/**
* Persist a ship build in local storage.
*
* @param {string} shipId The unique id for a model of ship
* @param {string} name The name of the build
* @param {string} code The serialized code
*/
saveBuild(shipId, name, code) {
if (LS) {
if (!this.builds[shipId]) {
this.builds[shipId] = {};
}
let newBuild = !this.builds[shipId][name];
this.builds[shipId][name] = code;
_put(LS_KEY_BUILDS, this.builds);
if (newBuild) {
this.emit('builds', this.builds);
}
}
};
/**
* Get the serialized code/string for a build. Returns null if a
* build is not found.
*
* @param {string} shipId The unique id for a model of ship
* @param {string} name The name of the build
* @return {string} The serialized build string.
*/
getBuild(shipId, name) {
if (this.builds[shipId] && this.builds[shipId][name]) {
return this.builds[shipId][name];
}
return null;
};
getBuilds() {
return this.builds;
}
/**
* Delete a build from local storage. It will also delete the ship build collection if
* it becomes empty
*
* @param {string} shipId The unique id for a model of ship
* @param {string} name The name of the build
*/
deleteBuild(shipId, name) {
if (this.builds[shipId][name]) {
delete this.builds[shipId][name];
if (Object.keys(this.builds[shipId]).length === 0) {
delete this.builds[shipId];
}
_put(LS_KEY_BUILDS, this.builds);
// Check if the build was used in existing comparisons
var comps = this.comparisons;
for (var c in comps) {
for (var i = 0; i < comps[c].builds.length; i++) { // For all builds in the current comparison
if (comps[c].builds[i].shipId == shipId && comps[c].builds[i].buildName == name) {
comps[c].builds.splice(i, 1);
break; // A build is unique per comparison
}
}
}
_put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('builds', this.builds);
}
};
/**
* Persist a comparison in localstorage.
*
* @param {string} name The name of the comparison
* @param {array} builds Array of builds
* @param {array} facets Array of facet indices
*/
saveComparison(name, builds, facets) {
if (!this.comparisons[name]) {
this.comparisons[name] = {};
}
this.comparisons[name] = {
facets: facets,
builds: builds.map(b => { shipId: b.id || b.shipId, buildName: b.buildName })
};
_put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('comparisons', this.comparisons);
};
/**
* [getComparison description]
* @param {string} name [description]
* @return {object} Object containing array of facets and ship id + build names
*/
getComparison(name) {
if (this.comparisons[name]) {
return this.comparisons[name];
}
return null;
};
getComparisons() {
return this.comparisons;
}
/**
* Removes the comparison from localstorage.
* @param {string} name Comparison name
*/
deleteComparison(name) {
if (this.comparisons[name]) {
delete this.comparisons[name];
_put(LS_KEY_COMPARISONS, this.comparisons);
this.emit('comparisons', this.comparisons);
}
};
/**
* Delete all builds and comparisons from localStorage
*/
deleteAll() {
this.builds = {};
this.comparisons = {};
_delete(LS_KEY_BUILDS);
_delete(LS_KEY_COMPARISONS);
this.emit('deletedall');
};
getAll() {
var data = {};
data[LS_KEY_BUILDS] = this.getBuilds();
data[LS_KEY_COMPARISONS] = this.getComparisons();
data[LS_KEY_INSURANCE] = this.getInsurance();
data[LS_KEY_DISCOUNTS] = this.getDiscount();
return data;
};
/**
* Get the saved insurance type
* @return {string} The name of the saved insurance type of null
*/
getInsurance() {
return this.insurance;
};
/**
* Persist selected insurance type
* @param {string} name Insurance type name
*/
setInsurance(insurance) {
this.insurance = insurance
_put(LS_KEY_INSURANCE, insurance);
this.emit('insurance', insurance);
};
/**
* Persist selected discount
* @param {number} val Discount value/amount
*/
setDiscount(discount) {
this.discount = discount;
_put(LS_KEY_DISCOUNTS, discount);
this.emit('discount', discount);
};
/**
* Get the saved discount
* @return {number} val Discount value/amount
*/
getDiscount() {
return this.discount;
};
/**
* Persist selected cost tab
* @param {number} val Discount value/amount
*/
setCostTab(tabName) {
this.costTab = tabName;
_put(LS_KEY_COST_TAB, tabName);
};
/**
* Get the saved discount
* @return {number} val Discount value/amount
*/
getCostTab() {
return this.costTab;
};
/**
* Retrieve the last router state from local storage
* @return {object} state State object containing state name and params
*/
getState() {
return this.state;
};
/**
* Save the current router state to localstorage
* @param {object} state State object containing state name and params
*/
setState(state) {
this.state = state;
_put(LS_KEY_STATE, state);
};
/**
* Retrieve the last router state from local storage
* @return {number} size Ratio
*/
getSizeRatio() {
return this.sizeRatio;
};
/**
* Save the current size ratio to localstorage
* @param {number} sizeRatio
*/
setSizeRatio(sizeRatio) {
this.sizeRatio = sizeRatio;
_put(LS_KEY_SIZE_RATIO, sizeRatio);
this.emit('sizeRatio', sizeRatio);
};
/**
* Check if localStorage is enabled/active
* @return {Boolean} True if localStorage is enabled
*/
isEnabled() {
return LS != null;
}
}
export default new Persist();

43
app/js/utils/BBCode.js Normal file
View File

@@ -0,0 +1,43 @@
import { term, format } from '../i18n/Language';
export default function comparisonBBCode(facets, builds, link) {
var colCount = 2, b, i, j, k, f, fl, p, pl, l = [];
for (i = 0; i < facets.length; i++) {
if (facets[i].active) {
f = facets[i];
p = f.props;
if (p.length == 1) {
l.push('[th][B][COLOR=#FF8C0D]', term(f.title).toUpperCase(), '[/COLOR][/B][/th]');
colCount++;
} else {
for (j = 0; j < p.length; j++) {
l.push('[th][B][COLOR=#FF8C0D]', term(f.title).toUpperCase(), '\n', term(f.lbls[j]).toUpperCase(), '[/COLOR][/B][/th]');
colCount++;
}
}
}
}
l.push('[/tr]\n');
for (i = 0; i < builds.length; i++) {
b = builds[i];
//var href = $state.href('outfit',{shipId: b.id, code: b.code, bn: b.buildName}, {absolute: true});
l.push('[tr][td]', b.name, '[/td][td]', b.buildName, '[/td]');
for (j = 0, fl = facets.length; j < fl; j++) {
if (facets[j].active) {
f = facets[j];
p = f.props;
for (k = 0, pl = p.length; k < pl; k++) {
l.push('[td="align: right"]', format[f.fmt](b[p[k]]), ' [size=-2]', term(f.unit), '[/size][/td]');
}
}
}
l.push('[/tr]\n');
}
l.push('[tr][td="align: center, colspan:', colCount, '"][size=-3]\n[url=', link, ']Interactive Comparison at Coriolis.io[/url][/td][/tr]\n[/size][/table]');
l.unshift('[table="width:', colCount * 90, ',align: center"]\n[tr][th][B][COLOR=#FF8C0D]Ship[/COLOR][/B][/th][th][B][COLOR="#FF8C0D"]Build[/COLOR][/B][/th]');
return l.join('');
}

View File

@@ -0,0 +1,10 @@
export default function shortenUrl(url) {
/*if (window.navigator.onLine) {
return $http.post(shortenAPI + GAPI_KEY, { longUrl: url }).then(function(response) {
return response.data.id;
});
} else {
return $q.reject({ statusText: 'Not Online' });
}*/
}

View File

@@ -45,7 +45,7 @@ div, a, li {
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
#main {
#coriolis {
margin: 0;
padding: 0.5em 0.5em;
width: 100%;

View File

@@ -1,91 +0,0 @@
<div id="app-update" ng-show="appCacheUpdate">
<a href="#" onclick="window.location.reload()">{{ 'PHRASE_UPDATE_RDY' | translate }}</a>
</div>
<header>
<a class="l" ui-sref="shipyard" style="margin-right: 1em;" title="Ships"><svg class="icon xl"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#coriolis"></use></svg></a>
<div class="l menu">
<div class="menu-header" ng-class="{selected: openedMenu=='s'}" ng-click="openMenu($event,'s')">
<svg class="icon warning"><use xlink:href="#rocket"></use></svg><span class="menu-item-label"> {{'ships' | translate}}</span>
</div>
<div class="menu-list dbl no-wrap" ng-if="openedMenu=='s'">
<a class="block" ng-repeat="(shipId,ship) in ships" ui-sref-active="active" ui-sref="outfit({shipId:shipId, code:null, bn:null})">{{::ship.properties.name}}</a>
</div>
</div>
<div class="l menu">
<div class="menu-header" ng-class="{selected: openedMenu=='b', disabled: !bs.hasBuilds}" ng-click="openMenu($event,'b')">
<svg class="icon warning" ng-class="{'warning-disabled': !bs.hasBuilds}"><use xlink:href="#hammer"></use></svg><span class="menu-item-label"> {{'builds' | translate}}</span>
</div>
<div class="menu-list" ng-if="openedMenu=='b'" ng-click="$event.stopPropagation();">
<div class="dbl" >
<div><ul ng-repeat="shipId in buildsList">
{{ships[shipId].properties.name}}
<li ng-repeat="(i, name) in cleanedBuildList(shipId)">
<a ui-sref-active="active" class="name" ui-sref="outfit({shipId:shipId, code:allBuilds[shipId][name], bn:name})" ng-bind="name"></a>
</li>
</ul></div>
</div>
</div>
</div>
<div class="l menu">
<div class="menu-header" ng-class="{selected: openedMenu=='comp', disabled: !bs.hasBuilds}" ng-click="openMenu($event,'comp')">
<svg class="icon warning" ng-class="{'warning-disabled': !bs.hasBuilds}"><use xlink:href="#stats-bars"></use></svg><span class="menu-item-label"> {{'compare' | translate}}</span>
</div>
<div class="menu-list" ng-if="openedMenu=='comp'" ng-click="$event.stopPropagation();" style="white-space: nowrap;">
<span class="cap" ng-if="!bs.hasComparisons" translate="none created"></span>
<a ng-repeat="(i, name) in allComparisons" ui-sref-active="active" class="block name" ui-sref="compare({name:name})" ng-bind="name"></a>
<hr />
<a ui-sref="compare({name: 'all'})" class="block cap" translate="compare all"></a>
<a ui-sref="compare({name: null})" class="block cap" translate="create new"></a>
</div>
</div>
<div class="r menu">
<div class="menu-header" ng-class="{selected: openedMenu=='settings'}" ng-click="openMenu($event,'settings')">
<svg class="icon xl warning"><use xlink:href="#cogs"></use></svg><span class="menu-item-label"> {{'settings' | translate}}</span>
</div>
<div class="menu-list no-wrap cap" ng-if="openedMenu=='settings'" ng-click="$event.stopPropagation();">
<ul>
{{'language' | translate}}
<li><select class="cap" ng-model="language.current" ng-options="langCode as langName for (langCode,langName) in language.opts" ng-change="changeLanguage()"></select></li>
</ul><br>
<ul>
{{'insurance' | translate}}
<li><select class="cap" ng-model="insurance.current" ng-options="ins.name | translate for (i,ins) in insurance.opts" ng-change="updateInsurance()"></select></li>
</ul><br>
<ul>
{{'ship' | translate}} {{'discount' | translate}}
<li><select class="cap" ng-model="discounts.ship" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
</ul><br>
<ul>
{{'component' | translate}} {{'discount' | translate}}
<li><select class="cap" ng-model="discounts.components" ng-options="i for (i,d) in discounts.opts" ng-change="updateDiscount()"></select></li>
</ul>
<hr />
<ul>
{{'builds' | translate}} & {{'comparisons' | translate}}
<li><a href="#" class="block" ng-click="backup($event)" translate="backup"></a></li>
<li><a href="#" class="block" ng-click="detailedExport($event)" translate="detailed export"></a></li>
<li><a href="#" class="block" ui-sref="modal.import" translate="import"></a></li>
<li><a href="#" class="block" ui-sref="modal.delete" translate="delete all"></a></li>
</ul>
<hr />
<table style="width: 300px;background-color:transparent">
<tr>
<td style="width: 20px"><u>A</u></td>
<td slider min="0.65" def="sizeRatio" max="1.2" on-change="textSizeChange(val)" ignore-resize="true"></td>
<td style="width: 20px"><span style="font-size: 30px">A</span></td>
</tr>
<tr>
<td></td><td style="text-align:center" class="primary-disabled cap" ng-click="resetTextSize()" translate="reset"></td><td></td>
</tr>
</table>
<hr />
<a href="#" ui-sref="modal.about" class="block" translate="about"></a>
</div>
</div>
</header>

View File

@@ -1,87 +0,0 @@
<div id="shipyard">
<div class="scroll-x">
<table align="center" style="font-size:0.85em;white-space:nowrap">
<thead>
<tr class="main">
<th rowspan="2" class="sortable le" ng-click="sortShips('name')" translate="ship"></th>
<th rowspan="2" class="sortable" ng-click="sortShips('manufacturer')" translate="manufacturer"></th>
<th rowspan="2" class="sortable" ng-click="sortShips('class')" translate="size"></th>
<th colspan="4" translate="base"></th>
<th colspan="4" translate="max"></th>
<th colspan="5" class="sortable" ng-click="sortShips('hpCount')" translate="hardpoints"></th>
<th colspan="8" class="sortable" ng-click="sortShips('intCount')" translate="internal compartments"></th>
<th rowspan="2" class="sortable" ng-click="sortShips('hullMass')" translate="hull"></th>
<th rowspan="2" class="sortable" ng-click="sortShips('masslock')" translate="MLF"></th>
<th rowspan="2" class="sortable" ng-click="sortShips('retailCost')" translate="cost"></th>
</tr>
<tr>
<!-- Base -->
<th class="sortable lft" ng-click="sortShips('speed')" translate="speed"></th>
<th class="sortable" ng-click="sortShips('boost')" translate="boost"></th>
<th class="sortable" ng-click="sortShips('baseArmour')" translate="armour"></th>
<th class="sortable" ng-click="sortShips('baseShieldStrength')" translate="shields"></th>
<!-- Max -->
<th class="sortable lft" ng-click="sortShips('topSpeed')" translate="speed"></th>
<th class="sortable " ng-click="sortShips('topBoost')" translate="boost"></th>
<th class="sortable " ng-click="sortShips('maxJumpRange')" translate="jump"></th>
<th class="sortable" ng-click="sortShips('maxCargo')" translate="cargo"></th>
<!-- Hardpoints -->
<th class="sortable lft" ng-click="sortShips('hp[1]')" translate="S"></th>
<th class="sortable" ng-click="sortShips('hp[2]')" translate="M"></th>
<th class="sortable" ng-click="sortShips('hp[3]')" translate="L"></th>
<th class="sortable" ng-click="sortShips('hp[4]')" translate="H"></th>
<th class="sortable" ng-click="sortShips('hp[0]')" translate="U"></th>
<!-- Internal -->
<th class="sortable lft" ng-click="sortShips('int[0]')" translate="1"></th>
<th class="sortable" ng-click="sortShips('int[1]')" translate="2"></th>
<th class="sortable" ng-click="sortShips('int[2]')" translate="3"></th>
<th class="sortable" ng-click="sortShips('int[3]')" translate="4"></th>
<th class="sortable" ng-click="sortShips('int[4]')" translate="5"></th>
<th class="sortable" ng-click="sortShips('int[5]')" translate="6"></th>
<th class="sortable" ng-click="sortShips('int[6]')" translate="7"></th>
<th class="sortable" ng-click="sortShips('int[7]')" translate="8"></th>
</tr>
</thead>
<tbody>
<tr class="highlight" ng-repeat="s in shipsOverview | orderBy:shipPredicate:shipDesc">
<td class="le"><a ui-sref="outfit({shipId: s.id})" ng-bind="s.name"></a></td>
<td class="le" ng-bind="s.manufacturer"></td>
<!-- Pad Size -->
<td class="cap" ng-bind="SZM[s.class] | translate"></td>
<!-- Base -->
<td class="ri">{{fCrd(s.speed)}} <u translate>m/s</u></td>
<td class="ri">{{fCrd(s.boost)}} <u translate>m/s</u></td>
<td class="ri" ng-bind="s.baseArmour"></td>
<td class="ri">{{fCrd(s.baseShieldStrength)}} <u translate>Mj</u></td>
<!-- Max -->
<td class="ri">{{fCrd(s.topSpeed)}} <u translate>m/s</u></td>
<td class="ri">{{fCrd(s.topBoost)}} <u translate>m/s</u></td>
<td class="ri">{{fRound(s.maxJumpRange)}} <u translate>LY</u></td>
<td class="ri">{{fCrd(s.maxCargo)}} <u translate>T</u></td>
<!-- Hardpoints -->
<td ng-bind="s.hp[1]" ng-class="{disabled: !s.hp[1]}"></td>
<td ng-bind="s.hp[2]" ng-class="{disabled: !s.hp[2]}"></td>
<td ng-bind="s.hp[3]" ng-class="{disabled: !s.hp[3]}"></td>
<td ng-bind="s.hp[4]" ng-class="{disabled: !s.hp[4]}"></td>
<td ng-bind="s.hp[0]" ng-class="{disabled: !s.hp[0]}"></td>
<!-- Internal -->
<td ng-bind="s.int[0]" ng-class="{disabled: !s.int[0]}"></td>
<td ng-bind="s.int[1]" ng-class="{disabled: !s.int[1]}"></td>
<td ng-bind="s.int[2]" ng-class="{disabled: !s.int[2]}"></td>
<td ng-bind="s.int[3]" ng-class="{disabled: !s.int[3]}"></td>
<td ng-bind="s.int[4]" ng-class="{disabled: !s.int[4]}"></td>
<td ng-bind="s.int[5]" ng-class="{disabled: !s.int[5]}"></td>
<td ng-bind="s.int[6]" ng-class="{disabled: !s.int[6]}"></td>
<td ng-bind="s.int[7]" ng-class="{disabled: !s.int[7]}"></td>
<!-- Other Stats -->
<td class="ri">{{fCrd(s.hullMass)}} <u translate>T</u></td>
<td class="ri" ng-bind="s.masslock"></td>
<td class="ri">{{fCrd(s.retailCost)}} <u translate>CR</u></td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -1,61 +0,0 @@
{
"name": "coriolis_shipyard",
"authors": [
"Colin McLeod <colin@mcleod.eu>"
],
"description": "Coriolis Shipyard for Elite Dangerous",
"main": "app/app.js",
"keywords": [
"elite",
"shipyard"
],
"license": "MIT",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"d3": "~3.5.5",
"ng-lodash": "~0.2.0",
"ui-router-extras": "0.0.13",
"angular-ui-router": "^0.2.15",
"d3-tip": "~0.6.7",
"ng-sortable": "~1.2.1",
"lz-string": "~1.4.4",
"angular": "~1.4.0",
"angular-translate": "~2.7.2"
},
"overrides": {
"angular": {
"main": "angular.min.js"
},
"angular-ui-router": {
"main": "release/angular-ui-router.min.js"
},
"angular-translate": {
"main": "angular-translate.min.js"
},
"d3": {
"main": "d3.min.js"
},
"ng-lodash": {
"main": "build/ng-lodash.min.js"
},
"ui-router-extras": {
"main": [
"release/modular/ct-ui-router-extras.core.min.js",
"release/modular/ct-ui-router-extras.sticky.min.js"
]
},
"ng-sortable": {
"main": "dist/ng-sortable.min.js"
}
},
"resolutions": {
"angular": "~1.4.0"
}
}

1
data

Submodule data deleted from 5350fb5e07

View File

@@ -1,6 +1,6 @@
{
"name": "coriolis_shipyard",
"version": "1.9.0",
"version": "2.0.0-alpha",
"repository": {
"type": "git",
"url": "https://github.com/cmmcleod/coriolis"
@@ -11,14 +11,11 @@
"engine": "node >= 0.12.2",
"license": "MIT",
"devDependencies": {
"angular-mocks": "1.4.x",
"async": "0.9.x",
"del": "1.2.x",
"gulp": "3.9.x",
"gulp-angular-templatecache": "1.6.x",
"gulp-concat": "2.5.x",
"gulp-eslint": "0.13.x",
"gulp-htmlmin": "1.1.x",
"gulp-jasmine": "2.0.x",
"gulp-jsonlint": "1.1.x",
"gulp-less": "3.0.x",
@@ -40,9 +37,14 @@
"karma-json-fixtures-preprocessor": "0.0.4",
"karma-mocha-reporter": "1.0.x",
"karma-phantomjs-launcher": "0.2.x",
"main-bower-files": "2.8.x",
"phantomjs": "1.9.x",
"run-sequence": "1.1.x",
"uglify-js": "2.4.x"
},
"dependencies": {
"classnames": "^2.2.0",
"fbemitter": "^2.0.0",
"react": "^0.14.2",
"react-dom": "^0.14.2"
}
}