diff --git a/ChangeLog.md b/ChangeLog.md index cd9a709b..0cb5e994 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,18 +1,28 @@ #2.3.0 - * Add 2.3 diminishing returns on shield value * Make scan time visible on scanners where available * Update power distributor able-to-boost calculation to take fractional MJ values in to account * Revert to floating header due to issues on iOS - * Add effective total shield value to defence summary * Fix issue where new module added to a slot did not reset its enabled status * Show integrity value for relevant modules * Reset old modification values when a new roll is applied * Fix issue with miner role where refinery would not be present in ships with class 5 slots but no class 4 * Ensure that boost value is set correctly when modifications to power distributor enable/disable boost * Ensure that hull reinforcement modifications take the inherent resistance in to account when calculating modification percentages - * Add tooltip for blueprints providing details of the features they alter + * Add tooltip for blueprints providing details of the features they alter, the components required for the blueprint and the engineer(s) who cam craft them * Use opponent's saved pips if available * Ignore rounds per shot for EPS and HPS calculations; it's already factored in to the numbers + * Ensure that clip size modification imports result in whole numbers + * Rework of separate offence/defence/movement sections to a unified interface + * Use cargo hatch information on import if available + * Additional information of power distributor pips, boost, cargo and fuel loads added to build + * Additional information of opponent and engagement range added to build + * Reworking of offence, defence and movement information in to separate tabs as part of the outfitting screen: + * Power and costs section provides the existing 'Power' and 'Costs' sections + * Profiles section provides a number of graphs that show how various components of the build (top speed, sustained DPS against opponent's shields and armour etc) are affected by mass, range, etc. + * Offence section provides details of your build's damage distribution and per-weapon effectiveness. It also gives summary information for how long it will take for your build to wear down your opponent's shields and armour + * Defence section provides details of your build's defences against your selected opponent. It provides details of the effectiveness of your resistances of both shields and armour, and effective strength of each as a result. It also provides key metrics around shield longevity and recovery times, as well as module protection + * Fix power band marker to show safe power limit at 40% rather than 50% + * Restyle blueprint list to improve consistency with similar menus #2.2.19 * Power management panel now displays modules in descending order of power usage by default diff --git a/src/app/components/Boost.jsx b/src/app/components/Boost.jsx index 10e60927..b6803138 100644 --- a/src/app/components/Boost.jsx +++ b/src/app/components/Boost.jsx @@ -1,7 +1,6 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import { Ships } from 'coriolis-data/dist'; -import ShipSelector from './ShipSelector'; import { nameComparator } from '../utils/SlotFunctions'; import { Pip } from './SvgIcons'; import LineChart from '../components/LineChart'; diff --git a/src/app/components/CostSection.jsx b/src/app/components/CostSection.jsx index f0dc5bfd..302dd5c8 100644 --- a/src/app/components/CostSection.jsx +++ b/src/app/components/CostSection.jsx @@ -507,7 +507,7 @@ export default class CostSection extends TranslatedComponent { scoop = true; break; case 'scb': - q = slotGroup[i].m.getCells(); + q = slotGroup[i].m.getAmmo() + 1; break; case 'am': q = slotGroup[i].m.getAmmo(); diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx deleted file mode 100644 index d1d0fbe6..00000000 --- a/src/app/components/DamageDealt.jsx +++ /dev/null @@ -1,589 +0,0 @@ -import React from 'react'; -import TranslatedComponent from './TranslatedComponent'; -import { Ships } from 'coriolis-data/dist'; -import ShipSelector from './ShipSelector'; -import { nameComparator } from '../utils/SlotFunctions'; -import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons'; -import LineChart from '../components/LineChart'; -import Slider from '../components/Slider'; -import * as ModuleUtils from '../shipyard/ModuleUtils'; -import Module from '../shipyard/Module'; - -const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777']; - -/** - * Generates an internationalization friendly weapon comparator that will - * sort by specified property (if provided) then by name/group, class, rating - * @param {function} translate Translation function - * @param {function} propComparator Optional property comparator - * @param {boolean} desc Use descending order - * @return {function} Comparator function for names - */ -export function weaponComparator(translate, propComparator, desc) { - return (a, b) => { - if (!desc) { // Flip A and B if ascending order - let t = a; - a = b; - b = t; - } - - // If a property comparator is provided use it first - let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b); - - if (diff) { - return diff; - } - - // Property matches so sort by name / group, then class, rating - if (a.name === b.name && a.grp === b.grp) { - if(a.class == b.class) { - return a.rating > b.rating ? 1 : -1; - } - return a.class - b.class; - } - - return nameComparator(translate, a, b); - }; -} - -/** - * Damage against a selected ship - */ -export default class DamageDealt extends TranslatedComponent { - static propTypes = { - ship: React.PropTypes.object.isRequired, - code: React.PropTypes.string.isRequired - }; - - static DEFAULT_AGAINST = Ships['anaconda']; - - /** - * Constructor - * @param {Object} props React Component properties - * @param {Object} context React Component context - */ - constructor(props, context) { - super(props); - - this._sort = this._sort.bind(this); - this._onShipChange = this._onShipChange.bind(this); - this._onCollapseExpand = this._onCollapseExpand.bind(this); - - const ship = this.props.ship; - const against = DamageDealt.DEFAULT_AGAINST; - const maxRange = this._calcMaxRange(ship); - const range = 1000 / maxRange; - const maxDps = this._calcMaxSDps(ship, against); - const weaponNames = this._weaponNames(ship, context); - - this.state = { - predicate: 'n', - desc: true, - against, - expanded: false, - range, - maxRange, - maxDps, - weaponNames, - calcHullDpsFunc: this._calcDps.bind(this, context, ship, weaponNames, against, true), - calcShieldsDpsFunc: this._calcDps.bind(this, context, ship, weaponNames, against, false) - }; - } - - /** - * Set the initial weapons state - */ - componentWillMount() { - const data = this._calcWeaponsDps(this.props.ship, this.state.against, this.state.range * this.state.maxRange, true); - this.setState({ weapons: data.weapons, totals: data.totals }); - } - - /** - * Set the updated weapons state if our ship changes - * @param {Object} nextProps Incoming/Next properties - * @param {Object} nextContext Incoming/Next conext - * @return {boolean} Returns true if the component should be rerendered - */ - componentWillReceiveProps(nextProps, nextContext) { - if (nextProps.code != this.props.code) { - const data = this._calcWeaponsDps(nextProps.ship, this.state.against, this.state.range * this.state.maxRange, this.props.hull); - const weaponNames = this._weaponNames(nextProps.ship, nextContext); - const maxRange = this._calcMaxRange(nextProps.ship); - const maxDps = this._calcMaxSDps(nextProps.ship, this.state.against); - this.setState({ weapons: data.weapons, - totals: data.totals, - weaponNames, - maxRange, - maxDps, - calcHullDpsFunc: this._calcDps.bind(this, nextContext, nextProps.ship, weaponNames, this.state.against, true), - calcShieldsDpsFunc: this._calcDps.bind(this, nextContext, nextProps.ship, weaponNames, this.state.against, false) }); - } - return true; - } - - /** - * Calculate the maximum sustained single-weapon DPS for this ship against another ship - * @param {Object} ship The ship - * @param {Object} against The target - * @return {number} The maximum sustained single-weapon DPS - */ - _calcMaxSDps(ship, against) { - let maxSDps = 0; - for (let i = 0; i < ship.hardpoints.length; i++) { - if (ship.hardpoints[i].m && ship.hardpoints[i].enabled) { - const m = ship.hardpoints[i].m; - const thisSDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps(); - if (thisSDps > maxSDps) { - maxSDps = thisSDps; - } - } - } - return maxSDps; - } - - /** - * Calculate the per-weapon DPS for this ship against another ship at a given range - * @param {Object} context The context - * @param {Object} ship The ship - * @param {Object} weaponNames The names of the weapons for which to calculate DPS - * @param {Object} against The target - * @param {bool} hull true if to calculate against hull, false if to calculate against shields - * @param {Object} range The engagement range - * @return {array} The array of weapon DPS - */ - _calcDps(context, ship, weaponNames, against, hull, range) { - let results = {}; - let weaponNum = 0; - for (let i = 0; i < ship.hardpoints.length; i++) { - if (ship.hardpoints[i].m && ship.hardpoints[i].enabled) { - const m = ship.hardpoints[i].m; - results[weaponNames[weaponNum++]] = this._calcWeaponDps(context, m, against, hull, range); - } - } - return results; - } - - /** - * Calculate the maximum range of a ship's weapons - * @param {Object} ship The ship - * @returns {int} The maximum range, in metres - */ - _calcMaxRange(ship) { - let maxRange = 1000; - for (let i = 0; i < ship.hardpoints.length; i++) { - if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) { - const thisRange = ship.hardpoints[i].m.getRange(); - if (thisRange > maxRange) { - maxRange = thisRange; - } - } - } - - return maxRange; - } - - /** - * Obtain the weapon names for this ship - * @param {Object} ship The ship - * @param {Object} context The context - * @return {array} The weapon names - */ - _weaponNames(ship, context) { - const translate = context.language.translate; - let names = []; - let num = 1; - for (let i = 0; i < ship.hardpoints.length; i++) { - if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) { - const m = ship.hardpoints[i].m; - let name = '' + num++ + ': ' + m.class + m.rating + (m.missile ? '/' + m.missile : '') + ' ' + translate(m.name || m.grp); - let engineering; - if (m.blueprint && m.blueprint.name) { - engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; - if (m.blueprint.special && m.blueprint.special.id) { - engineering += ', ' + translate(m.blueprint.special.name); - } - } - if (engineering) { - name = name + ' (' + engineering + ')'; - } - names.push(name); - } - } - return names; - } - - - /** - * Calculate a specific weapon DPS for this ship against another ship at a given range - * @param {Object} context The context - * @param {Object} m The weapon - * @param {Object} against The target - * @param {bool} hull true if to calculate against hull, false if to calculate against shields - * @param {Object} range The engagement range - * @return {number} The weapon DPS - */ - _calcWeaponDps(context, m, against, hull, range) { - const translate = context.language.translate; - let dropoff = 1; - if (m.getFalloff()) { - // Calculate the dropoff % due to range - if (range > m.getRange()) { - // Weapon is out of range - dropoff = 0; - } else { - const falloff = m.getFalloff(); - if (range > falloff) { - const dropoffRange = m.getRange() - falloff; - // Assuming straight-line falloff - dropoff = 1 - (range - falloff) / dropoffRange; - } - } - } - const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; - let engineering; - if (m.blueprint && m.blueprint.name) { - engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; - if (m.blueprint.special && m.blueprint.special.id) { - engineering += ', ' + translate(m.blueprint.special.name); - } - } - const effectivenessShields = dropoff; - const effectiveDpsShields = m.getDps() * effectivenessShields; - const effectiveSDpsShields = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessShields : effectiveDpsShields); - const effectivenessHull = (m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness) * dropoff; - const effectiveDpsHull = m.getDps() * effectivenessHull; - const effectiveSDpsHull = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessHull : effectiveDpsHull); - - return hull ? effectiveSDpsHull : effectiveSDpsShields; - } - - /** - * Calculate the damage dealt by a ship - * @param {Object} ship The ship which will deal the damage - * @param {Object} against The ship against which damage will be dealt - * @param {Object} range The engagement range - * @return {object} Returns the per-weapon damage - */ - _calcWeaponsDps(ship, against, range) { - const translate = this.context.language.translate; - - // Tidy up the range so that it's to 4 decimal places - range = Math.round(10000 * range) / 10000; - - // Track totals - let totals = {}; - totals.effectivenessShields = 0; - totals.effectiveDpsShields = 0; - totals.effectiveSDpsShields = 0; - totals.effectivenessHull = 0; - totals.effectiveDpsHull = 0; - totals.effectiveSDpsHull = 0; - let totalDps = 0; - - let weapons = []; - for (let i = 0; i < ship.hardpoints.length; i++) { - if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) { - const m = ship.hardpoints[i].m; - if (m.getDamage() && m.grp !== 'po') { - let dropoff = 1; - if (m.getFalloff()) { - // Calculate the dropoff % due to range - if (range > m.getRange()) { - // Weapon is out of range - dropoff = 0; - } else { - const falloff = m.getFalloff(); - if (range > falloff) { - const dropoffRange = m.getRange() - falloff; - // Assuming straight-line falloff - dropoff = 1 - (range - falloff) / dropoffRange; - } - } - } - const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; - let engineering; - if (m.blueprint && m.blueprint.name) { - engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; - if (m.blueprint.special && m.blueprint.special.id >= 0) { - engineering += ', ' + translate(m.blueprint.special.name); - } - } - - // Alter effectiveness as per standard shields (all have the same resistances) - const sg = ModuleUtils.findModule('sg', '3v'); - let effectivenessShields = 0; - if (m.getDamageDist().E) { - effectivenessShields += m.getDamageDist().E * (1 - sg.getExplosiveResistance()); - } - if (m.getDamageDist().K) { - effectivenessShields += m.getDamageDist().K * (1 - sg.getKineticResistance()); - } - if (m.getDamageDist().T) { - effectivenessShields += m.getDamageDist().T * (1 - sg.getThermalResistance()); - } - if (m.getDamageDist().A) { - effectivenessShields += m.getDamageDist().A; - } - effectivenessShields *= dropoff; - const effectiveDpsShields = m.getDps() * effectivenessShields; - const effectiveSDpsShields = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessShields : effectiveDpsShields); - - // Alter effectiveness as per standard hull - const bulkheads = new Module({ template: against.bulkheads }); - let effectivenessHull = 0; - if (m.getDamageDist().E) { - effectivenessHull += m.getDamageDist().E * (1 - bulkheads.getExplosiveResistance()); - } - if (m.getDamageDist().K) { - effectivenessHull += m.getDamageDist().K * (1 - bulkheads.getKineticResistance()); - } - if (m.getDamageDist().T) { - effectivenessHull += m.getDamageDist().T * (1 - bulkheads.getThermalResistance()); - } - if (m.getDamageDist().A) { - effectivenessHull += m.getDamageDist().A; - } - effectivenessHull *= Math.min(m.getPiercing() / against.properties.hardness, 1) * dropoff; - const effectiveDpsHull = m.getDps() * effectivenessHull; - const effectiveSDpsHull = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessHull : effectiveDpsHull); - totals.effectiveDpsShields += effectiveDpsShields; - totals.effectiveSDpsShields += effectiveSDpsShields; - totals.effectiveDpsHull += effectiveDpsHull; - totals.effectiveSDpsHull += effectiveSDpsHull; - totalDps += m.getDps(); - - weapons.push({ id: i, - mount: m.mount, - name: m.name || m.grp, - classRating, - engineering, - effectiveDpsShields, - effectiveSDpsShields, - effectivenessShields, - effectiveDpsHull, - effectiveSDpsHull, - effectivenessHull }); - } - } - } - totals.effectivenessShields = totalDps == 0 ? 0 : totals.effectiveDpsShields / totalDps; - totals.effectivenessHull = totalDps == 0 ? 0 : totals.effectiveDpsHull / totalDps; - - return { weapons, totals }; - } - - /** - * Triggered when the collapse or expand icons are clicked - */ - _onCollapseExpand() { - this.setState({ expanded: !this.state.expanded }); - } - - /** - * Triggered when the ship we compare against changes - * @param {string} s the new ship ID - */ - _onShipChange(s) { - const against = Ships[s]; - const data = this._calcWeaponsDps(this.props.ship, against, this.state.range * this.state.maxRange); - this.setState({ against, - weapons: data.weapons, - totals: data.totals, - calcHullDpsFunc: this._calcDps.bind(this, this.context, this.props.ship, this.state.weaponNames, against, true), - calcShieldsDpsFunc: this._calcDps.bind(this, this.context, this.props.ship, this.state.weaponNames, against, false) }); - } - - /** - * Set the sort order and sort - * @param {string} predicate Sort predicate - */ - _sortOrder(predicate) { - let desc = this.state.desc; - - if (predicate == this.state.predicate) { - desc = !desc; - } else { - desc = true; - } - - this._sort(this.props.ship, predicate, desc); - this.setState({ predicate, desc }); - } - - /** - * Sorts the weapon list - * @param {Ship} ship Ship instance - * @param {string} predicate Sort predicate - * @param {Boolean} desc Sort order descending - */ - _sort(ship, predicate, desc) { - let comp = weaponComparator.bind(null, this.context.language.translate); - - switch (predicate) { - case 'n': comp = comp(null, desc); break; - case 'edpss': comp = comp((a, b) => a.effectiveDpsShields - b.effectiveDpsShields, desc); break; - case 'esdpss': comp = comp((a, b) => a.effectiveSDpsShields - b.effectiveSDpsShields, desc); break; - case 'es': comp = comp((a, b) => a.effectivenessShields - b.effectivenessShields, desc); break; - case 'edpsh': comp = comp((a, b) => a.effectiveDpsHull - b.effectiveDpsHull, desc); break; - case 'esdpsh': comp = comp((a, b) => a.effectiveSDpsHull - b.effectiveSDpsHull, desc); break; - case 'eh': comp = comp((a, b) => a.effectivenessHull - b.effectivenessHull, desc); break; - } - - this.state.weapons.sort(comp); - } - - /** - * Render individual rows for hardpoints - * @param {Function} translate Translate function - * @param {Object} formats Localised formats map - * @return {array} The individual rows - * - */ - _renderRows(translate, formats) { - const { termtip, tooltip } = this.context; - - let rows = []; - - if (this.state.weapons) { - for (let i = 0; i < this.state.weapons.length; i++) { - const weapon = this.state.weapons[i]; - - rows.push( - - {weapon.mount == 'F' ? : null} - {weapon.mount == 'G' ? : null} - {weapon.mount == 'T' ? : null} - {weapon.classRating} {translate(weapon.name)} - {weapon.engineering ? ' (' + weapon.engineering + ')' : null } - - {formats.f1(weapon.effectiveDpsShields)} - {formats.f1(weapon.effectiveSDpsShields)} - {formats.pct(weapon.effectivenessShields)} - {formats.f1(weapon.effectiveDpsHull)} - {formats.f1(weapon.effectiveSDpsHull)} - {formats.pct(weapon.effectivenessHull)} - ); - } - } - - return rows; - } - - /** - * Update current range - * @param {number} range Range 0-1 - */ - _rangeChange(range) { - const data = this._calcWeaponsDps(this.props.ship, this.state.against, this.state.range * this.state.maxRange); - this.setState({ range, - weapons: data.weapons, - totals: data.totals }); - } - - /** - * Render damage dealt - * @return {React.Component} contents - */ - render() { - const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; - const { formats, translate, units } = language; - const { against, expanded, maxRange, range, totals } = this.state; - const { ship } = this.props; - - const sortOrder = this._sortOrder; - const onCollapseExpand = this._onCollapseExpand; - - const code = ship.getHardpointsString() + '.' + ship.getModificationsString() + '.' + ship.getPowerEnabledString() + '.' + against.properties.name; - - return ( - -

