diff --git a/__tests__/test-import.js b/__tests__/test-import.js index 4bd3595d..27aa9a4c 100644 --- a/__tests__/test-import.js +++ b/__tests__/test-import.js @@ -108,7 +108,8 @@ describe('Import Modal', function() { it('catches an invalid backup', function() { const importData = require('./fixtures/valid-backup'); let invalidImportData = Object.assign({}, importData); - invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison + //invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison + delete(invalidImportData.builds.asp); pasteText('"this is not valid"'); expect(modal.state.importValid).toBeFalsy(); diff --git a/__tests__/test-ship.js b/__tests__/test-ship.js index e32def5c..00ef39cc 100644 --- a/__tests__/test-ship.js +++ b/__tests__/test-ship.js @@ -16,7 +16,7 @@ describe("Ship", function() { ship.buildWith(shipData.defaults); expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost'); - expect(ship.cargoCapacity).toBeDefined(s + ' cargo'); + expect(ship.cargoCapacity).toBeDefined(); expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' priorityBands'); expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable'); expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange'); diff --git a/package.json b/package.json index c6635494..4c12a8b1 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "coriolis_shipyard", - "version": "2.1.2", + "version": "2.2.0", "repository": { "type": "git", - "url": "https://github.com/cmmcleod/coriolis" + "url": "https://github.com/EDCD/coriolis" }, - "homepage": "https://coriolis.io", - "bugs": "https://github.com/cmmcleod/coriolis/issues", + "homepage": "https://coriolis.edcd.io", + "bugs": "https://github.com/EDCD/coriolis/issues", "private": true, "engine": "node >= 4.0.0", "license": "MIT", @@ -24,14 +24,13 @@ }, "jest": { "scriptPreprocessor": "/node_modules/babel-jest", - "testFileExtensions": [ - "js" - ], + "testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$", "moduleFileExtensions": [ "js", "json", "jsx" ], + "automock": true, "unmockedModulePathPatterns": [ "/node_modules/react", "/node_modules/react-dom", @@ -68,7 +67,7 @@ "extract-text-webpack-plugin": "^0.9.1", "file-loader": "^0.8.4", "html-webpack-plugin": "^1.7.0", - "jest-cli": "^0.9.2", + "jest-cli": "^16.0.1", "jsen": "^0.6.0", "json-loader": "^0.5.3", "less": "^2.5.3", diff --git a/src/app/components/CostSection.jsx b/src/app/components/CostSection.jsx index 305eb21e..699f5d20 100644 --- a/src/app/components/CostSection.jsx +++ b/src/app/components/CostSection.jsx @@ -606,6 +606,7 @@ export default class CostSection extends TranslatedComponent { } if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) { + nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount()); this._updateAmmoCosts(nextProps.ship); this._updateRetrofit(nextProps.ship, retrofitShip); this._sortCost(nextProps.ship); diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index 5f910b7a..dd55be07 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -1,5 +1,6 @@ import React from 'react'; import Slot from './Slot'; +import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret } from './SvgIcons'; /** * Hardpoint / Utility Slot @@ -33,24 +34,31 @@ export default class HardpointSlot extends Slot { */ _getSlotDetails(m, translate, formats, u) { if (m) { - let classRating = `${m.class}${m.rating}${m.mount ? '/' + m.mount : ''}${m.missile ? m.missile : ''}`; + let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; let { drag, drop } = this.props; return
-
{classRating} {translate(m.name || m.grp)}
+
+ {m.mount && m.mount == 'F' ? : ''} + {m.mount && m.mount == 'G' ? : ''} + {m.mount && m.mount == 'T' ? : ''} + {m.type && m.type == 'K' ? : ''} + {m.type && m.type == 'T' ? : ''} + {m.type && m.type == 'KT' ? : ''} + {m.type && m.type == 'E' ? : ''} + {classRating} {translate(m.name || m.grp)}
{m.mass}{u.T}
- { m.damage ?
{translate('damage')}: {m.damage} { m.ssdam ? ({formats.int(m.ssdam)} {u.MJ}) : null }
: null } - { m.dps ?
{translate('DPS')}: {m.dps} { m.mjdps ? ({formats.int(m.mjdps)} {u.MJ}) : null }
: null } - { m.thermload ?
{translate('T-Load')}: {m.thermload}
: null } - { m.type ?
{translate('type')}: {m.type}
: null } + { m.dps ?
{translate('DPS')}: {formats.round1(m.dps)} { m.clip ? ({formats.round1((m.clip * m.dps / m.rof) / ((m.clip / m.rof) + m.reload)) }) : null }
: null } + { m.eps ?
{translate('EPS')}: {formats.round1(m.eps)} { m.clip ? ({formats.round1((m.clip * m.eps / m.rof) / ((m.clip / m.rof) + m.reload)) }) : null }
: null } + { m.hps ?
{translate('HPS')}: {formats.round1(m.hps)} { m.clip ? ({formats.round1((m.clip * m.hps / m.rof) / ((m.clip / m.rof) + m.reload)) }) : null }
: null } + { m.dps && m.eps ?
{translate('DPE')}: {formats.round1(m.dps / m.eps)}
: null } { m.rof ?
{translate('ROF')}: {m.rof}{u.ps}
: null } - { m.armourpen ?
{translate('pen')}: {m.armourpen}
: null } + { m.range && !m.dps ?
{translate('Range')} : {formats.round(m.range / 1000)}{u.km}
: null } { m.shieldmul ?
+{formats.rPct(m.shieldmul)}
: null } - { m.range ?
{m.range} km
: null } - { m.ammo >= 0 ?
{translate('ammo')}: {formats.int(m.clip)}+{formats.int(m.ammo)}
: null } + { m.ammo >= 0 ?
{translate('ammo')}: {formats.int(m.clip)}/{formats.int(m.ammo)}
: null }
; } else { diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index 60ec85bd..4c38afd7 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -39,7 +39,7 @@ export default class InternalSlot extends Slot { { m.time ?
{translate('time')}: {formats.time(m.time)}
: null } { m.maximum ?
{translate('max')}: {(m.maximum)}
: null } { m.rangeLS ?
{translate('range')}: {m.rangeLS}{u.Ls}
: null } - { m.rangeLS === null ?
{u.Ls}
: null } + { m.rangeLS === null ?
∞{u.Ls}
: null } { m.rangeRating ?
{translate('range')}: {m.rangeRating}
: null } { m.armouradd ?
+{m.armouradd} {translate('armour')}
: null } diff --git a/src/app/components/InternalSlotSection.jsx b/src/app/components/InternalSlotSection.jsx index 9a85215d..f35d81e4 100644 --- a/src/app/components/InternalSlotSection.jsx +++ b/src/app/components/InternalSlotSection.jsx @@ -58,7 +58,7 @@ export default class InternalSlotSection extends SlotSection { let ship = this.props.ship; let chargeCap = 0; // Capacity of single activation ship.internal.forEach(function(slot) { - if ((!slot.m || (clobber && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { // Check eligibility due to Orca special case + if ((!slot.m || (clobber && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { // Check eligibility due to passenger ships special case ship.use(slot, ModuleUtils.findInternal('scb', slot.maxClass, 'A')); ship.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge chargeCap += slot.m.recharge; @@ -108,7 +108,7 @@ export default class InternalSlotSection extends SlotSection { slots.push( availableModules.getInts(s.maxClass, s.eligible)} + availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)} onOpen={this._openMenu.bind(this,s)} onSelect={this._selectModule.bind(this, s)} selected={currentMenu == s} diff --git a/src/app/components/SlotSection.jsx b/src/app/components/SlotSection.jsx index af88b729..29ea806e 100644 --- a/src/app/components/SlotSection.jsx +++ b/src/app/components/SlotSection.jsx @@ -1,6 +1,7 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import { wrapCtxMenu } from '../utils/UtilityFunctions'; +import { canMount } from '../utils/SlotFunctions'; import { Equalizer } from '../components/SvgIcons'; import cn from 'classnames'; @@ -90,7 +91,7 @@ export default class SlotSection extends TranslatedComponent { e.stopPropagation(); let os = this.state.originSlot; if (os) { - e.dataTransfer.dropEffect = os != targetSlot && targetSlot.maxClass >= os.m.class ? 'copyMove' : 'none'; + e.dataTransfer.dropEffect = os != targetSlot && canMount(this.props.ship, targetSlot, os.m.grp, os.m.class) ? 'copyMove' : 'none'; this.setState({ targetSlot }); } else { e.dataTransfer.dropEffect = 'none'; @@ -116,9 +117,9 @@ export default class SlotSection extends TranslatedComponent { let { originSlot, targetSlot } = this.state; let m = originSlot.m; - if (targetSlot && m && targetSlot.maxClass >= m.class) { + if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) { // Swap modules if possible - if (targetSlot.m && originSlot.maxClass >= targetSlot.m.class) { + if (targetSlot.m && canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) { this.props.ship.use(originSlot, targetSlot.m, true); } else { // Otherwise empty the origin slot this.props.ship.use(originSlot, null, true); // Empty but prevent summary update @@ -141,12 +142,12 @@ export default class SlotSection extends TranslatedComponent { return null; } if (slot === originSlot) { - if (targetSlot && targetSlot.m && originSlot.maxClass < targetSlot.m.class) { + if (targetSlot && targetSlot.m && !canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) { return 'dropEmpty'; // Origin slot will be emptied } return null; } - if (originSlot.m && slot.maxClass >= originSlot.m.class) { // Eligble drop slot + if (originSlot.m && canMount(this.props.ship, slot, originSlot.m.grp, originSlot.m.class)) { // Eligble drop slot if (slot === targetSlot) { return 'drop'; // Can drop } diff --git a/src/app/components/SvgIcons.jsx b/src/app/components/SvgIcons.jsx index 58debe2d..6e228026 100644 --- a/src/app/components/SvgIcons.jsx +++ b/src/app/components/SvgIcons.jsx @@ -319,6 +319,90 @@ export class Warning extends SvgIcon { } } +/** + * Thermal damage + */ +export class DamageThermal extends SvgIcon { + /** + * Overriden view box + * @return {String} view box + */ + viewBox() { return '0 0 200 200'; } + /** + * Generate the SVG + * @return {React.Component} SVG Contents + */ + svg() { + return + + + + ; + } +} + +/** + * Kinetic damage + */ +export class DamageKinetic extends SvgIcon { + /** + * Overriden view box + * @return {String} view box + */ + viewBox() { return '0 0 200 200'; } + /** + * Generate the SVG + * @return {React.Component} SVG Contents + */ + svg() { + return + + + + + + + + + + + + ; + } +} + +/** + * Explosive damage + */ +export class DamageExplosive extends SvgIcon { + /** + * Overriden view box + * @return {String} view box + */ + viewBox() { return '0 0 200 200'; } + /** + * Generate the SVG + * @return {React.Component} SVG Contents + */ + svg() { + return + + + + + + + + + + + + + + ; + } +} + /** * Fixed mount hardpoint */ @@ -334,11 +418,11 @@ export class MountFixed extends SvgIcon { */ svg() { return - - - - - + + + + + ; } } @@ -358,8 +442,8 @@ export class MountGimballed extends SvgIcon { */ svg() { return - - + + ; } } @@ -379,9 +463,14 @@ export class MountTurret extends SvgIcon { */ svg() { return - - - + + + + + + + + ; } } diff --git a/src/app/i18n/Language.jsx b/src/app/i18n/Language.jsx index dc237c24..6d4d21a8 100644 --- a/src/app/i18n/Language.jsx +++ b/src/app/i18n/Language.jsx @@ -41,12 +41,14 @@ export function getLanguage(langCode) { formats: { gen, // General number format (.e.g 1,001,001.1234) int: d3Locale.numberFormat(',.0f'), // Fixed to 0 decimal places (.e.g 1,001) + f1: d3Locale.numberFormat(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1) f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10) s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k) pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%) pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%) r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1) rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%) + round1: (d) => gen(d3.round(d, 1)), // Rounded to 0-1 decimal places (.e.g 5.1, 4) round: (d) => gen(d3.round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1) time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2) }, diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index 7756bd1f..c29d0436 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -42,6 +42,7 @@ export const terms = { cs: 'Cargo Scanner', dc: 'Docking Computer', fc: 'Fragment Cannon', + fh: 'Fighter Hangar', fi: 'FSD Interdictor', fs: 'Fuel Scoop', fsd: 'Frame Shift Drive', @@ -58,6 +59,10 @@ export const terms = { pa: 'Plasma Accelerator', pas: 'Planetary Approach Suite', pc: 'Prospector Limpet Controller', + pce: 'Economy Class Passenger Cabin', + pci: 'Business Class Passenger Cabin', + pcm: 'First Class Passenger Cabin', + pcq: 'Luxury Passenger Cabin', pd: 'power distributor', pl: 'Pulse Laser', pp: 'Power Plant', diff --git a/src/app/pages/AboutPage.jsx b/src/app/pages/AboutPage.jsx index ed8771c9..77579786 100644 --- a/src/app/pages/AboutPage.jsx +++ b/src/app/pages/AboutPage.jsx @@ -29,7 +29,7 @@ export default class AboutPage extends Page {

This is a clone of the Coriolis project, whose original author is currently unable to maintain it. This clone is maintained by the EDCD community.

To recover your builds, go to https://coriolis.io/, backup your builds (Settings / Backup), copy the text, return here and import (Settings / Import).

The Coriolis project was inspired by E:D Shipyard and, of course, Elite Dangerous. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.

-

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.

+

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. A number of assets were sourced from ED Assets

diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 0ac81281..e4ee5633 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -293,7 +293,7 @@ export default class OutfittingPage extends Page {

{ship.name}

- + diff --git a/src/app/pages/ShipyardPage.jsx b/src/app/pages/ShipyardPage.jsx index 0dc32244..fe6bcae6 100644 --- a/src/app/pages/ShipyardPage.jsx +++ b/src/app/pages/ShipyardPage.jsx @@ -241,7 +241,7 @@ export default class ShipyardPage extends Page { return (
-

This is Coriolis EDCD Edition - a temporary clone of https://coriolis.io/ with added support for E:D 2.1. For more info see Settings / About

+

This is Coriolis EDCD Edition - a temporary clone of https://coriolis.io/ with added support for E:D 2.2. For more info see Settings / About

diff --git a/src/app/shipyard/Constants.js b/src/app/shipyard/Constants.js index 19b7ac07..70688066 100755 --- a/src/app/shipyard/Constants.js +++ b/src/app/shipyard/Constants.js @@ -37,6 +37,7 @@ export const ModuleGroupToName = { am: 'Auto Field-Maintenance Unit', bsg: 'Bi-Weave Shield Generator', cr: 'Cargo Rack', + fh: 'Fighter Hangar', fi: 'Frame Shift Drive Interdictor', hb: 'Hatch Breaker Limpet Controller', hr: 'Hull Reinforcement Package', @@ -48,6 +49,10 @@ export const ModuleGroupToName = { dc: 'Docking Computer', fx: 'Fuel Transfer Limpet Controller', pc: 'Prospector Limpet Controller', + pce: 'Economy Class Passenger Cabin', + pci: 'Business Class Passenger Cabin', + pcm: 'First Class Passenger Cabin', + pcq: 'Luxury Passenger Cabin', cc: 'Collector Limpet Controller', // Hard Points diff --git a/src/app/shipyard/ModuleSet.js b/src/app/shipyard/ModuleSet.js index 601bc33b..04711cb8 100755 --- a/src/app/shipyard/ModuleSet.js +++ b/src/app/shipyard/ModuleSet.js @@ -71,16 +71,23 @@ export default class ModuleSet { /** * Determine the modules that areeligible for an internal slot + * @param {Object} ship The ship * @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) { + getInts(ship, c, eligible) { let o = {}; for (let key in this.internal) { if (eligible && !eligible[key]) { continue; } + if (key == 'pcq' && !(ship.luxuryCabins && ship.luxuryCabins === true)) { + continue; + } + if (key == 'fh' && !(ship.fighterHangars && ship.fighterHangars === true)) { + continue; + } let data = filter(this.internal[key], c, 0, this.mass); if (data.length) { // If group is not empty o[key] = data; diff --git a/src/app/shipyard/Serializer.js b/src/app/shipyard/Serializer.js index f3033f94..99c47323 100644 --- a/src/app/shipyard/Serializer.js +++ b/src/app/shipyard/Serializer.js @@ -6,6 +6,29 @@ import LZString from 'lz-string'; const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank']; +/** + * Generates ship-loadout JSON Schema standard object + * @param {Object} standard model + * @return {Object} JSON Schema + */ +function standardToSchema(standard) { + if (standard.m) { + let o = { + class: standard.m.class, + rating: standard.m.rating, + enabled: Boolean(standard.enabled), + priority: standard.priority + 1 + }; + + if (standard.m.name) { + o.name = standard.m.name; + } + + return o; + } + return null; +} + /** * Generates ship-loadout JSON Schema slot object * @param {Object} slot Slot model @@ -61,13 +84,13 @@ export function toDetailedBuild(buildName, ship) { standard: { bulkheads: BulkheadNames[ship.bulkheads.m.index], cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 }, - powerPlant: { class: standard[0].m.class, rating: standard[0].m.rating, enabled: Boolean(standard[0].enabled), priority: standard[0].priority + 1 }, - thrusters: { class: standard[1].m.class, rating: standard[1].m.rating, enabled: Boolean(standard[1].enabled), priority: standard[1].priority + 1 }, - frameShiftDrive: { class: standard[2].m.class, rating: standard[2].m.rating, enabled: Boolean(standard[2].enabled), priority: standard[2].priority + 1 }, - lifeSupport: { class: standard[3].m.class, rating: standard[3].m.rating, enabled: Boolean(standard[3].enabled), priority: standard[3].priority + 1 }, - powerDistributor: { class: standard[4].m.class, rating: standard[4].m.rating, enabled: Boolean(standard[4].enabled), priority: standard[4].priority + 1 }, - sensors: { class: standard[5].m.class, rating: standard[5].m.rating, enabled: Boolean(standard[5].enabled), priority: standard[5].priority + 1 }, - fuelTank: { class: standard[6].m.class, rating: standard[6].m.rating, enabled: Boolean(standard[6].enabled), priority: standard[6].priority + 1 } + powerPlant: standardToSchema(standard[0]), + thrusters: standardToSchema(standard[1]), + frameShiftDrive: standardToSchema(standard[2]), + lifeSupport: standardToSchema(standard[3]), + powerDistributor: standardToSchema(standard[4]), + sensors: standardToSchema(standard[5]), + fuelTank: standardToSchema(standard[6]) }, hardpoints: hardpoints.filter(slot => slot.maxClass > 0).map(slotToSchema), utility: hardpoints.filter(slot => slot.maxClass === 0).map(slotToSchema), diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 9984e358..3b006005 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -3,7 +3,7 @@ import * as Calc from './Calculations'; import * as ModuleUtils from './ModuleUtils'; import LZString from 'lz-string'; -const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs']; +const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh']; /** * Returns the power usage type of a slot and it's particular modul diff --git a/src/app/shipyard/ShipRoles.js b/src/app/shipyard/ShipRoles.js index 7fbaefd2..fc06fbe0 100644 --- a/src/app/shipyard/ShipRoles.js +++ b/src/app/shipyard/ShipRoles.js @@ -15,7 +15,7 @@ export function multiPurpose(ship, shielded, bulkheadIndex) { if (shielded) { ship.internal.some(function(slot) { - if (canMount(slot, 'sg')) { // Assuming largest slot can hold an eligible shield + if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A')); ship.setSlotEnabled(slot, true); return true; @@ -35,7 +35,7 @@ export function trader(ship, shielded, standardOpts) { for (let i = ship.internal.length; i--;) { let slot = ship.internal[i]; - if (sg && canMount(slot, 'sg', sg.class)) { + if (sg && canMount(ship, slot, 'sg', sg.class)) { ship.use(slot, sg); sg = null; } else { @@ -77,23 +77,23 @@ export function explorer(ship, planetary) { let slot = ship.internal[i]; let nextSlot = (i + 1) < intLength ? ship.internal[i + 1] : null; // Fit best possible Fuel Scoop - if (!fuelScoopSlot && canMount(slot, 'fs')) { + if (!fuelScoopSlot && canMount(ship, slot, 'fs')) { fuelScoopSlot = slot; ship.use(slot, ModuleUtils.findInternal('fs', slot.maxClass, 'A')); ship.setSlotEnabled(slot, true); // Mount a Shield generator if possible AND an AFM Unit has been mounted already (Guarantees at least 1 AFM Unit) - } else if (!sgSlot && shieldNext && canMount(slot, 'sg', sg.class) && !canMount(nextSlot, 'sg', sg.class)) { + } else if (!sgSlot && shieldNext && canMount(ship, slot, 'sg', sg.class) && !canMount(ship, nextSlot, 'sg', sg.class)) { sgSlot = slot; shieldNext = false; ship.use(slot, sg); ship.setSlotEnabled(slot, true); // if planetary explorer and the next slot cannot mount a PVH or the next modul to mount is a SG - } else if (planetary && !pvhSlot && canMount(slot, 'pv') && (shieldNext || !canMount(nextSlot, 'pv', 2))) { + } else if (planetary && !pvhSlot && canMount(ship, slot, 'pv') && (shieldNext || !canMount(ship, nextSlot, 'pv', 2))) { pvhSlot = slot; ship.use(slot, ModuleUtils.findInternal('pv', Math.min(Math.floor(pvhSlot.maxClass / 2) * 2, 6), 'G')); ship.setSlotEnabled(slot, false); // Disabled power for PVH shieldNext = !sgSlot; - } else if (afmUnitCount > 0 && canMount(slot, 'am')) { + } else if (afmUnitCount > 0 && canMount(ship, slot, 'am')) { afmUnitCount--; ship.use(slot, ModuleUtils.findInternal('am', slot.maxClass, 'A')); ship.setSlotEnabled(slot, false); // Disabled power for AFM Unit diff --git a/src/app/utils/SlotFunctions.js b/src/app/utils/SlotFunctions.js index ed47d4dc..fa63852c 100644 --- a/src/app/utils/SlotFunctions.js +++ b/src/app/utils/SlotFunctions.js @@ -5,14 +5,19 @@ import { Infinite } from '../components/SvgIcons'; import Persist from '../stores/Persist'; /** - * Determine if a slot can mount a module of a particular class and group + * Determine if a slot on a ship can mount a module of a particular class and group + * @param {Object} ship Ship object * @param {Object} slot Slot object * @param {String} group Module group/type abbrivation/code * @param {Integer} clazz [Optional] Module Class/Size * @return {Boolean} True if the slot can mount the module */ -export function canMount(slot, group, clazz) { - if (slot && (!slot.eligible || slot.eligible[group]) && (clazz === undefined || slot.maxClass >= clazz)) { +export function canMount(ship, slot, group, clazz) { + if (slot && + (!slot.eligible || slot.eligible[group]) && + (group != 'pcq' || (ship.luxuryCabins && ship.luxuryCabins === true)) && + (group != 'fh' || (ship.fighterHangars && ship.fighterHangars === true)) && + (clazz === undefined || slot.maxClass >= clazz)) { return true; } return false; @@ -122,7 +127,11 @@ const PROP_BLACKLIST = { enginecapacity: 1, enginerecharge: 1, systemcapacity: 1, - systemrecharge: 1 + systemrecharge: 1, + breachdps: 1, + breachmin: 1, + breachmax: 1, + integrity: 1 }; const TERM_LOOKUP = { @@ -175,7 +184,7 @@ function diffClass(a, b, negative) { */ function diff(format, mVal, mmVal) { if (mVal == Infinity) { - return ; + return '∞'; } else { let diff = mVal - mmVal; if (!diff || mVal === undefined || diff == mVal || Math.abs(diff) == Infinity) { diff --git a/src/less/slot.less b/src/less/slot.less index 641b876b..11d17d30 100755 --- a/src/less/slot.less +++ b/src/less/slot.less @@ -12,6 +12,8 @@ background-color: @primary-bg; border: 1px solid @primary-disabled; color: @fg; + stroke: @fg; + stroke-width: 20; fill: @fg; .details-container { @@ -54,6 +56,8 @@ font-size: 1.2em; width: 1.2em; color: @primary-disabled; + stroke: @primary-disabled; + stroke-width: 20; border-right: 1px solid @primary-disabled; box-sizing: border-box; padding-top: 0.2em; @@ -64,6 +68,8 @@ text-transform: uppercase; font-size: 1.3em; color: lighten(@primary-bg, 12%); + stroke: lighten(@primary-bg, 12%); + stroke-width: 20; text-align: center; letter-spacing: 0.1em; line-height: 1.7em; @@ -71,12 +77,16 @@ &.selected { color: @primary-bg; + stroke: @primary-bg; + stroke-width: 20; fill: @primary-bg; background-color: @primary; border: 1px solid @primary; z-index: 1; .sz { color: @primary; + stroke: @primary; + stroke-width: 20; background-color: @primary-bg; border-right: 1px solid @primary; } diff --git a/src/schemas/ship-loadout/3.json b/src/schemas/ship-loadout/3.json index 1afbda8a..43c034d4 100644 --- a/src/schemas/ship-loadout/3.json +++ b/src/schemas/ship-loadout/3.json @@ -76,7 +76,11 @@ "class": { "type": "integer", "minimum": 2, "maximum": 8 }, "rating": { "$ref": "#/definitions/standardRatings" }, "enabled": { "type": "boolean" }, - "priority": { "type": "integer", "minimum": 1, "maximum": 5 } + "priority": { "type": "integer", "minimum": 1, "maximum": 5 }, + "name": { + "description": "The name identifing the thrusters (if applicable), e.g. 'Enhanced Performance'", + "type": "string" + } } }, "frameShiftDrive": {