{translate('damage dealt to')} {expanded ? : }

- {expanded ? - - - - - - - - - - - - - - - - - - - {this._renderRows(translate, formats)} - - - - - - - - - - - - -
{translate('weapon')}{translate('standard shields')}{translate('standard armour')}
{translate('effective dps')}{translate('effective sdps')}{translate('effectiveness')}{translate('effective dps')}{translate('effective sdps')}{translate('effectiveness')}
{translate('total')}{formats.f1(totals.effectiveDpsShields)}{formats.f1(totals.effectiveSDpsShields)}{formats.pct(totals.effectivenessShields)}{formats.f1(totals.effectiveDpsHull)}{formats.f1(totals.effectiveSDpsHull)}{formats.pct(totals.effectivenessHull)}
- - - - - - - - -
{translate('engagement range')} - - - {formats.f2(range * maxRange / 1000)}{units.km} -
-
-

{translate('sustained dps against standard shields')}

- -
-
-

{translate('sustained dps against standard armour')}

- -
-
: null } -
- ); - } -} diff --git a/src/app/components/DamageReceived.jsx b/src/app/components/DamageReceived.jsx deleted file mode 100644 index ec1799ed..00000000 --- a/src/app/components/DamageReceived.jsx +++ /dev/null @@ -1,327 +0,0 @@ -import React from 'react'; -import TranslatedComponent from './TranslatedComponent'; -import { Modules } from 'coriolis-data/dist'; -import { nameComparator } from '../utils/SlotFunctions'; -import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons'; -import Module from '../shipyard/Module'; -import Slider from '../components/Slider'; - -/** - * Generates an internationalization friendly weapon comparator that will - * sort by specified property (if provided) then by name/group, class, rating - * @param {function} translate Translation function - * @param {function} propComparator Optional property comparator - * @param {boolean} desc Use descending order - * @return {function} Comparator function for names - */ -export function weaponComparator(translate, propComparator, desc) { - return (a, b) => { - if (!desc) { // Flip A and B if ascending order - let t = a; - a = b; - b = t; - } - - // If a property comparator is provided use it first - let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b); - - if (diff) { - return diff; - } - - // Property matches so sort by name / group, then class, rating - if (a.name === b.name && a.grp === b.grp) { - if(a.class == b.class) { - return a.rating > b.rating ? 1 : -1; - } - return a.class - b.class; - } - - return nameComparator(translate, a, b); - }; -} - -/** - * Damage received by a selected ship - */ -export default class DamageReceived extends TranslatedComponent { - static propTypes = { - ship: React.PropTypes.object.isRequired, - code: React.PropTypes.string.isRequired - }; - - /** - * Constructor - * @param {Object} props React Component properties - */ - constructor(props) { - super(props); - - this._sort = this._sort.bind(this); - this._onCollapseExpand = this._onCollapseExpand.bind(this); - - this.state = { - predicate: 'n', - desc: true, - expanded: false, - range: 0.1667, - maxRange: 6000 - }; - } - - /** - * Set the initial weapons state - */ - componentWillMount() { - this.setState({ weapons: this._calcWeapons(this.props.ship, this.state.range * this.state.maxRange) }); - } - - /** - * Set the updated weapons state - * @param {Object} nextProps Incoming/Next properties - * @param {Object} nextContext Incoming/Next conext - * @return {boolean} Returns true if the component should be rerendered - */ - componentWillReceiveProps(nextProps, nextContext) { - if (nextProps.code != this.props.code) { - this.setState({ weapons: this._calcWeapons(nextProps.ship, this.state.range * this.state.maxRange) }); - } - return true; - } - - /** - * Calculate the damage received by a ship - * @param {Object} ship The ship which will receive the damage - * @param {Object} range The engagement range - * @return {boolean} Returns the per-weapon damage - */ - _calcWeapons(ship, range) { - // Tidy up the range so that it's to 4 decimal places - range = Math.round(10000 * range) / 10000; - - let weapons = []; - for (let grp in Modules.hardpoints) { - if (Modules.hardpoints[grp][0].damage && Modules.hardpoints[grp][0].damagedist) { - for (let mId in Modules.hardpoints[grp]) { - const m = new Module(Modules.hardpoints[grp][mId]); - let dropoff = 1; - if (m.getFalloff()) { - // Calculate the dropoff % due to range - if (range > m.getRange()) { - // Weapon is out of range - dropoff = 0; - } else { - const falloff = m.getFalloff(); - if (range > falloff) { - const dropoffRange = m.getRange() - falloff; - // Assuming straight-line falloff - dropoff = 1 - (range - falloff) / dropoffRange; - } - } - } - const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; - - // Base DPS - const baseDps = m.getDps() * dropoff; - const baseSDps = m.getClip() ? ((m.getClip() * baseDps / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) * dropoff : baseDps; - - // Effective DPS taking in to account shield resistance - let effectivenessShields = 0; - if (m.getDamageDist().E) { - effectivenessShields += m.getDamageDist().E * (1 - ship.shieldExplRes); - } - if (m.getDamageDist().K) { - effectivenessShields += m.getDamageDist().K * (1 - ship.shieldKinRes); - } - if (m.getDamageDist().T) { - effectivenessShields += m.getDamageDist().T * (1 - ship.shieldThermRes); - } - if (m.getDamageDist().A) { - effectivenessShields += m.getDamageDist().A; - } - effectivenessShields *= dropoff; - const effectiveDpsShields = baseDps * effectivenessShields; - const effectiveSDpsShields = baseSDps * effectivenessShields; - - // Effective DPS taking in to account hull hardness and resistance - let effectivenessHull = 0; - if (m.getDamageDist().E) { - effectivenessHull += m.getDamageDist().E * (1 - ship.hullExplRes); - } - if (m.getDamageDist().K) { - effectivenessHull += m.getDamageDist().K * (1 - ship.hullKinRes); - } - if (m.getDamageDist().T) { - effectivenessHull += m.getDamageDist().T * (1 - ship.hullThermRes); - } - if (m.getDamageDist().A) { - effectivenessHull += m.getDamageDist().A; - } - effectivenessHull *= Math.min(m.getPiercing() / ship.hardness, 1) * dropoff; - const effectiveDpsHull = baseDps * effectivenessHull; - const effectiveSDpsHull = baseSDps * effectivenessHull; - - weapons.push({ id: m.id, - classRating, - name: m.name || m.grp, - mount: m.mount, - effectiveDpsShields, - effectiveSDpsShields, - effectivenessShields, - effectiveDpsHull, - effectiveSDpsHull, - effectivenessHull }); - } - } - } - - return weapons; - } - - /** - * Triggered when the collapse or expand icons are clicked - */ - _onCollapseExpand() { - this.setState({ expanded: !this.state.expanded }); - } - - /** - * Set the sort order and sort - * @param {string} predicate Sort predicate - */ - _sortOrder(predicate) { - let desc = this.state.desc; - - if (predicate == this.state.predicate) { - desc = !desc; - } else { - desc = true; - } - - this._sort(this.props.ship, predicate, desc); - this.setState({ predicate, desc }); - } - - /** - * Sorts the weapon list - * @param {Ship} ship Ship instance - * @param {string} predicate Sort predicate - * @param {Boolean} desc Sort order descending - */ - _sort(ship, predicate, desc) { - let comp = weaponComparator.bind(null, this.context.language.translate); - - switch (predicate) { - case 'n': comp = comp(null, desc); break; - case 'edpss': comp = comp((a, b) => a.effectiveDpsShields - b.effectiveDpsShields, desc); break; - case 'esdpss': comp = comp((a, b) => a.effectiveSDpsShields - b.effectiveSDpsShields, desc); break; - case 'es': comp = comp((a, b) => a.effectivenessShields - b.effectivenessShields, desc); break; - case 'edpsh': comp = comp((a, b) => a.effectiveDpsHull - b.effectiveDpsHull, desc); break; - case 'esdpsh': comp = comp((a, b) => a.effectiveSDpsHull - b.effectiveSDpsHull, desc); break; - case 'eh': comp = comp((a, b) => a.effectivenessHull - b.effectivenessHull, desc); break; - } - - this.state.weapons.sort(comp); - } - - /** - * Render individual rows for weapons - * @param {Function} translate Translate function - * @param {Object} formats Localised formats map - * @return {array} The individual rows - * - */ - _renderRows(translate, formats) { - const { termtip, tooltip } = this.context; - - let rows = []; - - for (let i = 0; i < this.state.weapons.length; i++) { - const weapon = this.state.weapons[i]; - rows.push( - - {weapon.mount == 'F' ? : null} - {weapon.mount == 'G' ? : null} - {weapon.mount == 'T' ? : null} - {weapon.classRating} {translate(weapon.name)} - - {formats.round1(weapon.effectiveDpsShields)} - {formats.round1(weapon.effectiveSDpsShields)} - {formats.pct(weapon.effectivenessShields)} - {formats.round1(weapon.effectiveDpsHull)} - {formats.round1(weapon.effectiveSDpsHull)} - {formats.pct(weapon.effectivenessHull)} - ); - } - return rows; - } - - /** - * Update current range - * @param {number} range Range 0-1 - */ - _rangeChange(range) { - this.setState({ range, weapons: this._calcWeapons(this.props.ship, this.state.range * this.state.maxRange) }); - } - - /** - * Render damage received - * @return {React.Component} contents - */ - render() { - const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; - const { formats, translate, units } = language; - const { expanded, maxRange, range } = this.state; - - const sortOrder = this._sortOrder; - const onCollapseExpand = this._onCollapseExpand; - - return ( - -

{translate('damage received from')} {expanded ? : }

- {expanded ? - - - - - - - - - - - - - - - - - - {this._renderRows(translate, formats)} - -
{translate('weapon')}{translate('against shields')}{translate('against hull')}
{translate('DPS')}{translate('SDPS')}{translate('effectiveness')}{translate('DPS')}{translate('SDPS')}{translate('effectiveness')}
- - - - - - - - -
{translate('engagement range')} - - - {formats.f2(range * maxRange / 1000)}{units.km} -
: null } -
- ); - } -} diff --git a/src/app/components/DefenceSummary.jsx b/src/app/components/DefenceSummary.jsx deleted file mode 100644 index 93b230b0..00000000 --- a/src/app/components/DefenceSummary.jsx +++ /dev/null @@ -1,122 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import TranslatedComponent from './TranslatedComponent'; -import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons'; - -/** - * Defence summary - */ -export default class DefenceSummary extends TranslatedComponent { - static propTypes = { - ship: React.PropTypes.object.isRequired - }; - - /** - * Constructor - * @param {Object} props React Component properties - */ - constructor(props) { - super(props); - } - - /** - * Render defence summary - * @return {React.Component} contents - */ - render() { - let ship = this.props.ship; - let { language, tooltip, termtip } = this.context; - let { formats, translate, units } = language; - let hide = tooltip.bind(null, null); - - const shieldGenerator = ship.findShieldGenerator(); - - // Damage values are 1 - resistance values - return ( - -

{translate('defence summary')}

- - - {ship.shield ? - - - : null } - {ship.shield ? - - - - - - : null } - {ship.shield ? - - - - - - : null } - {ship.shield ? - - - - - - : null } - - { ship.shield && ship.shieldCells ? - - - : null } - - - - - - - - - - - - {ship.modulearmour > 0 ? - - - : null } - - {ship.moduleprotection > 0 ? - - - - : null } - -

{translate('shields')}: {formats.int(ship.shield)} {units.MJ}

{translate('recovery')}{formats.time(ship.calcShieldRecovery())}{translate('recharge')}{formats.time(ship.calcShieldRecharge())}
{translate('damage from')} -   - {formats.pct1(1 - ship.shieldExplRes)} - -   - {formats.pct1(1 - ship.shieldKinRes)} - -   - {formats.pct1(1 - ship.shieldThermRes)} -
{translate('total effective shield')} -   - {formats.int((ship.shield + ship.shieldCells) / (1 - ship.shieldExplRes))}{units.MJ} - -   - {formats.int((ship.shield + ship.shieldCells) / (1 - ship.shieldKinRes))}{units.MJ} - -   - {formats.int((ship.shield + ship.shieldCells) / (1 - ship.shieldThermRes))}{units.MJ} -

{translate('shield cells')}: {formats.int(ship.shieldCells)} {units.MJ}

{translate('armour')}: {formats.int(ship.armour)}

{translate('damage from')} -   - {formats.pct1(1 - ship.hullExplRes)} -   - {formats.pct1(1 - ship.hullKinRes)} - -   - {formats.pct1(1 - ship.hullThermRes)} -

{translate('module armour')}: {formats.int(ship.modulearmour)}

{translate('internal protection')} {formats.pct1(ship.moduleprotection)}{translate('external protection')} {formats.pct1(ship.moduleprotection / 2)}
-
- ); - } -} diff --git a/src/app/components/EngineProfile.jsx b/src/app/components/EngineProfile.jsx index 8c9ddf0f..8b90165f 100644 --- a/src/app/components/EngineProfile.jsx +++ b/src/app/components/EngineProfile.jsx @@ -1,7 +1,6 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import { Ships } from 'coriolis-data/dist'; -import ShipSelector from './ShipSelector'; import { nameComparator } from '../utils/SlotFunctions'; import LineChart from '../components/LineChart'; import Slider from '../components/Slider'; diff --git a/src/app/components/FSDProfile.jsx b/src/app/components/FSDProfile.jsx index 81548632..c3e7bb83 100644 --- a/src/app/components/FSDProfile.jsx +++ b/src/app/components/FSDProfile.jsx @@ -1,7 +1,6 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import { Ships } from 'coriolis-data/dist'; -import ShipSelector from './ShipSelector'; import { nameComparator } from '../utils/SlotFunctions'; import LineChart from '../components/LineChart'; import Slider from '../components/Slider'; diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index bca5ecbc..6ac44e65 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -55,7 +55,7 @@ export default class HardpointSlot extends Slot { modTT = (
{modTT}
- {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)} + {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
); } diff --git a/src/app/components/HardpointsSlotSection.jsx b/src/app/components/HardpointSlotSection.jsx similarity index 98% rename from src/app/components/HardpointsSlotSection.jsx rename to src/app/components/HardpointSlotSection.jsx index 51bb79d4..5d697476 100644 --- a/src/app/components/HardpointsSlotSection.jsx +++ b/src/app/components/HardpointSlotSection.jsx @@ -8,7 +8,7 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions'; /** * Hardpoint slot section */ -export default class HardpointsSlotSection extends SlotSection { +export default class HardpointSlotSection extends SlotSection { /** * Constructor diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index 562f7555..32916038 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -34,7 +34,7 @@ export default class InternalSlot extends Slot { modTT = (
{modTT}
- {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)} + {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
); } @@ -52,14 +52,16 @@ export default class InternalSlot extends Slot { { m.bays ?
{translate('bays')}: {m.bays}
: null } { m.rebuildsperbay ?
{translate('rebuildsperbay')}: {m.rebuildsperbay}
: null } { m.rate ?
{translate('rate')}: {m.rate}{u.kgs}   {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}
: null } - { m.getAmmo() ?
{translate('ammunition')}: {formats.gen(m.getAmmo())}
: null } - { m.cells ?
{translate('cells')}: {m.cells}
: null } - { m.getShieldReinforcement() ?
{translate('shieldreinforcement')}: {formats.int(m.getShieldReinforcement())} MJ   {translate('total')}: {formats.int(m.cells * m.getShieldReinforcement())}{u.MJ}
: null } + { m.getAmmo() && m.grp !== 'scb' ?
{translate('ammunition')}: {formats.gen(m.getAmmo())}
: null } + { m.getSpinup() ?
{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}
: null } + { m.getDuration() ?
{translate('duration')}: {formats.f1(m.getDuration())}{u.s}
: null } + { m.grp === 'scb' ?
{translate('cells')}: {formats.int(m.getAmmo() + 1)}
: null } + { m.getShieldReinforcement() ?
{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}
: null } + { m.getShieldReinforcement() ?
{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}
: null } { m.repair ?
{translate('repair')}: {m.repair}
: null } { m.getFacingLimit() ?
{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°
: null } { m.getRange() ?
{translate('range')} {formats.f2(m.getRange())}{u.km}
: null } { m.getRangeT() ?
{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}
: null } - { m.getSpinup() ?
{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}
: null } { m.getTime() ?
{translate('time')}: {formats.time(m.getTime())}
: null } { m.maximum ?
{translate('max')}: {(m.maximum)}
: null } { m.rangeLS ?
{translate('range')}: {m.rangeLS}{u.Ls}
: null } diff --git a/src/app/components/JumpRange.jsx b/src/app/components/JumpRange.jsx index 7fc28c36..d1421ec1 100644 --- a/src/app/components/JumpRange.jsx +++ b/src/app/components/JumpRange.jsx @@ -1,7 +1,6 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import { Ships } from 'coriolis-data/dist'; -import ShipSelector from './ShipSelector'; import { nameComparator } from '../utils/SlotFunctions'; import LineChart from '../components/LineChart'; import Slider from '../components/Slider'; diff --git a/src/app/components/Modification.jsx b/src/app/components/Modification.jsx index 15cf5a44..4212e3e0 100644 --- a/src/app/components/Modification.jsx +++ b/src/app/components/Modification.jsx @@ -37,7 +37,7 @@ export default class Modification extends TranslatedComponent { const name = this.props.name; let scaledValue = Math.round(Number(value) * 100); - // Limit to +1000% / -100% + // Limit to +1000% / -99.99% if (scaledValue > 100000) { scaledValue = 100000; value = 1000; @@ -52,6 +52,9 @@ export default class Modification extends TranslatedComponent { ship.setModification(m, name, scaledValue, true); this.setState({ value }); + } + + _updateFinished() { this.props.onChange(); } @@ -79,7 +82,7 @@ export default class Modification extends TranslatedComponent { } return ( -
+
{translate(name, m.grp)}{symbol}
diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx index 6486d951..1e657e79 100644 --- a/src/app/components/ModificationsMenu.jsx +++ b/src/app/components/ModificationsMenu.jsx @@ -15,6 +15,7 @@ export default class ModificationsMenu extends TranslatedComponent { static propTypes = { ship: React.PropTypes.object.isRequired, m: React.PropTypes.object.isRequired, + marker: React.PropTypes.string.isRequired, onChange: React.PropTypes.func.isRequired }; @@ -25,7 +26,6 @@ export default class ModificationsMenu extends TranslatedComponent { */ constructor(props, context) { super(props); - this.state = this._initState(props, context); this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this); this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this); @@ -34,33 +34,59 @@ export default class ModificationsMenu extends TranslatedComponent { this._rollBest = this._rollBest.bind(this); this._rollExtreme = this._rollExtreme.bind(this); this._reset = this._reset.bind(this); + + this.state = { + blueprintMenuOpened: false, + specialMenuOpened: false + }; } /** - * Initialise state - * @param {Object} props React Component properties - * @param {Object} context React Component context + * Render the blueprints + * @param {Object} props React component properties + * @param {Object} context React component context * @return {Object} list: Array of React Components */ - _initState(props, context) { - let { m } = props; + _renderBlueprints(props, context) { + const { m } = props; const { language, tooltip, termtip } = context; const translate = language.translate; - // Set up the blueprints - let blueprints = []; + const blueprints = []; for (const blueprintName in Modifications.modules[m.grp].blueprints) { - for (const grade of Modifications.modules[m.grp].blueprints[blueprintName]) { + const blueprint = getBlueprint(blueprintName, m); + let blueprintGrades = []; + for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) { + // Grade is a string in the JSON so make it a number + grade = Number(grade); + const classes = cn('c', { + active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade + }); const close = this._blueprintSelected.bind(this, blueprintName, grade); const key = blueprintName + ':' + grade; - const blueprint = getBlueprint(blueprintName, m); - const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade].features); - blueprints.push(
{translate(blueprint.name + ' grade ' + grade)}
); + const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp); + blueprintGrades.unshift(
  • {grade}
  • ); + } + if (blueprintGrades) { + blueprints.push(
    {translate(blueprint.name)}
    ); + blueprints.push(); } } + return blueprints; + } - // Set up the special effects - let specials = []; + /** + * Render the specials + * @param {Object} props React component properties + * @param {Object} context React component context + * @return {Object} list: Array of React Components + */ + _renderSpecials(props, context) { + const { m } = props; + const { language, tooltip, termtip } = context; + const translate = language.translate; + + const specials = []; if (Modifications.modules[m.grp].specials && Modifications.modules[m.grp].specials.length > 0) { const close = this._specialSelected.bind(this, null); specials.push(
    {translate('PHRASE_NO_SPECIAL')}
    ); @@ -69,24 +95,17 @@ export default class ModificationsMenu extends TranslatedComponent { specials.push(
    {translate(Modifications.specials[specialName].name)}
    ); } } - - // Set up the modifications - const modifications = this._setModifications(props); - - const blueprintMenuOpened = false; - const specialMenuOpened = false; - - return { blueprintMenuOpened, blueprints, modifications, specialMenuOpened, specials }; + return specials; } /** - * Initialise the modifications + * Render the modifications * @param {Object} props React Component properties * @return {Object} list: Array of React Components */ - _setModifications(props) { + _renderModifications(props) { const { m, onChange, ship } = props; - let modifications = []; + const modifications = []; for (const modName of Modifications.modules[m.grp].modifications) { if (!Modifications.modifications[modName].hidden) { const key = modName + (m.getModValue(modName) / 100 || 0); @@ -110,13 +129,13 @@ export default class ModificationsMenu extends TranslatedComponent { * @param {int} grade The grade of the selected blueprint */ _blueprintSelected(fdname, grade) { + this.context.tooltip(null); const { m } = this.props; const blueprint = getBlueprint(fdname, m); blueprint.grade = grade; m.blueprint = blueprint; - const blueprintMenuOpened = false; - this.setState({ blueprintMenuOpened }); + this.setState({ blueprintMenuOpened: false }); this.props.onChange(); } @@ -133,6 +152,7 @@ export default class ModificationsMenu extends TranslatedComponent { * @param {int} special The name of the selected special */ _specialSelected(special) { + this.context.tooltip(null); const { m, ship } = this.props; if (m.blueprint) { @@ -146,8 +166,7 @@ export default class ModificationsMenu extends TranslatedComponent { ship.recalculateEps(); } - const specialMenuOpened = false; - this.setState({ specialMenuOpened, modifications: this._setModifications(this.props) }); + this.setState({ specialMenuOpened: false }); this.props.onChange(); } @@ -179,7 +198,7 @@ export default class ModificationsMenu extends TranslatedComponent { let value = features[featureName][0]; this._setRollResult(ship, m, featureName, value); } - this.setState({ modifications: this._setModifications(this.props) }); + this.props.onChange(); } @@ -194,7 +213,7 @@ export default class ModificationsMenu extends TranslatedComponent { let value = features[featureName][0] + (Math.random() * (features[featureName][1] - features[featureName][0])); this._setRollResult(ship, m, featureName, value); } - this.setState({ modifications: this._setModifications(this.props) }); + this.props.onChange(); } @@ -208,7 +227,7 @@ export default class ModificationsMenu extends TranslatedComponent { let value = features[featureName][1]; this._setRollResult(ship, m, featureName, value); } - this.setState({ modifications: this._setModifications(this.props) }); + this.props.onChange(); } @@ -239,7 +258,7 @@ export default class ModificationsMenu extends TranslatedComponent { this._setRollResult(ship, m, featureName, value); } - this.setState({ modifications: this._setModifications(this.props) }); + this.props.onChange(); } @@ -251,7 +270,6 @@ export default class ModificationsMenu extends TranslatedComponent { ship.clearModifications(m); ship.clearBlueprint(m); - this.setState({ modifications: this._setModifications(this.props) }); this.props.onChange(); } @@ -279,7 +297,7 @@ export default class ModificationsMenu extends TranslatedComponent { if (m.blueprint && !isEmpty(m.blueprint)) { blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; haveBlueprint = true; - blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features); + blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp); } let specialLabel; @@ -289,8 +307,10 @@ export default class ModificationsMenu extends TranslatedComponent { specialLabel = translate('PHRASE_SELECT_SPECIAL'); } + const specials = this._renderSpecials(this.props, this.context); + const showBlueprintsMenu = blueprintMenuOpened; - const showSpecial = haveBlueprint && this.state.specials.length > 0 && !blueprintMenuOpened; + const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened; const showSpecialsMenu = specialMenuOpened; const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened; const showReset = !blueprintMenuOpened && !specialMenuOpened; @@ -302,12 +322,12 @@ export default class ModificationsMenu extends TranslatedComponent { onClick={(e) => e.stopPropagation() } onContextMenu={stopCtxPropagation} > - { haveBlueprint ? + { showBlueprintsMenu ? '' : haveBlueprint ?
    {blueprintLabel}
    :
    {translate('PHRASE_SELECT_BLUEPRINT')}
    } - { showBlueprintsMenu ? this.state.blueprints : null } + { showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null } { showSpecial ?
    {specialLabel}
    : null } - { showSpecialsMenu ? this.state.specials : null } + { showSpecialsMenu ? specials : null } { showRolls || showReset ? @@ -327,7 +347,7 @@ export default class ModificationsMenu extends TranslatedComponent {
    : null } { showMods ? - { this.state.modifications } + { this._renderModifications(this.props) } : null }
    ); diff --git a/src/app/components/MovementSummary.jsx b/src/app/components/MovementSummary.jsx deleted file mode 100644 index f4c268f1..00000000 --- a/src/app/components/MovementSummary.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import TranslatedComponent from './TranslatedComponent'; - -/** - * Movement summary - */ -export default class MovementSummary extends TranslatedComponent { - static propTypes = { - ship: React.PropTypes.object.isRequired - }; - - /** - * Constructor - * @param {Object} props React Component properties - */ - constructor(props) { - super(props); - } - - /** - * Render movement summary - * @return {React.Component} contents - */ - render() { - let ship = this.props.ship; - let { language, tooltip, termtip } = this.context; - let { formats, translate, units } = language; - let boostMultiplier = ship.topBoost / ship.topSpeed; - - return ( - -

    {translate('movement summary')}

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     {translate('engine pips')}
     012344B
    {translate('speed')} ({units['m/s']}){formats.int(ship.speeds[0])}{formats.int(ship.speeds[1])}{formats.int(ship.speeds[2])}{formats.int(ship.speeds[3])}{formats.int(ship.speeds[4])}{formats.int(ship.speeds[4] * boostMultiplier)}
    {translate('pitch')} ({units['°/s']}){formats.int(ship.pitches[0])}{formats.int(ship.pitches[1])}{formats.int(ship.pitches[2])}{formats.int(ship.pitches[3])}{formats.int(ship.pitches[4])}{formats.int(ship.pitches[4] * boostMultiplier)}
    {translate('roll')} ({units['°/s']}){formats.int(ship.rolls[0])}{formats.int(ship.rolls[1])}{formats.int(ship.rolls[2])}{formats.int(ship.rolls[3])}{formats.int(ship.rolls[4])}{formats.int(ship.rolls[4] * boostMultiplier)}
    {translate('yaw')} ({units['°/s']}){formats.int(ship.yaws[0])}{formats.int(ship.yaws[1])}{formats.int(ship.yaws[2])}{formats.int(ship.yaws[3])}{formats.int(ship.yaws[4])}{formats.int(ship.yaws[4] * boostMultiplier)}
    -
    - ); - } -} diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx index 4f502c0f..f67f721f 100644 --- a/src/app/components/Offence.jsx +++ b/src/app/components/Offence.jsx @@ -248,11 +248,13 @@ export default class Offence extends TranslatedComponent {

    {translate('offence metrics')}

    {translate('PHRASE_TIME_TO_DRAIN_WEP')}
    {timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}

    +

    {translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}
    {formats.f1(totalShieldsSDps)}

    {translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}
    {timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}

    +

    {translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}
    {formats.f1(totalArmourSDps)}

    {translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}
    {timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}

    -

    {translate('shield damage sources')}

    +

    {translate('shield damage sources')}

    diff --git a/src/app/components/OffenceSummary.jsx b/src/app/components/OffenceSummary.jsx deleted file mode 100644 index d7778a70..00000000 --- a/src/app/components/OffenceSummary.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import TranslatedComponent from './TranslatedComponent'; -import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons'; - -/** - * Offence summary - */ -export default class OffenceSummary extends TranslatedComponent { - static propTypes = { - ship: React.PropTypes.object.isRequired - }; - - /** - * Constructor - * @param {Object} props React Component properties - */ - constructor(props) { - super(props); - } - - /** - * Render offence summary - * @return {React.Component} contents - */ - render() { - let ship = this.props.ship; - let { language, tooltip, termtip } = this.context; - let { formats, translate } = language; - - return ( - -

    {translate('offence summary')}

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    {translate('dps')}: {formats.f1(ship.totalDps)}

    {translate('damage by')} {formats.f1(ship.totalAbsDps)} {formats.f1(ship.totalExplDps)} {formats.f1(ship.totalKinDps)} {formats.f1(ship.totalThermDps)}

    {translate('sdps')}: {formats.f1(ship.totalSDps)}

    {translate('damage by')} {formats.f1(ship.totalAbsSDps)} {formats.f1(ship.totalExplSDps)} {formats.f1(ship.totalKinSDps)} {formats.f1(ship.totalThermSDps)}

    {translate('dpe')}: {formats.f1(ship.totalDpe)}

    {translate('damage by')} {formats.f1(ship.totalAbsDpe)} {formats.f1(ship.totalExplDpe)} {formats.f1(ship.totalKinDpe)} {formats.f1(ship.totalThermDpe)}
    -
    - ); - } -} diff --git a/src/app/components/Pips.jsx b/src/app/components/Pips.jsx index 29051318..08102c40 100644 --- a/src/app/components/Pips.jsx +++ b/src/app/components/Pips.jsx @@ -1,7 +1,6 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import { Ships } from 'coriolis-data/dist'; -import ShipSelector from './ShipSelector'; import { nameComparator } from '../utils/SlotFunctions'; import { Pip } from './SvgIcons'; import LineChart from '../components/LineChart'; diff --git a/src/app/components/PowerBands.jsx b/src/app/components/PowerBands.jsx index ad2ebcaf..fc9a1d7a 100644 --- a/src/app/components/PowerBands.jsx +++ b/src/app/components/PowerBands.jsx @@ -189,7 +189,7 @@ export default class PowerBands extends TranslatedComponent { let { f2, pct1 } = formats; // wattFmt, pctFmt let { available, bands } = props; let { innerWidth, ret, dep } = state; - let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum * 2 >= available }); + let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum > available * 0.4 }); let deployed = []; let retracted = []; let retSelected = Object.keys(ret).length > 0; @@ -268,7 +268,7 @@ export default class PowerBands extends TranslatedComponent { axis.call(this.pctAxis); axis.select('g:nth-child(6)').selectAll('line, text').attr('class', pwrWarningClass); }} className='pct axis' transform={`translate(0,${state.innerHeight})`}> - + {translate('ret')} {translate('dep')} {f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))}) diff --git a/src/app/components/ShipSelector.jsx b/src/app/components/ShipSelector.jsx deleted file mode 100644 index a48ba4a8..00000000 --- a/src/app/components/ShipSelector.jsx +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { Ships } from 'coriolis-data/dist'; -import TranslatedComponent from './TranslatedComponent'; -import { Rocket } from './SvgIcons'; - -/** - * Selector for ships - */ -export default class ShipSelector extends TranslatedComponent { - static propTypes = { - initial: React.PropTypes.object.isRequired, - onChange: React.PropTypes.func.isRequired - }; - - /** - * Constructor - * @param {Object} props React Component properties - */ - constructor(props) { - super(props); - - this.state = { ship : this.props.initial }; - } - - /** - * Generate the ships menu - * @return {React.Component} Menu - */ - _getShipsMenu() { - const _selectShip = this._selectShip; - - let shipList = []; - - for (let s in Ships) { - shipList.push(
    {Ships[s].properties.name}
    ); - } - - return shipList; - } - - /** - * Handle opening the menu - * @param {string} menu The ID of the opened menu - * @param {SyntheticEvent} event Event - */ - _openMenu(menu, event) { - event.stopPropagation(); - if (this.props.currentMenu == menu) { - menu = null; - } - - this.context.openMenu(menu); - } - - /** - * Handle selection of a ship - * @param {string} s The selected ship ID - */ - _selectShip(s) { - this.setState({ ship: Ships[s] }); - - this.context.openMenu(null); - this.props.onChange(s); - } - - /** - * Render ship selector - * @return {React.Component} contents - */ - render() { - const currentMenu = this.props.currentMenu; - const ship = this.state.ship; - - return ( -
    -
    -
    - {ship.properties.name} - {currentMenu == 'wds' ? -
    e.stopPropagation() }> - {this._getShipsMenu()} -
    : null } -
    -
    -
    - ); - } -} diff --git a/src/app/components/Slot.jsx b/src/app/components/Slot.jsx index 9959a16f..03964d35 100644 --- a/src/app/components/Slot.jsx +++ b/src/app/components/Slot.jsx @@ -79,7 +79,7 @@ export default class Slot extends TranslatedComponent { let language = this.context.language; let translate = language.translate; let { ship, m, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props; - let slotDetails, menu; + let slotDetails, modificationsMarker, menu; if (!selected) { // If not selected then sure that modifications flag is unset @@ -88,8 +88,10 @@ export default class Slot extends TranslatedComponent { if (m) { slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes + modificationsMarker = JSON.stringify(m); } else { slotDetails =
    {translate(eligible ? 'emptyrestricted' : 'empty')}
    ; + modificationsMarker = ''; } if (selected) { @@ -99,6 +101,7 @@ export default class Slot extends TranslatedComponent { onChange={onChange} ship={ship} m={m} + marker={modificationsMarker} />; } else { menu =
    {modTT}
    - {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)} + {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
    ); } @@ -67,6 +67,8 @@ export default class StandardSlot extends TranslatedComponent { this._modificationsSelected = false; } + const modificationsMarker = JSON.stringify(m); + if (selected) { if (this._modificationsSelected) { menu = ; } else { menu = {translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T} : null } { m.getOptMass() ?
    {translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}
    : null } { m.getMaxMass() ?
    {translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}
    : null } + { m.getOptMul() ?
    {translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}
    : null } { m.getRange() ?
    {translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}
    : null } { m.time ?
    {translate('time')}: {formats.time(m.time)}
    : null } { m.getThermalEfficiency() ?
    {translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}
    : null } diff --git a/src/app/components/VerticalBarChart.jsx b/src/app/components/VerticalBarChart.jsx index b8a709cf..1f2ecb94 100644 --- a/src/app/components/VerticalBarChart.jsx +++ b/src/app/components/VerticalBarChart.jsx @@ -98,8 +98,10 @@ const ValueLabel = React.createClass({ render() { const { x, y, payload, value } = this.props; + const em = value < 1000 ? '1em' : value < 1000 ? '0.8em' : '0.7em'; + return ( - {value} + {value} ); } }); diff --git a/src/app/components/WeaponDamageChart.jsx b/src/app/components/WeaponDamageChart.jsx index 9c04fbef..a2c407b0 100644 --- a/src/app/components/WeaponDamageChart.jsx +++ b/src/app/components/WeaponDamageChart.jsx @@ -1,7 +1,6 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import { Ships } from 'coriolis-data/dist'; -import ShipSelector from './ShipSelector'; import { nameComparator } from '../utils/SlotFunctions'; import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons'; import LineChart from '../components/LineChart'; diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index 5c68a1f8..6a425f6d 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -70,6 +70,9 @@ export const terms = { TT_EFFECTIVE_SDPS_ARMOUR: 'Actual sustained DPS whilst WEP capacitor is not empty', TT_EFFECTIVENESS_ARMOUR: 'Effectivness compared to hitting a 0-resistance target at 0m', + PHRASE_EFFECTIVE_SDPS_SHIELDS: 'SDPS against shields', + PHRASE_EFFECTIVE_SDPS_ARMOUR: 'SDPS against armour', + TT_SUMMARY_SPEED: 'With full fuel tank and 4 pips to ENG', TT_SUMMARY_SPEED_NONFUNCTIONAL: 'Thrusters powered off or over maximum mass', TT_SUMMARY_BOOST: 'With full fuel tank and 4 pips to ENG', @@ -251,6 +254,18 @@ export const terms = { minmul_sg: 'Minimum strength', optmul_sg: 'Optimal strength', maxmul_sg: 'Minimum strength', + minmass_psg: 'Minimum hull mass', + optmass_psg: 'Optimal hull mass', + maxmass_psg: 'Maximum hull mass', + minmul_psg: 'Minimum strength', + optmul_psg: 'Optimal strength', + maxmul_psg: 'Minimum strength', + minmass_bsg: 'Minimum hull mass', + optmass_bsg: 'Optimal hull mass', + maxmass_bsg: 'Maximum hull mass', + minmul_bsg: 'Minimum strength', + optmul_bsg: 'Optimal strength', + maxmul_bsg: 'Minimum strength', range_s: 'Typical emission range', diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 18dae989..cc3aec70 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -14,7 +14,7 @@ import { FloppyDisk, Bin, Switch, Download, Reload, LinkIcon, ShoppingIcon } fro import LZString from 'lz-string'; import ShipSummaryTable from '../components/ShipSummaryTable'; import StandardSlotSection from '../components/StandardSlotSection'; -import HardpointsSlotSection from '../components/HardpointsSlotSection'; +import HardpointSlotSection from '../components/HardpointSlotSection'; import InternalSlotSection from '../components/InternalSlotSection'; import UtilitySlotSection from '../components/UtilitySlotSection'; import Pips from '../components/Pips'; @@ -574,7 +574,7 @@ export default class OutfittingPage extends Page { - + {/* Control of ship and opponent */} diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js index 3b806710..ad040e0c 100644 --- a/src/app/shipyard/Calculations.js +++ b/src/app/shipyard/Calculations.js @@ -341,7 +341,9 @@ export function shieldMetrics(ship, sys) { } // Calculate diminishing returns for boosters - boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5); + // Diminishing returns not currently in-game + //boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5); + // Remove base shield generator strength boost -= 1; // Apply diminishing returns diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 2ea29321..37984d85 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -48,7 +48,7 @@ export default class Module { // this special effect modifies our returned value const modification = Modifications.modifications[name]; if (modification.method === 'additive') { - result = result + modifierActions[name]; + result = result + modifierActions[name] * 100; } else if (modification.method === 'overwrite') { result = modifierActions[name]; } else { @@ -680,14 +680,6 @@ export default class Module { return this._getModifiedValue('rebuildsperbay'); } - /** - * Get the cells for this module, taking in to account modifications - * @return {Number} the cells for this module - */ - getCells() { - return this._getModifiedValue('cells'); - } - /** * Get the jitter for this module, taking in to account modifications * @return {Number} the jitter for this module diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index cfa9a1f4..d622254e 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -221,39 +221,6 @@ export default class Ship { return Calc.calcYaw(this.unladenMass + fuel + cargo, this.yaw, this.standard[1].m, this.pipSpeed, eng, this.topBoost / this.topSpeed, boost); } - /** - * Calculate the recovery time after losing or turning on shields - * Thanks to CMDRs Al Gray, GIF, and Nomad Enigma for providing Shield recharge data and formulas - * - * @return {Number} Recovery time in seconds - */ - calcShieldRecovery() { - const shieldGenerator = this.findShieldGenerator(); - if (shieldGenerator) { - const brokenRegenRate = shieldGenerator.getBrokenRegenerationRate(); - // 50% of shield strength / broken recharge rate + 15 second delay before recharge starts - return ((this.shield / 2) / brokenRegenRate) + 15; - } - return 0; - } - - /** - * Calculate the recharge time for a shield going from 50% to 100% - * Thanks to CMDRs Al Gray, GIF, and Nomad Enigma for providing Shield recharge data and formulas - * - * @return {Number} 50 - 100% Recharge time in seconds - */ - calcShieldRecharge() { - const shieldGenerator = this.findShieldGenerator(); - if (shieldGenerator) { - const regenRate = shieldGenerator.getRegenerationRate(); - - // 50% of shield strength / recharge rate - return (this.shield / 2) / regenRate; - } - return 0; - } - /** * Calculate the hypothetical shield strength for the ship using the specified parameters * @param {Object} sg [optional] Shield Generator to use @@ -1296,7 +1263,9 @@ export default class Ship { for (let slot of this.internal) { if (slot.m && slot.m.grp == 'scb') { - shieldCells += slot.m.getShieldReinforcement() * slot.m.getCells(); + // There is currently a bug with Elite where you can have a clip > 1 thanks to engineering but it doesn't do anything, + // so we need to hard-code clip to 1 + shieldCells += slot.m.getShieldReinforcement() * slot.m.getDuration() * (slot.m.getAmmo() + 1); } } diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js index 2a55a56d..eaaffaec 100644 --- a/src/app/utils/BlueprintFunctions.js +++ b/src/app/utils/BlueprintFunctions.js @@ -4,14 +4,16 @@ import { Modifications } from 'coriolis-data/dist'; /** * Generate a tooltip with details of a blueprint's effects * @param {Object} translate The translate object - * @param {Object} features The features of the blueprint + * @param {Object} blueprint The blueprint at the required grade + * @param {Array} engineers The engineers supplying this blueprint + * @param {string} grp The group of the module * @param {Object} m The module to compare with * @returns {Object} The react components */ -export function blueprintTooltip(translate, features, m) { - const results = []; - for (const feature in features) { - const featureIsBeneficial = isBeneficial(feature, features[feature]); +export function blueprintTooltip(translate, blueprint, engineers, grp, m) { + const effects = []; + for (const feature in blueprint.features) { + const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]); const featureDef = Modifications.modifications[feature]; if (!featureDef.hidden) { let symbol = ''; @@ -20,8 +22,8 @@ export function blueprintTooltip(translate, features, m) { } else if (featureDef.type === 'percentage') { symbol = '%'; } - let lowerBound = features[feature][0]; - let upperBound = features[feature][1]; + let lowerBound = blueprint.features[feature][0]; + let upperBound = blueprint.features[feature][1]; if (featureDef.type === 'percentage') { lowerBound = Math.round(lowerBound * 1000) / 10; upperBound = Math.round(upperBound * 1000) / 10; @@ -33,11 +35,13 @@ export function blueprintTooltip(translate, features, m) { let current = m.getModValue(feature); if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; } const currentIsBeneficial = isValueBeneficial(feature, current); - results.push( + effects.push( - {translate(feature)} + {translate(feature, grp)} {lowerBound}{symbol} {current}{symbol} {upperBound}{symbol} @@ -45,9 +49,9 @@ export function blueprintTooltip(translate, features, m) { ); } else { // We do not have a module, no value - results.push( + effects.push( - {translate(feature)} + {translate(feature, grp)} {lowerBound}{symbol} {upperBound}{symbol} @@ -55,21 +59,97 @@ export function blueprintTooltip(translate, features, m) { } } } + if (m) { + // Because we have a module add in any benefits that aren't part of the primary blueprint + for (const feature in m.mods) { + if (!blueprint.features[feature]) { + const featureDef = Modifications.modifications[feature]; + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let current = m.getModValue(feature); + if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + effects.push( + + {translate(feature, grp)} +   + {current}{symbol} +   + + ); + } + } + } + let components; + if (!m) { + components = []; + for (const component in blueprint.components) { + components.push( + + {translate(component)} + {blueprint.components[component]} + + ); + } + } + + let engineersList; + if (engineers) { + engineersList = []; + for (const engineer of engineers) { + engineersList.push( + + {engineer} + + ); + } + } return ( - - - - - - {m ? : null } - - - - - {results} - -
    {translate('feature')}{translate('worst')}{translate('current')}{translate('best')}
    +
    + + + + + + {m ? : null } + + + + + {effects} + +
    {translate('feature')}{translate('worst')}{translate('current')}{translate('best')}
    + { components ? + + + + + + + + {components} + +
    {translate('component')}{translate('amount')}
    : null } + { engineersList ? + + + + + + + {engineersList} + +
    {translate('engineers')}
    : null } +
    ); } @@ -112,8 +192,8 @@ export function getBlueprint(name, module) { // Start with a copy of the blueprint const blueprint = JSON.parse(JSON.stringify(Modifications.blueprints[name])); if (module) { - if (module.grp === 'bh' || module.grp === 'hr') { - // Bulkheads and hull reinforcements need to have their resistances altered by the base values + if (module.grp === 'bh' || module.grp === 'hr' || module.grp === 'sg' || module.grp === 'psg' || module.grp === 'bsg') { + // Bulkheads, hull reinforcements and shield generators need to have their resistances altered by the base values for (const grade in blueprint.grades) { for (const feature in blueprint.grades[grade].features) { if (feature === 'explres') { diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index 47a1db01..5f6df7c5 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -130,9 +130,15 @@ export function shipFromJson(json) { let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots); ship.buildWith(null); - // Set the cargo hatch. We don't have any information on it so guess it's priority 5 and disabled - ship.cargoHatch.enabled = false; - ship.cargoHatch.priority = 4; + // Set the cargo hatch + if (json.modules.CargoHatch) { + ship.cargoHatch.enabled = json.modules.CargoHatch.module.on == true; + ship.cargoHatch.priority = json.modules.CargoHatch.module.priority; + } else { + // We don't have any information on it so guess it's priority 5 and disabled + ship.cargoHatch.enabled = false; + ship.cargoHatch.priority = 4; + } // Add the bulkheads const armourJson = json.modules.Armour.module; @@ -427,4 +433,10 @@ function _addModifications(module, modifiers, blueprint, grade) { if (module.getModValue('rof')) { module.setModValue('rof', ((1 / (1 + module.getModValue('rof') / 10000)) - 1) * 10000); } + + // Clip size is rounded up so that the result is a whole number + if (module.getModValue('clip')) { + const individual = 1 / (module.clip || 1); + module.setModValue('clip', Math.ceil((module.getModValue('clip') / 10000) / individual) * individual * 10000); + } } diff --git a/src/app/utils/SlotFunctions.js b/src/app/utils/SlotFunctions.js index cca7eb1e..8a57e63e 100644 --- a/src/app/utils/SlotFunctions.js +++ b/src/app/utils/SlotFunctions.js @@ -243,7 +243,7 @@ export function diffDetails(language, m, mm) { } } - let mIntegrity = m.integrity; + let mIntegrity = m.integrity || 0; let mmIntegrity = mm ? mm.getIntegrity() || 0 : 0; if (mIntegrity != mmIntegrity) { propDiffs.push(
    {translate('integrity')}: {diff(formats.round, mIntegrity, mmIntegrity)}
    ); diff --git a/src/less/app.less b/src/less/app.less index 5c015eff..69c72d71 100755 --- a/src/less/app.less +++ b/src/less/app.less @@ -16,7 +16,6 @@ @import 'tooltip'; @import 'buttons'; @import 'error'; -@import 'shipselector'; @import 'sortable'; @import 'loader'; @import 'pips'; diff --git a/src/less/select.less b/src/less/select.less index 3fd98506..96f27a55 100755 --- a/src/less/select.less +++ b/src/less/select.less @@ -29,7 +29,7 @@ select { padding: 0.5em 0; width: 100%; margin: 0; - max-height: 400px; + max-height: 500px; overflow-y: auto; overflow-x: hidden; z-index: 0; diff --git a/src/less/shipselector.less b/src/less/shipselector.less deleted file mode 100755 index 5764bdc5..00000000 --- a/src/less/shipselector.less +++ /dev/null @@ -1,199 +0,0 @@ -.shipselector { - background-color: @bgBlack; - margin: 0; - padding: 0 0 0 1em; - height: 3em; - line-height: 3em; - font-family: @fTitle; - vertical-align: middle; - position: relative; - - .user-select-none(); - - .menu { - position: relative; - z-index: 1; - cursor: default; - - &.r { - .menu-list { - right: 0; - } - } - - .smallTablet({ - position: static; - position: initial; - }); - } - - .menu-header { - padding : 0 1em; - cursor: pointer; - color: @warning; - text-transform: uppercase; - } - - .menu-list { - font-family: @fStandard; - position: absolute; - padding: 0.5em 1em; - box-sizing: border-box; - min-width: 100%; - overflow-x: hidden; - background-color: @bgBlack; - font-size: 0.9em; - overflow-y: auto; - z-index: 0; - -webkit-overflow-scrolling: touch; - max-height: 500px; - - &::-webkit-scrollbar { - width: 0.5em; - } - - &::-webkit-scrollbar-track { - background-color: transparent; - } - - &::-webkit-scrollbar-thumb { - background-color: @warning-disabled; - } - - input { - border: none; - background-color: transparent; - text-align: right; - font-size: 1em; - font-family: @fStandard; - } - - .smallTablet({ - max-height: 400px; - left: 0; - right: 0; - border-bottom: 1px solid @bg; - }); - - - .tablet({ - li, a { - padding: 0.3em 0; - } - }); - } - - .dbl { - -webkit-column-count: 2; /* Chrome, Safari, Opera */ - -moz-column-count: 2; /* Firefox */ - column-count: 2; - ul { - min-width: 10em; - } - - .smallTablet({ - -webkit-column-count: 3; /* Chrome, Safari, Opera */ - -moz-column-count: 3; /* Firefox */ - column-count: 3; - - ul { - min-width: 20em; - } - }); - - .largePhone({ - -webkit-column-count: 2; /* Chrome, Safari, Opera */ - -moz-column-count: 2; /* Firefox */ - column-count: 2; - }); - - .smallPhone({ - -webkit-column-count: 1; /* Chrome, Safari, Opera */ - -moz-column-count: 1; /* Firefox */ - column-count: 1; - }); - } - - .quad { - -webkit-column-count: 4; /* Chrome, Safari, Opera */ - -moz-column-count: 4; /* Firefox */ - column-count: 4; - ul { - min-width: 10em; - } - - .smallTablet({ - -webkit-column-count: 3; /* Chrome, Safari, Opera */ - -moz-column-count: 3; /* Firefox */ - column-count: 3; - - ul { - min-width: 20em; - } - }); - - .largePhone({ - -webkit-column-count: 2; /* Chrome, Safari, Opera */ - -moz-column-count: 2; /* Firefox */ - column-count: 2; - }); - - .smallPhone({ - -webkit-column-count: 1; /* Chrome, Safari, Opera */ - -moz-column-count: 1; /* Firefox */ - column-count: 1; - }); - } - - ul { - display: inline-block; - white-space: nowrap; - margin: 0 0 0.5em; - padding: 0; - line-height: 1.3em; - } - - li { - white-space: normal; - list-style: none; - margin-left: 1em; - line-height: 1.1em; - } - - a { - vertical-align: middle; - color: @warning; - text-decoration: none; - - &:visited { - color: @warning; - } - .no-touch &:hover { - color: teal; - } - &.active { - color: @primary; - } - } - - hr { - border: none; - border-top: 1px solid @disabled; - } - - .no-wrap { - white-space: nowrap; - } - - .block { - display: block; - line-height: 1.5em; - } - - .title { - font-size: 1.3em; - display: inline-block; - margin:0px; - text-transform: uppercase; - } -}