From 0dc58bad7e094f298097e65c7eb2353382cb288e Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sat, 3 Dec 2016 15:52:17 +0000 Subject: [PATCH 01/35] Beta version --- env | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 env diff --git a/env b/env new file mode 100644 index 00000000..0bb2d2e4 --- /dev/null +++ b/env @@ -0,0 +1,4 @@ +CORIOLIS_UA_TRACKING=UA-87944382-1 +export CORIOLIS_UA_TRACKING +CORIOLIS_GAPI_KEY=AIzaSyAvuJC2TECa3rulzI5rTyfycYi9T28Xhwc +export CORIOLIS_GAPI_KEY diff --git a/package.json b/package.json index 3aeb579c..c84efaea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coriolis_shipyard", - "version": "2.2.5", + "version": "2.2.6b", "repository": { "type": "git", "url": "https://github.com/EDCD/coriolis" From a6a10df39c5316cd342d5f210a380556e0ef79e4 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 11 Dec 2016 21:52:49 +0000 Subject: [PATCH 02/35] Add movement summary; add standard internal class sizes to shipyard page; fix issue when importing Viper Mk IV --- ChangeLog.md | 7 + __tests__/fixtures/agility-data.json | 30 +++++ .../anaconda-test-detailed-export-v4.json | 24 ++-- __tests__/test-agility.js | 90 +++++++++++++ package.json | 1 + src/app/components/MovementSummary.jsx | 125 ++++++++++++++++++ src/app/i18n/Language.jsx | 1 + src/app/i18n/en.js | 9 +- src/app/pages/OutfittingPage.jsx | 31 +++-- src/app/pages/ShipyardPage.jsx | 22 ++- src/app/shipyard/Calculations.js | 122 ++++++++++++++--- src/app/shipyard/Ship.js | 31 +++-- src/app/utils/CompanionApiUtils.js | 2 +- 13 files changed, 435 insertions(+), 60 deletions(-) create mode 100644 __tests__/fixtures/agility-data.json create mode 100644 __tests__/test-agility.js create mode 100644 src/app/components/MovementSummary.jsx diff --git a/ChangeLog.md b/ChangeLog.md index 4d35d1c7..6b9a9582 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,10 @@ +#2.2.6 + * Add pitch/roll/yaw information + * Use combination of pitch, roll and yaw to provide a more useful agility metric + * Add movement summary to outfitting page + * Add standard internal class sizes to shipyard page + * Fix issue when importing Viper Mk IV + #2.2.5 * Calculate rate of fire for multi-burst weapons * Add note to disable ghostery in error situation diff --git a/__tests__/fixtures/agility-data.json b/__tests__/fixtures/agility-data.json new file mode 100644 index 00000000..e78f4ea5 --- /dev/null +++ b/__tests__/fixtures/agility-data.json @@ -0,0 +1,30 @@ +{ + "adder": { + "t3": {"speed": 205, "boost": 298, "pitch": 35.37, "roll": 93.09, "yaw": 13.03}, + "t2": {"speed": 209, "boost": 304, "pitch": 36.06, "roll": 94.90, "yaw": 13.29}, + "t1": {"speed": 213, "boost": 310, "pitch": 36.80, "roll": 96.84, "yaw": 13.56}, + "t0": {"speed": 218, "boost": 317, "pitch": 37.70, "roll": 99.20, "yaw": 13.89}, + "t9": {"speed": 220, "boost": 321, "pitch": 38.08, "roll": 100.21, "yaw": 14.03}, + "t8": {"speed": 225, "boost": 327, "pitch": 38.86, "roll": 102.26, "yaw": 14.32}, + "t7": {"speed": 230, "boost": 334, "pitch": 39.69, "roll": 104.44, "yaw": 14.62}, + "t6": {"speed": 234, "boost": 340, "pitch": 40.41, "roll": 106.34, "yaw": 14.89}, + "t5": {"speed": 242, "boost": 351, "pitch": 41.71, "roll": 109.78, "yaw": 15.37} + }, + "eagle": { + "t2": {"speed": 223, "boost": 325, "pitch": 46.45, "roll": 111.48, "yaw": 16.72}, + "t1": {"speed": 229, "boost": 334, "pitch": 47.69, "roll": 114.46, "yaw": 17.17}, + "t0": {"speed": 235, "boost": 343, "pitch": 49.00, "roll": 117.60, "yaw": 17.64}, + "t9": {"speed": 239, "boost": 349, "pitch": 49.80, "roll": 119.53, "yaw": 17.93}, + "t8": {"speed": 243, "boost": 355, "pitch": 50.70, "roll": 121.69, "yaw": 18.25}, + "t7": {"speed": 248, "boost": 361, "pitch": 51.62, "roll": 123.89, "yaw": 18.58}, + "t6": {"speed": 252, "boost": 367, "pitch": 52.46, "roll": 125.91, "yaw": 18.89}, + "t5": {"speed": 259, "boost": 378, "pitch": 53.99, "roll": 129.56, "yaw": 19.43} + }, + "hauler": { + "t4": {"speed": 203, "boost": 305, "pitch": 36.61, "roll": 101.71, "yaw": 14.24}, + "t3": {"speed": 209, "boost": 314, "pitch": 37.63, "roll": 104.54, "yaw": 14.64}, + "t2": {"speed": 216, "boost": 324, "pitch": 38.89, "roll": 108.03, "yaw": 15.12}, + "t1": {"speed": 222, "boost": 333, "pitch": 39.97, "roll": 111.02, "yaw": 15.54}, + "t0": {"speed": 232, "boost": 348, "pitch": 41.76, "roll": 116.00, "yaw": 16.24} + } +} diff --git a/__tests__/fixtures/anaconda-test-detailed-export-v4.json b/__tests__/fixtures/anaconda-test-detailed-export-v4.json index c925f052..b80dc53e 100644 --- a/__tests__/fixtures/anaconda-test-detailed-export-v4.json +++ b/__tests__/fixtures/anaconda-test-detailed-export-v4.json @@ -264,22 +264,21 @@ "topBoost": 248.62, "topSpeed": 186.46, "totalCost": 882362058, - "totalDpe": 127.26, - "totalDps": 97.74, + "totalDpe": 143.01, + "totalDps": 102.83, "totalEps": 22.71, "totalHps": 677.29, "totalExplDpe": 0, "totalExplDps": 0, "totalExplSDps": 0, "totalHps": 33.62, - "totalKinDpe": 103.97, - "totalKinDps": 28.92, - "totalKinSDps": 21.23, - "totalSDps": 85.77, - "totalThermDpe": 23.29, - "totalThermDps": 68.82, - "totalThermSDps": 64.53, - "agility": 2, + "totalKinDpe": 119.43, + "totalKinDps": 32.51, + "totalKinSDps": 24.79, + "totalSDps": 91.3, + "totalThermDpe": 23.58, + "totalThermDps": 70.32, + "totalThermSDps": 66.51, "baseShieldStrength": 350, "baseArmour": 945, "hullExplRes": 0.78, @@ -288,6 +287,7 @@ "hullThermRes": 1.37, "masslock": 23, "pipSpeed": 0.14, + "pitch": 25, "moduleCostMultiplier": 1, "fuelCapacity": 32, "cargoCapacity": 128, @@ -297,8 +297,10 @@ "unladenMass": 1179.2, "powerAvailable": 39.6, "powerRetracted": 23.33, - "powerDeployed": 34.76, + "powerDeployed": 34.13, + "roll": 60, "unladenRange": 18.49, + "yaw": 10, "fullTankRange": 18.12, "ladenRange": 16.39, "unladenFastestRange": 73.21, diff --git a/__tests__/test-agility.js b/__tests__/test-agility.js new file mode 100644 index 00000000..e3bd87cb --- /dev/null +++ b/__tests__/test-agility.js @@ -0,0 +1,90 @@ +import Ship from '../src/app/shipyard/Ship'; +import { Ships } from 'coriolis-data/dist'; +import * as ModuleUtils from '../src/app/shipyard/ModuleUtils'; + +describe("Agility", function() { + + it("correctly calculates speed", function() { + let agilityData = require('./fixtures/agility-data'); + + for (let shipId in agilityData) { + for (let thrusterId in agilityData[shipId]) { + const thrusterData = agilityData[shipId][thrusterId]; + let shipData = Ships[shipId]; + let ship = new Ship(shipId, shipData.properties, shipData.slots); + ship.buildWith(shipData.defaults); + ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId)); + + expect(Math.round(ship.topSpeed)).toBe(thrusterData.speed); + } + } + }); + + it("correctly calculates boost", function() { + let agilityData = require('./fixtures/agility-data'); + + for (let shipId in agilityData) { + for (let thrusterId in agilityData[shipId]) { + const thrusterData = agilityData[shipId][thrusterId]; + let shipData = Ships[shipId]; + let ship = new Ship(shipId, shipData.properties, shipData.slots); + ship.buildWith(shipData.defaults); + // Turn off internals to ensure we have enough power to boost + for (let internal in ship.internal) { + ship.internal[internal].enabled = 0; + } + ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId)); + + expect(Math.round(ship.topBoost)).toBe(thrusterData.boost); + } + } + }); + + it("correctly calculates pitch", function() { + let agilityData = require('./fixtures/agility-data'); + + for (let shipId in agilityData) { + for (let thrusterId in agilityData[shipId]) { + const thrusterData = agilityData[shipId][thrusterId]; + let shipData = Ships[shipId]; + let ship = new Ship(shipId, shipData.properties, shipData.slots); + ship.buildWith(shipData.defaults); + ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId)); + + expect(Math.round(ship.pitches[4] * 100) / 100).toBeCloseTo(thrusterData.pitch, 1); + } + } + }); + + it("correctly calculates roll", function() { + let agilityData = require('./fixtures/agility-data'); + + for (let shipId in agilityData) { + for (let thrusterId in agilityData[shipId]) { + const thrusterData = agilityData[shipId][thrusterId]; + let shipData = Ships[shipId]; + let ship = new Ship(shipId, shipData.properties, shipData.slots); + ship.buildWith(shipData.defaults); + ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId)); + + expect(Math.round(ship.rolls[4] * 100) / 100).toBeCloseTo(thrusterData.roll, 1); + } + } + }); + + it("correctly calculates yaw", function() { + let agilityData = require('./fixtures/agility-data'); + + for (let shipId in agilityData) { + for (let thrusterId in agilityData[shipId]) { + const thrusterData = agilityData[shipId][thrusterId]; + let shipData = Ships[shipId]; + let ship = new Ship(shipId, shipData.properties, shipData.slots); + ship.buildWith(shipData.defaults); + ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId)); + + expect(Math.round(ship.yaws[4] * 100) / 100).toBeCloseTo(thrusterData.yaw, 1); + } + } + }); +}); diff --git a/package.json b/package.json index c84efaea..40992cd2 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "jsx" ], "automock": true, + "bail": false, "unmockedModulePathPatterns": [ "/node_modules/lodash", "/node_modules/react", diff --git a/src/app/components/MovementSummary.jsx b/src/app/components/MovementSummary.jsx new file mode 100644 index 00000000..14fba3bf --- /dev/null +++ b/src/app/components/MovementSummary.jsx @@ -0,0 +1,125 @@ +import React from 'react'; +import cn from 'classnames'; +import TranslatedComponent from './TranslatedComponent'; +import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons'; + +/** + * 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 hide = tooltip.bind(null, null); + 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)}
+
+ ); +// return ( +// +//

{translate('movement summary')}

+// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +//

{translate('normal')}

{translate('speed')} {formats.int(ship.topSpeed)}{units['m/s']}{translate('pitch')} {formats.f1(ship.pitches[4])}{units['°/s']}{translate('roll')} {formats.f1(ship.rolls[4])}{units['°/s']}{translate('yaw')} {formats.f1(ship.yaws[4])}{units['°/s']}

{translate('boost')}

{translate('speed')} {formats.int(ship.topSpeed * boostMultiplier)}{units['m/s']}{translate('pitch')} {formats.f1(ship.pitches[4] * boostMultiplier)}{units['°/s']}{translate('roll')} {formats.f1(ship.rolls[4] * boostMultiplier)}{units['°/s']}{translate('yaw')} {formats.f1(ship.yaws[4] * boostMultiplier)}{units['°/s']}

Frame Shift

Maximum {formats.f2(ship.fullTankRange)} {units.LY}
+//
+// ); + } +} diff --git a/src/app/i18n/Language.jsx b/src/app/i18n/Language.jsx index 5a6a5ee4..d16b2e1f 100644 --- a/src/app/i18n/Language.jsx +++ b/src/app/i18n/Language.jsx @@ -65,6 +65,7 @@ export function getLanguage(langCode) { LY: {translate('LY')}, // Light Years MJ: {translate('MJ')}, // Mega Joules 'm/s': {translate('m/s')}, // Meters per second + '°/s': {translate('°/s')}, // Degrees per second MW: {translate('MW')}, // Mega Watts (same as Mega Joules per second) ps: {translate('/s')}, // per second pm: {translate('/min')}, // per minute diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index b8b7f0ff..08fa074f 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -94,7 +94,7 @@ export const terms = { // Unit for seconds secs: 's', - // Weapon, offence and defence + // Weapon, offence, defence and movement dpe: 'Damage per MJ of energy', dps: 'Damage per second', sdps: 'Sustained damage per second', @@ -106,6 +106,13 @@ export const terms = { 'damage by': 'Damage by', 'damage from': 'Damage from', 'shield cells': 'Shield cells', + 'recovery': 'Recovery', + 'recharge': 'Recharge', + 'engine pips': 'Engine Pips', + 'speed': 'Speed', + 'pitch': 'Pitch', + 'roll': 'Roll', + 'yaw': 'Yaw', // Modifications ammo: 'Ammunition maximum', diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index c7831e9d..9e3793fc 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -16,6 +16,7 @@ import InternalSlotSection from '../components/InternalSlotSection'; import UtilitySlotSection from '../components/UtilitySlotSection'; import OffenceSummary from '../components/OffenceSummary'; import DefenceSummary from '../components/DefenceSummary'; +import MovementSummary from '../components/MovementSummary'; import LineChart from '../components/LineChart'; import PowerManagement from '../components/PowerManagement'; import CostSection from '../components/CostSection'; @@ -344,19 +345,7 @@ export default class OutfittingPage extends Page {
-

{translate('speed')}

- +
@@ -402,3 +391,19 @@ export default class OutfittingPage extends Page { // func={state.jumpRangeChartFunc} // /> //
+//
+//

{translate('speed')}

+// +//
+ diff --git a/src/app/pages/ShipyardPage.jsx b/src/app/pages/ShipyardPage.jsx index f794eb10..136931a5 100644 --- a/src/app/pages/ShipyardPage.jsx +++ b/src/app/pages/ShipyardPage.jsx @@ -40,7 +40,9 @@ function shipSummary(shipId, shipData) { intCount: 0, maxCargo: 0, hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge - int: [0, 0, 0, 0, 0, 0, 0, 0] // Sizes 1 - 8 + int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8 + standard: shipData.slots.standard, + agility: shipData.properties.pitch + shipData.properties.yaw + shipData.properties.roll }; Object.assign(summary, shipData.properties); let ship = new Ship(shipId, shipData.properties, shipData.slots); @@ -139,7 +141,7 @@ export default class ShipyardPage extends Page { > {s.manufacturer} {translate(SizeMap[s.class])} - {s.agility} + {fInt(s.agility)} {fInt(s.speed)}{u['m/s']} {fInt(s.boost)}{u['m/s']} {fInt(s.baseArmour)} @@ -148,6 +150,12 @@ export default class ShipyardPage extends Page { {fInt(s.topBoost)}{u['m/s']} {fRound(s.maxJumpRange)}{u.LY} {fInt(s.maxCargo)}{u.T} + {s.standard[0]} + {s.standard[1]} + {s.standard[2]} + {s.standard[3]} + {s.standard[4]} + {s.standard[5]} {s.hp[1]} {s.hp[2]} {s.hp[3]} @@ -260,9 +268,10 @@ export default class ShipyardPage extends Page { {translate('manufacturer')} {translate('size')} - {translate('mnv')} + {translate('agility')} {translate('base')} {translate('max')} + {translate('core module classes')} {translate('hardpoints')} {translate('internal compartments')} {translate('hull')} @@ -280,6 +289,13 @@ export default class ShipyardPage extends Page { {translate('jump')} {translate('cargo')} + {'pp'} + {'th'} + {'fsd'} + {'ls'} + {'pd'} + {'s'} + {translate('S')} {translate('M')} {translate('L')} diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js index 96c7b0fe..44cccab0 100644 --- a/src/app/shipyard/Calculations.js +++ b/src/app/shipyard/Calculations.js @@ -67,14 +67,35 @@ export function shieldStrength(mass, baseShield, sg, multiplier) { /** * Calculate the a ships speed based on mass, and thrusters. * - * @param {number} mass Current mass of the ship - * @param {number} baseSpeed Base speed m/s for ship - * @param {number} baseBoost Base boost speed m/s for ship - * @param {object} thrusters The Thrusters used - * @param {number} pipSpeed Speed pip multiplier - * @return {object} Approximate speed by pips + * @param {number} mass the mass of the ship + * @param {number} baseSpeed base speed m/s for ship + * @param {object} thrusters The ship's thrusters + * @param {number} engpip the multiplier per pip to engines + * @return {array} Speed by pips */ -export function speed(mass, baseSpeed, baseBoost, thrusters, pipSpeed) { +export function speed(mass, baseSpeed, thrusters, engpip) { + // thrusters might be a module or a template; handle either here + const minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; + const optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; + const maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass; + const minMul = thrusters instanceof Module ? thrusters.getMinMul() : thrusters.minmul; + const optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul; + const maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.maxmul; + + let results = normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseSpeed, engpip); + + return results; +} + +/** + * Calculate pitch of a ship based on mass and thrusters + * @param {number} mass the mass of the ship + * @param {number} basePitch base pitch of the ship + * @param {object} thrusters the ship's thrusters + * @param {number} engpip the multiplier per pip to engines + * @return {array} Pitch by pips + */ +export function pitch(mass, basePitch, thrusters, engpip) { // thrusters might be a module or a template; handle either here let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; @@ -83,16 +104,79 @@ export function speed(mass, baseSpeed, baseBoost, thrusters, pipSpeed) { let optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul; let maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.maxmul; - let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass)); - let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass))); - let ynorm = Math.pow(xnorm, exponent); - let mul = minMul + ynorm * (maxMul - minMul); - let speed = baseSpeed * mul; - - return { - '0 Pips': speed * (1 - (pipSpeed * 4)), - '2 Pips': speed * (1 - (pipSpeed * 2)), - '4 Pips': speed, - 'boost': baseBoost * mul - }; + return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, basePitch, engpip); +} + +/** + * Calculate yaw of a ship based on mass and thrusters + * @param {number} mass the mass of the ship + * @param {number} baseYaw base yaw of the ship + * @param {object} thrusters the ship's thrusters + * @param {number} engpip the multiplier per pip to engines + * @return {array} Yaw by pips + */ +export function yaw(mass, baseYaw, thrusters, engpip) { + // thrusters might be a module or a template; handle either here + let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; + let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; + let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass; + let minMul = thrusters instanceof Module ? thrusters.getMinMul() : thrusters.minmul; + let optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul; + let maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.maxmul; + + return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseYaw, engpip); +} + +/** + * Calculate roll of a ship based on mass and thrusters + * @param {number} mass the mass of the ship + * @param {number} baseRoll base roll of the ship + * @param {object} thrusters the ship's thrusters + * @param {number} engpip the multiplier per pip to engines + * @return {array} Roll by pips + */ +export function roll(mass, baseRoll, thrusters, engpip) { + // thrusters might be a module or a template; handle either here + let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; + let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; + let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass; + let minMul = thrusters instanceof Module ? thrusters.getMinMul() : thrusters.minmul; + let optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul; + let maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.maxmul; + + return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseRoll, engpip); +} + +/** + * Normalise according to FD's calculations and return suitable values + * @param {number} minMass the minimum mass of the thrusters + * @param {number} optMass the optimum mass of the thrusters + * @param {number} maxMass the maximum mass of the thrusters + * @param {number} minMul the minimum multiplier of the thrusters + * @param {number} optMul the optimum multiplier of the thrusters + * @param {number} maxMul the maximum multiplier of the thrusters + * @param {number} mass the mass of the ship + * @param {base} base the base value from which to calculate + * @param {number} engpip the multiplier per pip to engines + * @return {array} values by pips + */ +function normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base, engpip) { + const xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass)); + const exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass))); + const ynorm = Math.pow(xnorm, exponent); + const mul = minMul + ynorm * (maxMul - minMul); + const res = base * mul; + + return [res * (1 - (engpip * 4)), + res * (1 - (engpip * 3)), + res * (1 - (engpip * 2)), + res * (1 - (engpip * 1)), + res]; + //return { + // '0 Pips': res * (1 - (engpip * 4)), + // '1 Pip': res * (1 - (engpip * 3)), + // '2 Pips': res * (1 - (engpip * 2)), + // '3 Pips': res * (1 - (engpip)), + // '4 Pips': res + //}; } diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index be771cbc..32a1a969 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -188,10 +188,10 @@ export default class Ship { * Calculate the hypothetical top speeds at cargo and fuel tonnage * @param {Number} fuel Fuel available in tons * @param {Number} cargo Cargo in tons - * @return {Object} Speed at pip settings and boost + * @return {array} Speed at pip settings */ calcSpeedsWith(fuel, cargo) { - return Calc.speed(this.unladenMass + fuel + cargo, this.speed, this.boost, this.standard[1].m, this.pipSpeed); + return Calc.speed(this.unladenMass + fuel + cargo, this.speed, this.standard[1].m, this.pipSpeed); } /** @@ -432,7 +432,7 @@ export default class Ship { let newMass = m.getMass(); this.unladenMass = this.unladenMass - oldMass + newMass; this.ladenMass = this.ladenMass - oldMass + newMass; - this.updateTopSpeed(); + this.updateMovement(); this.updateJumpStats(); } else if (name === 'maxfuel') { m.setModValue(name, value); @@ -440,13 +440,13 @@ export default class Ship { } else if (name === 'optmass') { m.setModValue(name, value); // Could be for any of thrusters, FSD or shield - this.updateTopSpeed(); + this.updateMovement(); this.updateJumpStats(); this.recalculateShield(); } else if (name === 'optmul') { m.setModValue(name, value); // Could be for any of thrusters, FSD or shield - this.updateTopSpeed(); + this.updateMovement(); this.updateJumpStats(); this.recalculateShield(); } else if (name === 'shieldboost') { @@ -597,7 +597,7 @@ export default class Ship { .recalculateDps() .recalculateEps() .recalculateHps() - .updateTopSpeed(); + .updateMovement(); } return this.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString(); @@ -876,7 +876,7 @@ export default class Ship { if (shieldCellsChange) { this.recalculateShieldCells(); } - this.updateTopSpeed(); + this.updateMovement(); this.updateJumpStats(); } return this; @@ -1088,13 +1088,20 @@ export default class Ship { } /** - * Update top speed and boost + * Update movement values * @return {this} The ship instance (for chaining operations) */ - updateTopSpeed() { - let speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.boost, this.standard[1].m, this.pipSpeed); - this.topSpeed = speeds['4 Pips']; - this.topBoost = this.canBoost() ? speeds.boost : 0; + updateMovement() { + this.speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.standard[1].m, this.pipSpeed); + this.topSpeed = this.speeds[4]; + this.topBoost = this.canBoost() ? this.speeds[4] * this.boost / this.speed : 0; + + this.pitches = Calc.pitch(this.unladenMass + this.fuelCapacity, this.pitch, this.standard[1].m, this.pipSpeed); + + this.rolls = Calc.roll(this.unladenMass + this.fuelCapacity, this.roll, this.standard[1].m, this.pipSpeed); + + this.yaws = Calc.yaw(this.unladenMass + this.fuelCapacity, this.yaw, this.standard[1].m, this.pipSpeed); + return this; } diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index 6d90bc7f..46d7fc05 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -33,7 +33,7 @@ const SHIP_FD_NAME_TO_CORIOLIS_NAME = { 'Type7': 'type_7_transport', 'Type9': 'type_9_heavy', 'Viper': 'viper', - 'Viper_MKIV': 'viper_mk_iv', + 'Viper_MkIV': 'viper_mk_iv', 'Vulture': 'vulture' }; From 5426b55637be742e55225d4ba5bb11ed5a18b2fa Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 12 Dec 2016 10:28:54 +0000 Subject: [PATCH 03/35] Ensure module ordering is consistent --- ChangeLog.md | 1 + src/app/components/AvailableModulesMenu.jsx | 34 +++++++++++++++++++-- src/app/components/MovementSummary.jsx | 2 +- src/app/i18n/en.js | 1 + 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 6b9a9582..f2a04852 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,6 +4,7 @@ * Add movement summary to outfitting page * Add standard internal class sizes to shipyard page * Fix issue when importing Viper Mk IV + * Ensure ordering of all types of modules (standard, internal, utilities) is consistent #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index f72a475d..4b5013fb 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -93,8 +93,10 @@ export default class AvailableModulesMenu extends TranslatedComponent { let prevClass = null, prevRating = null; let elems = []; - for (let i = 0; i < modules.length; i++) { - let m = modules[i]; + const sortedModules = modules.sort(this._moduleOrder); + + for (let i = 0; i < sortedModules.length; i++) { + let m = sortedModules[i]; let mount = null; let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass; let active = mountedModule && mountedModule.id === m.id; @@ -126,7 +128,7 @@ export default class AvailableModulesMenu extends TranslatedComponent { case 'T': mount = ; break; } - if (i > 0 && modules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mount) && m.grp != 'pa') { + if (i > 0 && sortedModules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mount) && m.grp != 'pa') { elems.push(
); } @@ -201,6 +203,32 @@ export default class AvailableModulesMenu extends TranslatedComponent { this.context.tooltip(); } + _moduleOrder(a, b) { + // Named modules go last + if (!a.name && b.name) { + return -1; + } + if (a.name && !b.name) { + return 1; + } + // Class ordered from highest (8) to lowest (1) + if (a.class < b.class) { + return 1; + } + if (a.class > b.class) { + return -1; + // Rating ordered from lowest (E) to highest (A) + } + if (a.rating < b.rating) { + return 1; + } + if (a.rating > b.rating) { + return -1; + } + // Do not attempt to order by name at this point, as that mucks up the order of armour + return 0; + } + /** * Scroll to mounted (if it exists) module group on mount */ diff --git a/src/app/components/MovementSummary.jsx b/src/app/components/MovementSummary.jsx index 14fba3bf..c3b60970 100644 --- a/src/app/components/MovementSummary.jsx +++ b/src/app/components/MovementSummary.jsx @@ -46,7 +46,7 @@ export default class MovementSummary extends TranslatedComponent { 2 3 4 - 4B + 4B {translate('speed')} ({units['m/s']}) diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index 08fa074f..383e0f1e 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -109,6 +109,7 @@ export const terms = { 'recovery': 'Recovery', 'recharge': 'Recharge', 'engine pips': 'Engine Pips', + '4b': '4 pips and boost', 'speed': 'Speed', 'pitch': 'Pitch', 'roll': 'Roll', From 7c6a4fc5f889a09c435c4d93640f5728c498946d Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 12 Dec 2016 10:38:50 +0000 Subject: [PATCH 04/35] Do not rely on coriolis-data' internal ordering of modules for display purposes --- ChangeLog.md | 1 + src/app/components/AvailableModulesMenu.jsx | 6 ++++++ src/app/components/InternalSlot.jsx | 1 + src/app/i18n/en.js | 3 +++ src/app/shipyard/Calculations.js | 7 ------- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index f2a04852..cd710252 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,6 +5,7 @@ * Add standard internal class sizes to shipyard page * Fix issue when importing Viper Mk IV * Ensure ordering of all types of modules (standard, internal, utilities) is consistent + * Add rebuilds per bay information for fighter hangars #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index 4b5013fb..fa4c18d5 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -203,6 +203,12 @@ export default class AvailableModulesMenu extends TranslatedComponent { this.context.tooltip(); } + /** + * Order two modules suitably for display in module selection + * @param {Object} a the first module + * @param {Object} b the second module + * @return {int} -1 if the first module should go first, 1 if the second module should go first + */ _moduleOrder(a, b) { // Named modules go last if (!a.name && b.name) { diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index 2f8fb206..369d001f 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -43,6 +43,7 @@ export default class InternalSlot extends Slot { { m.getMaxMass() ?
{translate('max mass')}: {formats.int(m.getMaxMass())}{u.T}
: null } { m.bins ?
{m.bins} {translate('bins')}
: null } { 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 } diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index 383e0f1e..808f4e12 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -94,6 +94,8 @@ export const terms = { // Unit for seconds secs: 's', + rebuildsperbay: 'Rebuilds per bay', + // Weapon, offence, defence and movement dpe: 'Damage per MJ of energy', dps: 'Damage per second', @@ -120,6 +122,7 @@ export const terms = { boot: 'Boot time', brokenregen: 'Broken regeneration rate', burst: 'Burst', + burstrof: 'Burst rate of fire', clip: 'Ammunition clip', damage: 'Damage', distdraw: 'Distributor draw', diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js index 44cccab0..600b6bab 100644 --- a/src/app/shipyard/Calculations.js +++ b/src/app/shipyard/Calculations.js @@ -172,11 +172,4 @@ function normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, bas res * (1 - (engpip * 2)), res * (1 - (engpip * 1)), res]; - //return { - // '0 Pips': res * (1 - (engpip * 4)), - // '1 Pip': res * (1 - (engpip * 3)), - // '2 Pips': res * (1 - (engpip * 2)), - // '3 Pips': res * (1 - (engpip)), - // '4 Pips': res - //}; } From b944035541d62bf1bd78d1051a8f6f62f5fec345 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 12 Dec 2016 13:37:36 +0000 Subject: [PATCH 05/35] Use separate speed/rotation/acceleration multipliers for thrusters if available --- ChangeLog.md | 1 + src/app/shipyard/Calculations.js | 24 ++++++++--------- src/app/shipyard/Module.js | 46 ++++++++++++++++++++++---------- 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index cd710252..8e0b2e59 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,7 @@ * Fix issue when importing Viper Mk IV * Ensure ordering of all types of modules (standard, internal, utilities) is consistent * Add rebuilds per bay information for fighter hangars + * Use separate speed/rotation/acceleration multipliers for thrusters if available #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js index 600b6bab..f9ab11e4 100644 --- a/src/app/shipyard/Calculations.js +++ b/src/app/shipyard/Calculations.js @@ -78,9 +78,9 @@ export function speed(mass, baseSpeed, thrusters, engpip) { const minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; const optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; const maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass; - const minMul = thrusters instanceof Module ? thrusters.getMinMul() : thrusters.minmul; - const optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul; - const maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.maxmul; + const minMul = thrusters instanceof Module ? thrusters.getMinMul('speed') : (thrusters.minmulspeed ? thrusters.minmulspeed : thrusters.minmul); + const optMul = thrusters instanceof Module ? thrusters.getOptMul('speed') : (thrusters.optmulspeed ? thrusters.minmulspeed : thrusters.minmul); + const maxMul = thrusters instanceof Module ? thrusters.getMaxMul('speed') : (thrusters.maxmulspeed ? thrusters.minmulspeed : thrusters.minmul); let results = normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseSpeed, engpip); @@ -100,9 +100,9 @@ export function pitch(mass, basePitch, thrusters, engpip) { let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass; - let minMul = thrusters instanceof Module ? thrusters.getMinMul() : thrusters.minmul; - let optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul; - let maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.maxmul; + let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul); + let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul); + let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul); return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, basePitch, engpip); } @@ -120,9 +120,9 @@ export function yaw(mass, baseYaw, thrusters, engpip) { let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass; - let minMul = thrusters instanceof Module ? thrusters.getMinMul() : thrusters.minmul; - let optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul; - let maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.maxmul; + let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul); + let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul); + let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul); return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseYaw, engpip); } @@ -140,9 +140,9 @@ export function roll(mass, baseRoll, thrusters, engpip) { let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass; let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass; - let minMul = thrusters instanceof Module ? thrusters.getMinMul() : thrusters.minmul; - let optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul; - let maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.maxmul; + let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul); + let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul); + let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul); return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseRoll, engpip); } diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index bf38e8ec..31ee09e8 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -370,42 +370,60 @@ export default class Module { /** * Get the minimum multiplier for this module, taking in to account modifications + * @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null * @return {Number} the minimum multiplier of this module */ - getMinMul() { + getMinMul(type = null) { // Modifier is optmul let result = 0; - if (this['minmul']) { + if (this['minmul' + type]) { + result = this['minmul' + type]; + } else if (this['minmul']) { result = this['minmul']; - if (result) { - let mult = this.getModValue('optmul') / 10000; - if (mult) { result = result * (1 + mult); } - } + } + if (result) { + let mult = this.getModValue('optmul') / 10000; + if (mult) { result = result * (1 + mult); } } return result; } /** * Get the optimum multiplier for this module, taking in to account modifications + * @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null * @return {Number} the optimum multiplier of this module */ - getOptMul() { - return this._getModifiedValue('optmul'); + getOptMul(type = null) { + // Modifier is optmul + let result = 0; + if (this['optmul' + type]) { + result = this['optmul' + type]; + } else if (this['optmul']) { + result = this['optmul']; + } + if (result) { + let mult = this.getModValue('optmul') / 10000; + if (mult) { result = result * (1 + mult); } + } + return result; } /** * Get the maximum multiplier for this module, taking in to account modifications + * @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null * @return {Number} the maximum multiplier of this module */ - getMaxMul() { + getMaxMul(type = null) { // Modifier is optmul let result = 0; - if (this['maxmul']) { + if (this['maxmul' + type]) { + result = this['maxmul' + type]; + } else if (this['maxmul']) { result = this['maxmul']; - if (result) { - let mult = this.getModValue('optmul') / 10000; - if (mult) { result = result * (1 + mult); } - } + } + if (result) { + let mult = this.getModValue('optmul') / 10000; + if (mult) { result = result * (1 + mult); } } return result; } From 041f873f97f83aae715fcce981469aee9cff8363 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Tue, 13 Dec 2016 18:56:59 +0000 Subject: [PATCH 06/35] Updates --- .gitignore | 3 +- ChangeLog.md | 3 ++ env | 4 --- src/app/components/AvailableModulesMenu.jsx | 10 ++++++- src/app/components/DefenceSummary.jsx | 11 +++++++ src/app/components/InternalSlot.jsx | 1 + src/app/components/MovementSummary.jsx | 33 --------------------- src/app/i18n/en.js | 4 +++ src/app/shipyard/Constants.js | 16 +++++++--- src/app/shipyard/Module.js | 8 +++++ src/app/shipyard/Ship.js | 29 ++++++++++++++---- 11 files changed, 73 insertions(+), 49 deletions(-) delete mode 100644 env diff --git a/.gitignore b/.gitignore index 9d011721..1bbc61e9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ build *.log nginx.pid .idea -/bin \ No newline at end of file +/bin +env diff --git a/ChangeLog.md b/ChangeLog.md index 8e0b2e59..19028379 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,9 @@ * Fix issue when importing Viper Mk IV * Ensure ordering of all types of modules (standard, internal, utilities) is consistent * Add rebuilds per bay information for fighter hangars + * Add ability to show military compartments + * Add diminishing returns for shield boosters + * Show module reinforcement package results in defence summary * Use separate speed/rotation/acceleration multipliers for thrusters if available #2.2.5 diff --git a/env b/env deleted file mode 100644 index 0bb2d2e4..00000000 --- a/env +++ /dev/null @@ -1,4 +0,0 @@ -CORIOLIS_UA_TRACKING=UA-87944382-1 -export CORIOLIS_UA_TRACKING -CORIOLIS_GAPI_KEY=AIzaSyAvuJC2TECa3rulzI5rTyfycYi9T28Xhwc -export CORIOLIS_GAPI_KEY diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index fa4c18d5..4d3697bd 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -223,8 +223,16 @@ export default class AvailableModulesMenu extends TranslatedComponent { } if (a.class > b.class) { return -1; - // Rating ordered from lowest (E) to highest (A) } + // Mount type, if applicable + if (a.mount && b.mount && a.mount !== b.mount) { + if (a.mount === 'F' || (a.mount === 'G' && b.mount === 'T')) { + return -1; + } else { + return 1; + } + } + // Rating ordered from lowest (E) to highest (A) if (a.rating < b.rating) { return 1; } diff --git a/src/app/components/DefenceSummary.jsx b/src/app/components/DefenceSummary.jsx index d9ec54a9..94831068 100644 --- a/src/app/components/DefenceSummary.jsx +++ b/src/app/components/DefenceSummary.jsx @@ -67,6 +67,17 @@ export default class DefenceSummary extends TranslatedComponent { {formats.pct1(ship.hullKinRes || 1)} {formats.pct1(ship.hullThermRes || 1)} + + {ship.modulearmour > 0 ? + +

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

+ : null } + + {ship.moduleprotection > 0 ? + + {translate('internal protection')} {formats.pct1(ship.moduleprotection)} + {translate('external protection')} {formats.pct1(ship.moduleprotection / 2)} + : null } diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index 369d001f..cfd6e7ff 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -59,6 +59,7 @@ export default class InternalSlot extends Slot { { m.rangeLS === null ?
∞{u.Ls}
: null } { m.rangeRating ?
{translate('range')}: {m.rangeRating}
: null } { m.getHullReinforcement() ?
+{formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)} {translate('armour')}
: null } + { m.getProtection() ?
{formats.rPct(m.getProtection())} {translate('protection')}
: null } { m.passengers ?
{translate('passengers')}: {m.passengers}
: null } { showModuleResistances && m.getExplosiveResistance() ?
{translate('explres')}: {formats.pct(m.getExplosiveResistance())}
: null } { showModuleResistances && m.getKineticResistance() ?
{translate('kinres')}: {formats.pct(m.getKineticResistance())}
: null } diff --git a/src/app/components/MovementSummary.jsx b/src/app/components/MovementSummary.jsx index c3b60970..f5b31699 100644 --- a/src/app/components/MovementSummary.jsx +++ b/src/app/components/MovementSummary.jsx @@ -88,38 +88,5 @@ export default class MovementSummary extends TranslatedComponent { ); -// return ( -// -//

{translate('movement summary')}

-// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -//

{translate('normal')}

{translate('speed')} {formats.int(ship.topSpeed)}{units['m/s']}{translate('pitch')} {formats.f1(ship.pitches[4])}{units['°/s']}{translate('roll')} {formats.f1(ship.rolls[4])}{units['°/s']}{translate('yaw')} {formats.f1(ship.yaws[4])}{units['°/s']}

{translate('boost')}

{translate('speed')} {formats.int(ship.topSpeed * boostMultiplier)}{units['m/s']}{translate('pitch')} {formats.f1(ship.pitches[4] * boostMultiplier)}{units['°/s']}{translate('roll')} {formats.f1(ship.rolls[4] * boostMultiplier)}{units['°/s']}{translate('yaw')} {formats.f1(ship.yaws[4] * boostMultiplier)}{units['°/s']}

Frame Shift

Maximum {formats.f2(ship.fullTankRange)} {units.LY}
-//
-// ); } } diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index 808f4e12..91c49c19 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -59,6 +59,7 @@ export const terms = { mc: 'Multi-cannon', ml: 'Mining Laser', mr: 'Missile Rack', + mrp: 'Module Reinforcement Package', nl: 'Mine Launcher', pa: 'Plasma Accelerator', pas: 'Planetary Approach Suite', @@ -116,6 +117,8 @@ export const terms = { 'pitch': 'Pitch', 'roll': 'Roll', 'yaw': 'Yaw', + 'internal protection': 'Internal protection', + 'external protection': 'External protection', // Modifications ammo: 'Ammunition maximum', @@ -144,6 +147,7 @@ export const terms = { pgen: 'Power generation', piercing: 'Piercing', power: 'Power draw', + protection: 'Protection', range: 'Range', ranget: 'Range', // Range in time (for FSD interdictor) regen: 'Regeneration rate', diff --git a/src/app/shipyard/Constants.js b/src/app/shipyard/Constants.js index e8714216..35901ee3 100755 --- a/src/app/shipyard/Constants.js +++ b/src/app/shipyard/Constants.js @@ -105,8 +105,9 @@ export const BulkheadNames = [ export const ShipFacets = [ { // 0 title: 'agility', - props: ['agility'], - fmt: 'int', + props: ['topPitch', 'topRoll', 'topYaw'], + lbls: ['pitch', 'roll', 'yaw'], + fmt: 'f1', i: 0 }, { // 1 @@ -185,11 +186,18 @@ export const ShipFacets = [ }, { // 11 title: 'DPS', - props: ['totalDps'], - lbls: ['DPS'], + props: ['totalDps', 'totalExplDps', 'totalKinDps', 'totalThermDps'], + lbls: ['total', 'explosive', 'kinetic', 'thermal'], fmt: 'round', i: 11 }, + { // 14 + title: 'Sustained DPS', + props: ['totalSDps', 'totalExplSDps', 'totalKinSDps', 'totalThermSDps'], + lbls: ['total', 'explosive', 'kinetic', 'thermal'], + fmt: 'round', + i: 14 + }, { // 12 title: 'EPS', props: ['totalEps'], diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 31ee09e8..a9f7bd07 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -302,6 +302,14 @@ export default class Module { return this._getModifiedValue('hullreinforcement'); } + /** + * Get the protection for this module, taking in to account modifications + * @return {Number} the protection of this module + */ + getProtection() { + return this._getModifiedValue('protection'); + } + /** * Get the delay for this module, taking in to account modifications * @return {Number} the delay of this module diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 32a1a969..b839a90f 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -452,7 +452,7 @@ export default class Ship { } else if (name === 'shieldboost') { m.setModValue(name, value); this.recalculateShield(); - } else if (name === 'hullboost' || name === 'hullreinforcement') { + } else if (name === 'hullboost' || name === 'hullreinforcement' || name === 'modulereinforcement') { m.setModValue(name, value); this.recalculateArmour(); } else if (name === 'shieldreinforcement') { @@ -801,7 +801,7 @@ export default class Ship { let epsChanged = n && n.getEps() || old && old.getEps(); let hpsChanged = n && n.getHps() || old && old.getHps(); - let armourChange = (slot === this.bulkheads) || (n && n.grp === 'hr') || (old && old.grp === 'hr'); + let armourChange = (slot === this.bulkheads) || (n && n.grp === 'hr') || (old && old.grp === 'hr') || (n && n.grp === 'mrp') || (old && old.grp === 'mrp'); let shieldChange = (n && n.grp === 'bsg') || (old && old.grp === 'bsg') || (n && n.grp === 'psg') || (old && old.grp === 'psg') || (n && n.grp === 'sg') || (old && old.grp === 'sg') || (n && n.grp === 'sb') || (old && old.grp === 'sb'); @@ -1097,10 +1097,13 @@ export default class Ship { this.topBoost = this.canBoost() ? this.speeds[4] * this.boost / this.speed : 0; this.pitches = Calc.pitch(this.unladenMass + this.fuelCapacity, this.pitch, this.standard[1].m, this.pipSpeed); + this.topPitch = this.pitches[4]; this.rolls = Calc.roll(this.unladenMass + this.fuelCapacity, this.roll, this.standard[1].m, this.pipSpeed); + this.topRoll = this.rolls[4]; this.yaws = Calc.yaw(this.unladenMass + this.fuelCapacity, this.yaw, this.standard[1].m, this.pipSpeed); + this.topYaw = this.yaws[4]; return this; } @@ -1111,6 +1114,7 @@ export default class Ship { */ recalculateShield() { let shield = 0; + let shieldBoost = 0; let shieldExplRes = null; let shieldKinRes = null; let shieldThermRes = null; @@ -1118,8 +1122,7 @@ export default class Ship { const sgSlot = this.findInternalByGroup('sg'); if (sgSlot && sgSlot.enabled) { // Shield from generator - const baseShield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1); - shield = baseShield; + shield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1); shieldExplRes = 1 - sgSlot.m.getExplosiveResistance(); shieldKinRes = 1 - sgSlot.m.getKineticResistance(); shieldThermRes = 1 - sgSlot.m.getThermalResistance(); @@ -1127,7 +1130,8 @@ export default class Ship { // Shield from boosters for (let slot of this.hardpoints) { if (slot.m && slot.m.grp == 'sb') { - shield += baseShield * slot.m.getShieldBoost(); + //shield += baseShield * slot.m.getShieldBoost(); + shieldBoost += slot.m.getShieldBoost(); shieldExplRes *= (1 - slot.m.getExplosiveResistance()); shieldKinRes *= (1 - slot.m.getKineticResistance()); shieldThermRes *= (1 - slot.m.getThermalResistance()); @@ -1135,6 +1139,10 @@ export default class Ship { } } + // We apply diminishing returns to the boosted value + shieldBoost = Math.min(shieldBoost, (1 - Math.pow(Math.E, -0.7 * shieldBoost)) * 2.5); + shield = shield * shieldBoost; + this.shield = shield; this.shieldExplRes = shieldExplRes ? 1 - this.diminishingReturns(1 - shieldExplRes, 0.5, 0.75) : null; this.shieldKinRes = shieldKinRes ? 1 - this.diminishingReturns(1 - shieldKinRes, 0.5, 0.75) : null; @@ -1169,11 +1177,13 @@ export default class Ship { // Armour from bulkheads let bulkhead = this.bulkheads.m; let armour = this.baseArmour + (this.baseArmour * bulkhead.getHullBoost()); + let modulearmour = 0; + let moduleprotection = 1; let hullExplRes = 1 - bulkhead.getExplosiveResistance(); let hullKinRes = 1 - bulkhead.getKineticResistance(); let hullThermRes = 1 - bulkhead.getThermalResistance(); - // Armour from HRPs + // Armour from HRPs and module armour from MRPs for (let slot of this.internal) { if (slot.m && slot.m.grp == 'hr') { armour += slot.m.getHullReinforcement(); @@ -1184,9 +1194,16 @@ export default class Ship { hullKinRes *= (1 - slot.m.getKineticResistance()); hullThermRes *= (1 - slot.m.getThermalResistance()); } + if (slot.m && slot.m.grp == 'mrp') { + modulearmour += slot.m.getIntegrity(); + moduleprotection = moduleprotection * (1 - slot.m.getProtection()); + } } + moduleprotection = 1 - moduleprotection; this.armour = armour; + this.modulearmour = modulearmour; + this.moduleprotection = moduleprotection; this.hullExplRes = 1 - this.diminishingReturns(1 - hullExplRes, 0.5, 0.75); this.hullKinRes = 1 - this.diminishingReturns(1 - hullKinRes, 0.5, 0.75); this.hullThermRes = 1 - this.diminishingReturns(1 - hullThermRes, 0.5, 0.75); From 2a6850ded0bc2998726789dc90eeb589c5b1a07a Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Tue, 13 Dec 2016 22:08:49 +0000 Subject: [PATCH 07/35] Set initial shield boost correctly --- src/app/shipyard/Ship.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index b839a90f..5b78501a 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -1114,7 +1114,7 @@ export default class Ship { */ recalculateShield() { let shield = 0; - let shieldBoost = 0; + let shieldBoost = 1; let shieldExplRes = null; let shieldKinRes = null; let shieldThermRes = null; @@ -1130,7 +1130,6 @@ export default class Ship { // Shield from boosters for (let slot of this.hardpoints) { if (slot.m && slot.m.grp == 'sb') { - //shield += baseShield * slot.m.getShieldBoost(); shieldBoost += slot.m.getShieldBoost(); shieldExplRes *= (1 - slot.m.getExplosiveResistance()); shieldKinRes *= (1 - slot.m.getKineticResistance()); From 203e9c7b4622ec32979a0cf74b8615106bd28219 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Wed, 14 Dec 2016 08:49:23 +0000 Subject: [PATCH 08/35] Fix for importing definitions with missing slots --- src/app/utils/CompanionApiUtils.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index 46d7fc05..d0da068a 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -224,7 +224,9 @@ export function shipFromJson(json) { // Now that we know what we're looking for, find it const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum; const hardpointSlot = json.modules[hardpointName]; - if (!hardpointSlot.module) { + if (!hardpointSlot) { + // This can happen with old imports that don't contain new hardpoints + } else if (!hardpointSlot.module) { // No module } else { const hardpointJson = hardpointSlot.module; @@ -251,7 +253,9 @@ export function shipFromJson(json) { } internalSlotNum++; } - if (!internalSlot.module) { + if (!internalSlot) { + // This can happen with old imports that don't contain new slots + } else if (!internalSlot.module) { // No module } else { const internalJson = internalSlot.module; From 5bbc6be3d896a8d3f2a9cc11a507ddba5ac28de0 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Wed, 14 Dec 2016 13:23:19 +0000 Subject: [PATCH 09/35] Obey restricted slot rules when adding all for internal slots --- ChangeLog.md | 1 + src/app/components/InternalSlotSection.jsx | 36 ++++++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 19028379..c32ab6db 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -10,6 +10,7 @@ * Add diminishing returns for shield boosters * Show module reinforcement package results in defence summary * Use separate speed/rotation/acceleration multipliers for thrusters if available + * Obey restricted slot rules when adding all for internal slots #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/components/InternalSlotSection.jsx b/src/app/components/InternalSlotSection.jsx index ae6b8bec..8d1b11a0 100644 --- a/src/app/components/InternalSlotSection.jsx +++ b/src/app/components/InternalSlotSection.jsx @@ -22,6 +22,7 @@ export default class InternalSlotSection extends SlotSection { this._fillWithCargo = this._fillWithCargo.bind(this); this._fillWithCells = this._fillWithCells.bind(this); this._fillWithArmor = this._fillWithArmor.bind(this); + this._fillWithModuleReinforcementPackages = this._fillWithModuleReinforcementPackages.bind(this); this._fillWithFuelTanks = this._fillWithFuelTanks.bind(this); this._fillWithLuxuryCabins = this._fillWithLuxuryCabins.bind(this); this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this); @@ -46,7 +47,7 @@ export default class InternalSlotSection extends SlotSection { let clobber = event.getModifierState('Alt'); let ship = this.props.ship; ship.internal.forEach((slot) => { - if (clobber || !slot.m) { + if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.cr)) { ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E')); } }); @@ -62,7 +63,7 @@ export default class InternalSlotSection extends SlotSection { let clobber = event.getModifierState('Alt'); let ship = this.props.ship; ship.internal.forEach((slot) => { - if (clobber || !slot.m) { + if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.ft)) { ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C')); } }); @@ -78,7 +79,7 @@ export default class InternalSlotSection extends SlotSection { let clobber = event.getModifierState('Alt'); let ship = this.props.ship; ship.internal.forEach((slot) => { - if (clobber || !slot.m) { + if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.pcq)) { ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6 } }); @@ -94,7 +95,7 @@ export default class InternalSlotSection extends SlotSection { let clobber = event.getModifierState('Alt'); let ship = this.props.ship; ship.internal.forEach((slot) => { - if (clobber || !slot.m) { + if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.pcm)) { ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6 } }); @@ -110,7 +111,7 @@ export default class InternalSlotSection extends SlotSection { let clobber = event.getModifierState('Alt'); let ship = this.props.ship; ship.internal.forEach((slot) => { - if (clobber || !slot.m) { + if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.pci)) { ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6 } }); @@ -126,7 +127,7 @@ export default class InternalSlotSection extends SlotSection { let clobber = event.getModifierState('Alt'); let ship = this.props.ship; ship.internal.forEach((slot) => { - if (clobber || !slot.m) { + if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.pce)) { ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6 } }); @@ -143,7 +144,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 passenger ships special case + if ((clobber || (!slot.m && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { 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; @@ -161,8 +162,24 @@ export default class InternalSlotSection extends SlotSection { let clobber = event.getModifierState('Alt'); let ship = this.props.ship; ship.internal.forEach((slot) => { - if (clobber || !slot.m) { - ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D + if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.hr)) { + ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D + } + }); + this.props.onChange(); + this._close(); + } + + /** + * Fill all slots with Module Reinforcement Packages + * @param {SyntheticEvent} event Event + */ + _fillWithModuleReinforcementPackages(event) { + let clobber = event.getModifierState('Alt'); + let ship = this.props.ship; + ship.internal.forEach((slot) => { + if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.mrp)) { + ship.use(slot, ModuleUtils.findInternal('mrp', Math.min(slot.maxClass, 5), 'D')); // Module reinforcements top out at 5D } }); this.props.onChange(); @@ -226,6 +243,7 @@ export default class InternalSlotSection extends SlotSection {
  • {translate('cargo')}
  • {translate('scb')}
  • {translate('hr')}
  • +
  • {translate('mrp')}
  • {translate('ft')}
  • {translate('pce')}
  • {translate('pci')}
  • From 5603315bf04f47d32868f2707c5d586d2fb45bac Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Wed, 14 Dec 2016 13:23:54 +0000 Subject: [PATCH 10/35] Version URLs to handle changes to ship specifications over time --- ChangeLog.md | 1 + src/app/pages/OutfittingPage.jsx | 3 +- src/app/shipyard/Ship.js | 50 ++++++++++++++++++++++++++++---- src/app/utils/UrlGenerators.js | 2 +- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index c32ab6db..a714fd81 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -11,6 +11,7 @@ * Show module reinforcement package results in defence summary * Use separate speed/rotation/acceleration multipliers for thrusters if available * Obey restricted slot rules when adding all for internal slots + * Version URLs to handle changes to ship specifications over time #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 9e3793fc..8b1cbfd3 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -61,6 +61,7 @@ export default class OutfittingPage extends Page { let params = context.route.params; let shipId = params.ship; let code = params.code; + let version = params.ver; let buildName = params.bn; let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults let savedCode = Persist.getBuild(shipId, buildName); @@ -72,7 +73,7 @@ export default class OutfittingPage extends Page { let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance if (code) { - ship.buildFrom(code); // Populate modules from serialized 'code' URL param + ship.buildFrom(code, version); // Populate modules from serialized 'code' URL param } else { ship.buildWith(data.defaults); // Populate with default components } diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 5b78501a..a18079f6 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -610,7 +610,7 @@ export default class Ship { * @param {String} serializedString The string to deserialize * @return {this} The current ship instance for chaining */ - buildFrom(serializedString) { + buildFrom(serializedString, version = 1) { let standard = new Array(this.standard.length), hardpoints = new Array(this.hardpoints.length), internal = new Array(this.internal.length), @@ -644,6 +644,19 @@ export default class Ship { decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, standard, 1))); +console.log('Priorities was ' + JSON.stringify(priorities)); +console.log('Enableds was ' + JSON.stringify(enabled)); +console.log('Modifications was ' + JSON.stringify(modifications)); +console.log('Blueprints was ' + JSON.stringify(blueprints)); + if (version != 2) { + // Alter as required due to changes in the (build) code from one version to the next + this.upgradeInternals(this.id, internal, 1 + this.standard.length + this.hardpoints.length, priorities, enabled, modifications, blueprints, version); + } +console.log('Priorities is ' + JSON.stringify(priorities)); +console.log('Enableds is ' + JSON.stringify(enabled)); +console.log('Modifications is ' + JSON.stringify(modifications)); +console.log('Blueprints is ' + JSON.stringify(blueprints)); + return this.buildWith( { bulkheads: code.charAt(0) * 1, @@ -1392,7 +1405,7 @@ export default class Ship { for (let slot of slots) { if (slot.length > 0) { buffer.writeInt8(i, curpos++); - if (blueprints[i]) { + if (blueprints[i] && blueprints[i].id) { buffer.writeInt8(MODIFICATION_ID_BLUEPRINT, curpos++); buffer.writeInt32LE(blueprints[i].id, curpos); curpos += 4; @@ -1457,9 +1470,13 @@ export default class Ship { curpos += 4; // There are a number of 'special' modification IDs, check for them here if (modificationId === MODIFICATION_ID_BLUEPRINT) { - blueprint = Object.assign(blueprint, _.find(Modifications.blueprints, function(o) { return o.id === modificationValue; })); + if (modificationValue !== 0) { + blueprint = Object.assign(blueprint, _.find(Modifications.blueprints, function(o) { return o.id === modificationValue; })); + } } else if (modificationId === MODIFICATION_ID_GRADE) { - blueprint.grade = modificationValue; + if (modificationValue !== 0) { + blueprint.grade = modificationValue; + } } else if (modificationId === MODIFICATION_ID_SPECIAL) { blueprint.special = _.find(Modifications.specials, function(o) { return o.id === modificationValue; }); } else { @@ -1470,7 +1487,9 @@ export default class Ship { modificationId = buffer.readInt8(curpos++); } modArr[slot] = modifications; - blueprintArr[slot] = blueprint; + if (blueprint.id) { + blueprintArr[slot] = blueprint; + } slot = buffer.readInt8(curpos++); } } @@ -1638,4 +1657,25 @@ export default class Ship { } return this; } + + upgradeInternals(shipId, internals, offset, priorities, enableds, modifications, blueprints, version) { + if (version == 1) { + // Version 2 reflects the addition of military slots. this means that we need to juggle the internals and their + // associated information around to make holes in the appropriate places + for (var slotId = 0; slotId < this.internal.length; slotId++) { + if (this.internal[slotId].eligible && this.internal[slotId].eligible.mrp) { + // Found an MRP - push all of the existing items down one to compensate for the fact that they didn't exist before now + internals.push.apply(internals, [0].concat(internals.splice(slotId).slice(0, -1))); + + const offsetSlotId = offset + slotId; + + // Same for priorities etc. + priorities.push.apply(priorities, [0].concat(priorities.splice(offsetSlotId))); + enableds.push.apply(enableds, [1].concat(enableds.splice(offsetSlotId))); + modifications.push.apply(modifications, [null].concat(modifications.splice(offsetSlotId).slice(0, -1))); + blueprints.push.apply(blueprints, [null].concat(blueprints.splice(offsetSlotId).slice(0, -1))); + } + } + } + } } diff --git a/src/app/utils/UrlGenerators.js b/src/app/utils/UrlGenerators.js index 827cc04e..15259c47 100644 --- a/src/app/utils/UrlGenerators.js +++ b/src/app/utils/UrlGenerators.js @@ -11,7 +11,7 @@ export function outfitURL(shipId, code, buildName) { let sepChar = '?'; if (code) { - path = path + sepChar + 'code=' + encodeURIComponent(code); + path = path + sepChar + 'code=' + encodeURIComponent(code) + '&ver=2'; sepChar = '&'; } From 46ed9003dd9c8bea62bbfbe29b4eefd44e9994de Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Wed, 14 Dec 2016 13:31:20 +0000 Subject: [PATCH 11/35] Tidy-ups --- src/app/components/InternalSlotSection.jsx | 2 +- src/app/shipyard/Ship.js | 27 ++++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/app/components/InternalSlotSection.jsx b/src/app/components/InternalSlotSection.jsx index 8d1b11a0..af60b841 100644 --- a/src/app/components/InternalSlotSection.jsx +++ b/src/app/components/InternalSlotSection.jsx @@ -163,7 +163,7 @@ export default class InternalSlotSection extends SlotSection { let ship = this.props.ship; ship.internal.forEach((slot) => { if ((clobber || !slot.m) && (!slot.eligible || slot.eligible.hr)) { - ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D + ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D } }); this.props.onChange(); diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index a18079f6..0bbef3dd 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -644,18 +644,10 @@ export default class Ship { decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, standard, 1))); -console.log('Priorities was ' + JSON.stringify(priorities)); -console.log('Enableds was ' + JSON.stringify(enabled)); -console.log('Modifications was ' + JSON.stringify(modifications)); -console.log('Blueprints was ' + JSON.stringify(blueprints)); if (version != 2) { // Alter as required due to changes in the (build) code from one version to the next this.upgradeInternals(this.id, internal, 1 + this.standard.length + this.hardpoints.length, priorities, enabled, modifications, blueprints, version); } -console.log('Priorities is ' + JSON.stringify(priorities)); -console.log('Enableds is ' + JSON.stringify(enabled)); -console.log('Modifications is ' + JSON.stringify(modifications)); -console.log('Blueprints is ' + JSON.stringify(blueprints)); return this.buildWith( { @@ -1472,11 +1464,11 @@ console.log('Blueprints is ' + JSON.stringify(blueprints)); if (modificationId === MODIFICATION_ID_BLUEPRINT) { if (modificationValue !== 0) { blueprint = Object.assign(blueprint, _.find(Modifications.blueprints, function(o) { return o.id === modificationValue; })); - } + } } else if (modificationId === MODIFICATION_ID_GRADE) { if (modificationValue !== 0) { blueprint.grade = modificationValue; - } + } } else if (modificationId === MODIFICATION_ID_SPECIAL) { blueprint.special = _.find(Modifications.specials, function(o) { return o.id === modificationValue; }); } else { @@ -1658,18 +1650,29 @@ console.log('Blueprints is ' + JSON.stringify(blueprints)); return this; } + /** + * Upgrade information about internals with version changes + * @param {int} shipId the ID of the ship + * @param {array} internals the internals from the ship code + * @param {int} offset the offset of the internals information in the priorities etc. arrays + * @param {array} priorities the existing priorities arrray + * @param {array} enableds the existing enableds arrray + * @param {array} modifications the existing modifications arrray + * @param {array} blueprints the existing blueprints arrray + * @param {int} version the version of the information + */ upgradeInternals(shipId, internals, offset, priorities, enableds, modifications, blueprints, version) { if (version == 1) { // Version 2 reflects the addition of military slots. this means that we need to juggle the internals and their // associated information around to make holes in the appropriate places - for (var slotId = 0; slotId < this.internal.length; slotId++) { + for (let slotId = 0; slotId < this.internal.length; slotId++) { if (this.internal[slotId].eligible && this.internal[slotId].eligible.mrp) { // Found an MRP - push all of the existing items down one to compensate for the fact that they didn't exist before now internals.push.apply(internals, [0].concat(internals.splice(slotId).slice(0, -1))); const offsetSlotId = offset + slotId; - // Same for priorities etc. + // Same for priorities etc. priorities.push.apply(priorities, [0].concat(priorities.splice(offsetSlotId))); enableds.push.apply(enableds, [1].concat(enableds.splice(offsetSlotId))); modifications.push.apply(modifications, [null].concat(modifications.splice(offsetSlotId).slice(0, -1))); From 93ba1bf67a25cc84712659a79b885120b63cf933 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Wed, 14 Dec 2016 17:39:01 +0000 Subject: [PATCH 12/35] Do not include disabled shield boosters in calculations --- ChangeLog.md | 1 + src/app/shipyard/Ship.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index a714fd81..0e821ec8 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,6 +12,7 @@ * Use separate speed/rotation/acceleration multipliers for thrusters if available * Obey restricted slot rules when adding all for internal slots * Version URLs to handle changes to ship specifications over time + * Do not include disabled shield boosters in calculations #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 0bbef3dd..0fb84ae5 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -1134,7 +1134,7 @@ export default class Ship { // Shield from boosters for (let slot of this.hardpoints) { - if (slot.m && slot.m.grp == 'sb') { + if (slot.enabled && slot.m && slot.m.grp == 'sb') { shieldBoost += slot.m.getShieldBoost(); shieldExplRes *= (1 - slot.m.getExplosiveResistance()); shieldKinRes *= (1 - slot.m.getKineticResistance()); From 22e74164c57022051db1f905ed554358e0c76894 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Wed, 14 Dec 2016 22:38:21 +0000 Subject: [PATCH 13/35] Alternate (embedded) code versioning scheme; start to fix up tests --- __tests__/test-import.js | 12 +++---- src/app/pages/OutfittingPage.jsx | 3 +- src/app/shipyard/Serializer.js | 59 +++----------------------------- src/app/shipyard/Ship.js | 16 ++++++++- src/app/utils/UrlGenerators.js | 2 +- 5 files changed, 27 insertions(+), 65 deletions(-) diff --git a/__tests__/test-import.js b/__tests__/test-import.js index dbfd0bfd..772acca5 100644 --- a/__tests__/test-import.js +++ b/__tests__/test-import.js @@ -142,7 +142,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA%3D%3D.CwBhCYzBGW9qCTSqs5xA.&bn=Test%20My%20Ship'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04----0303326b.AwRj4zNLZI%3D%3D.CwBhCYzBGW9qCTSq15xA.&bn=Test%20My%20Ship'); }); it('catches an invalid build', function() { @@ -167,7 +167,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA%3D%3D.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2P8xwAEf0GE2AtmBob%2F%2FwFvM%2BjKEgAAAA%3D%3D&bn=Test%20My%20Ship'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04----0303326b.AwRj4zNLZI%3D%3D.CwBhCYzBGW9qCTSq15xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMcAABTINwTEgAAAA%3D%3D&bn=Test%20My%20Ship'); }); }); @@ -184,7 +184,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FAwDFxwtofAAAAA%3D%3D&bn=Multi-purpose%20Asp%20Explorer'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FAwDFxwtofAAAAA%3D%3D&bn=Multi-purpose%20Asp%20Explorer'); }); it('imports a valid v4 build with modifications', function() { @@ -196,7 +196,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s%2FkIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA%2B%2Fz%2Bz3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ%2BLgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ%2F0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g%2F6v0TTdbxJ2KhdEIaCw15MF%2FNB0L%2BS2hwEwyFM8KgP%2BqEpWWA3Qu9Z3z9kPWHzakt7Dt%2BAeD7ghSTgEAAA%3D%3D&bn=Multi-purpose%20Imperial%20Courier'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s%2FkIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA%2B%2Fz%2Bz3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ%2BLgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ%2F0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g%2F6v0TTdbxJ2KhdEIaCw15MF%2FNB0L%2BS2hwEwyFM8KgP%2BqEpWWA3Qu9Z3z9kPWHzakt7Dt%2BAeD7ghSTgEAAA%3D%3D&bn=Multi-purpose%20Imperial%20Courier'); }); }); @@ -238,7 +238,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifrv66g2f.AwRj4zNaKA%3D%3D.CwRgDBldUExuBiQqA%3D%3D%3D.H4sIAAAAAAAAA02Svy9DURTHT1vvtfoat30eXlvV0ufXQmLAIDHSRDcJAzHV1PgDDAaJpVbxF0gYKhFiEFuXTgbCIsKfYJCItHWP75E83vLNue%2F7Od977zs3pBeJ6DsE6TcNIlVn5lgFSw7rfrEikL6mSVS0HSL3MgxoqM3sTGtm%2BxA2R3RGSLSTfWzD32kxu043kVNFDxt6wU8ajVpEY7coh5uARrYR0n3aYY4%2FY6lmkc4xveafqZOHpHejRMb9J7NZQqN9Ascto4fjet0P7iQgRhV7mo5LlLtAUnIe34rVDaKBF9AThUJhla3%2FHqMRB76XBV7v8vEvOOoGx%2BJEgKz9BgvZEHJOyHNUakYujUuSW8KxWOkl%2F%2BzuMsR6QpkS8URUTYKTAagNta4EEvFE1INAqQD0IdCdQCKeiOoBk9%2BPYU87QL7i2tajkITKk0odSFxvAJrClawX%2BCkRT0RZYNjV5b%2BRbyLaOpMkafJa%2BBgufjFnjxBnvgFxKvgBnNYlP7jwiXcRnYQ%2F%2FoRlqCnTHAz41xha9F78CNahGXk8eZ3z%2FcyWjJcg7goeU%2BJdZsw%2FFW2pAaMCAAA%3D&bn=Imported%20Federal%20Corvette'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr------.AwRj4zNapI%3D%3D.CwRgDBldUExuBiZA.H4sIAAAAAAAAA02SP0vDUBTFb1qTtE3xtTFqav0Tbfy3KHRQB8FRO7gJOuioU%2FEDODgILrqKn0DQQUEUB3Hr0smhYhcp%2BiEEEVvf9VwhmuVw3zu%2Fe959eTH0EhF9G5A%2ByyRSl8yc2saSE7pPrCSkt24RlVyPyL9JABpuM3uzmtk9hs1JPSAk2sk9deHvfjH7XprIq6KHTb0YJY3bDtHEA8rROqCxHYSkzzvMmRcs1RzSOaXXo5k6I5DCnk1kNj6YrQoa3TM4%2Fip6OKM3ouBOFmJWcabl%2BURD10jKLWCvVN0k6m%2BBngqCYI2d%2Fx6zlgG%2BXwR%2B2RXhn3DUPcbibIw8%2Bg0WsibkvJBXqFRZLo1Lkl%2FBWKz0cjS7vwJxmijzIqGIOpLgXAxqQ51bgURCEfUkUD4GvQv0KJBIKKK6wYwcpHCmGyNfcW3nWUhCFUqlDiWuJwbN4EpOC35eJBRRDhj29erfk28h2rmQJGkKv7CZKH0yF08QZ70B8bbxAbigK1Fw8IH%2Fwp6GP9nE0qjLaw7G%2FDs8mt0QP4m1UZafh38AuKZDe4MCAAA%3D&bn=Imported%20Federal%20Corvette'); }); it('imports a valid v4 build', function() { @@ -250,7 +250,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwMAIrEcGGsAAAA%3D&bn=Imported%20Beluga%20Liner'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwMAIrEcGGsAAAA%3D&bn=Imported%20Beluga%20Liner'); }); }); diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 8b1cbfd3..9e3793fc 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -61,7 +61,6 @@ export default class OutfittingPage extends Page { let params = context.route.params; let shipId = params.ship; let code = params.code; - let version = params.ver; let buildName = params.bn; let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults let savedCode = Persist.getBuild(shipId, buildName); @@ -73,7 +72,7 @@ export default class OutfittingPage extends Page { let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance if (code) { - ship.buildFrom(code, version); // Populate modules from serialized 'code' URL param + ship.buildFrom(code); // Populate modules from serialized 'code' URL param } else { ship.buildWith(data.defaults); // Populate with default components } diff --git a/src/app/shipyard/Serializer.js b/src/app/shipyard/Serializer.js index cd9951a6..e9210d16 100644 --- a/src/app/shipyard/Serializer.js +++ b/src/app/shipyard/Serializer.js @@ -127,74 +127,23 @@ export function toDetailedBuild(buildName, ship) { return data; }; -/** - * Instantiates a ship from a ship-loadout object - * @param {Object} detailedBuild ship-loadout object - * @return {Ship} Ship instance - */ export function fromDetailedBuild(detailedBuild) { let shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase()); - if (!shipId) { throw 'No such ship: ' + detailedBuild.ship; } - let comps = detailedBuild.components; - let stn = comps.standard; - let priorities = [stn.cargoHatch && stn.cargoHatch.priority !== undefined ? stn.cargoHatch.priority - 1 : 0]; - let enabled = [stn.cargoHatch && stn.cargoHatch.enabled !== undefined ? stn.cargoHatch.enabled : true]; let shipData = Ships[shipId]; let ship = new Ship(shipId, shipData.properties, shipData.slots); - let bulkheads = ModuleUtils.bulkheadIndex(stn.bulkheads); - let modifications = new Array(stn.bulkheads.modifications); - let blueprints = new Array(stn.bulkheads.blueprint); - if (bulkheads < 0) { - throw 'Invalid bulkheads: ' + stn.bulkheads; + if (!detailedBuild.references[0] || !detailedBuild.references[0].code) { + throw 'Missing reference code'; } - let standard = STANDARD.map((c) => { - if (!stn[c].class || !stn[c].rating) { - throw 'Invalid value for ' + c; - } - priorities.push(stn[c].priority === undefined ? 0 : stn[c].priority - 1); - enabled.push(stn[c].enabled === undefined ? true : stn[c].enabled); - modifications.push(stn[c].modifications); - blueprints.push(stn[c].blueprint); - return ModuleUtils.findStandardId(STANDARD_GROUPS[c], stn[c].class, stn[c].rating, stn[c].name); - }); - - let internal = comps.internal.map(c => c ? ModuleUtils.findInternalId(c.group, c.class, c.rating, c.name) : 0); - - let hardpoints = comps.hardpoints.map(c => c ? ModuleUtils.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount], c.missile) : 0) - .concat(comps.utility.map(c => c ? ModuleUtils.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount]) : 0)); - - // The ordering of these arrays must match the order in which they are read in Ship.buildWith - priorities = priorities.concat( - comps.hardpoints.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1), - comps.utility.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1), - comps.internal.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1) - ); - enabled = enabled.concat( - comps.hardpoints.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1), - comps.utility.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1), - comps.internal.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1) - ); - modifications = modifications.concat( - comps.hardpoints.map(c => (c ? c.modifications : null)), - comps.utility.map(c => (c ? c.modifications : null)), - comps.internal.map(c => (c ? c.modifications : null)) - ); - blueprints = blueprints.concat( - comps.hardpoints.map(c => (c ? c.blueprint : null)), - comps.utility.map(c => (c ? c.blueprint : null)), - comps.internal.map(c => (c ? c.blueprint : null)) - ); - - ship.buildWith({ bulkheads, standard, hardpoints, internal }, priorities, enabled, modifications, blueprints); + ship.buildFrom(detailedBuild.references[0].code); return ship; -}; +} /** * Generates an array of ship-loadout JSON Schema object for export diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 0fb84ae5..cc8b6773 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -301,6 +301,7 @@ export default class Ship { */ toString() { return [ + 'A', this.getStandardString(), this.getHardpointsString(), this.getInternalString(), @@ -610,7 +611,7 @@ export default class Ship { * @param {String} serializedString The string to deserialize * @return {this} The current ship instance for chaining */ - buildFrom(serializedString, version = 1) { + buildFrom(serializedString) { let standard = new Array(this.standard.length), hardpoints = new Array(this.hardpoints.length), internal = new Array(this.internal.length), @@ -621,6 +622,19 @@ export default class Ship { enabled = null, code = parts[0]; + // Code has a version ID embedded as the first character (if it is alphabetic) + let version; + if (code && code.match(/^[0-4]/)) { + // Starting with bulkhead number is version 1 + version = 1; + } else { + // Version 2 (current version) + version = 2; + if (code) { + code = code.substring(1); + } + } + if (parts[1]) { enabled = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[1])).split(''); } diff --git a/src/app/utils/UrlGenerators.js b/src/app/utils/UrlGenerators.js index 15259c47..827cc04e 100644 --- a/src/app/utils/UrlGenerators.js +++ b/src/app/utils/UrlGenerators.js @@ -11,7 +11,7 @@ export function outfitURL(shipId, code, buildName) { let sepChar = '?'; if (code) { - path = path + sepChar + 'code=' + encodeURIComponent(code) + '&ver=2'; + path = path + sepChar + 'code=' + encodeURIComponent(code); sepChar = '&'; } From 6e18793d82001c6433701759a36d0e4ee0b3f67a Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Wed, 14 Dec 2016 22:41:10 +0000 Subject: [PATCH 14/35] Lint fix --- src/app/shipyard/Serializer.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/shipyard/Serializer.js b/src/app/shipyard/Serializer.js index e9210d16..0e60c305 100644 --- a/src/app/shipyard/Serializer.js +++ b/src/app/shipyard/Serializer.js @@ -127,6 +127,11 @@ export function toDetailedBuild(buildName, ship) { return data; }; +/** + * Instantiates a ship from a ship-loadout object + * @param {Object} detailedBuild ship-loadout object + * @return {Ship} Ship instance + */ export function fromDetailedBuild(detailedBuild) { let shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase()); if (!shipId) { From af82b8ca1ee798f02f1bfbbabe5a4df7ea4e1ed2 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Thu, 15 Dec 2016 16:46:38 +0000 Subject: [PATCH 15/35] Fix up tests --- __tests__/fixtures/expected-builds.json | 48 ++++++++++++------------- __tests__/test-import.js | 2 +- src/app/shipyard/Ship.js | 8 ++--- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/__tests__/fixtures/expected-builds.json b/__tests__/fixtures/expected-builds.json index 054de777..8528cf1c 100644 --- a/__tests__/fixtures/expected-builds.json +++ b/__tests__/fixtures/expected-builds.json @@ -1,50 +1,50 @@ { "type_6_transporter": { - "Cargo": "0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.", - "Miner": "0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.", - "Hopper": "0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==." + "Cargo": "A0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.", + "Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.", + "Hopper": "A0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==." }, "type_7_transport": { - "Cargo": "0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.", - "Miner": "0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==." + "Cargo": "A0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.", + "Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==." }, "federal_dropship": { - "Cargo": "0pdtiFflnddsif4-1717------05040448020201.Iw18aQ==.Aw18aQ==." + "Cargo": "A0pdtiFflnddsif4-1717------05040448---020201.Iw18RQ==.Aw18RQ==." }, "asp": { - "Miner": "2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==." + "Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==." }, "imperial_clipper": { - "Cargo": "0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.", - "Dream": "2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==.", - "Current": "0patkFflndfskf4----------------.Iw18aQ==.Aw18aQ==." + "Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.", + "Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA.", + "Current": "A0patkFflndfskf4----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA." }, "type_9_heavy": { - "Current": "0patsFklndnsif6---------0706054a0303020224.Iw18eQ==.Aw18eQ==." + "Current": "A0patsFklndnsif6---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg=." }, "python": { - "Cargo": "0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.", - "Miner": "0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==.", - "Dream": "2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==.", - "Missile": "0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==." + "Cargo": "A0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.", + "Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===.", + "Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA===.", + "Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==." }, "anaconda": { - "Dream": "4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.Iw18ZlA=.Aw18ZlA=.", - "Cargo": "0patnFklndnsxf5----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=.", - "Current": "0patnFklndksxf5----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=.", - "Explorer": "0patnFklndksxf5--------0202------f7050505040s372f2i4524.Iw18ZlA=.Aw18ZlA=.", - "Test": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA=." + "Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04--0303326b.AwRj4yo5dqg=.MwBhCYy6dvvARiA=.", + "Cargo": "A0patnFklndnsxf5----------------06050505040404--45030301.Iw18ZXEA.Aw18ZXEA.", + "Current": "A0patnFklndksxf5----------------06050505040404--03034524.Iw18ZXEA.Aw18ZXEA.", + "Explorer": "A0patnFklndksxf5--------0202------f7050505040s37--2f2i4524.AwRj4yVKJug=.AwhMIyumIRhEA===.", + "Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04----0303326b.Iw18ZXEA.Aw18ZXEA." }, "diamondback_explorer": { - "Explorer": "0p0tdFfldddsdf5---0202--320p432i2f.Iw1/kA==.Aw1/kA==." + "Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f.AwRj4zTI.AwiMIypI." }, "vulture": { - "Bounty Hunter": "3patcFalddksff31e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA==." + "Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a--5d27662j.AwRj4yOI.MwBhBYy7oJmAjLIA." }, "fer_de_lance": { - "Attack": "2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ==." + "Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA." }, "eagle": { - "Figther": "4p0t5F5l3d5s5f20p0p24-40532j-.Iw1/EA==.Aw1/EA==." + "Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j-.Iw18kA==.Aw18kA==." } } diff --git a/__tests__/test-import.js b/__tests__/test-import.js index 772acca5..3081a819 100644 --- a/__tests__/test-import.js +++ b/__tests__/test-import.js @@ -147,7 +147,7 @@ describe('Import Modal', function() { it('catches an invalid build', function() { const importData = require('./fixtures/anaconda-test-detailed-export-v3'); - pasteText(JSON.stringify(importData).replace('components', 'comps')); + pasteText(JSON.stringify(importData).replace('references', 'refs')); expect(modal.state.importValid).toBeFalsy(); expect(modal.state.errorMsg).toEqual('Anaconda Build "Test My Ship": Invalid data'); diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index cc8b6773..0f912e50 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -1687,10 +1687,10 @@ export default class Ship { const offsetSlotId = offset + slotId; // Same for priorities etc. - priorities.push.apply(priorities, [0].concat(priorities.splice(offsetSlotId))); - enableds.push.apply(enableds, [1].concat(enableds.splice(offsetSlotId))); - modifications.push.apply(modifications, [null].concat(modifications.splice(offsetSlotId).slice(0, -1))); - blueprints.push.apply(blueprints, [null].concat(blueprints.splice(offsetSlotId).slice(0, -1))); + if (priorities) { priorities.push.apply(priorities, [0].concat(priorities.splice(offsetSlotId))); } + if (enableds) { enableds.push.apply(enableds, [1].concat(enableds.splice(offsetSlotId))); } + if (modifications) { modifications.push.apply(modifications, [null].concat(modifications.splice(offsetSlotId).slice(0, -1))); } + if (blueprints) { blueprints.push.apply(blueprints, [null].concat(blueprints.splice(offsetSlotId).slice(0, -1))); } } } } From 9ed0e30538226640cfc2d361b33991ece1c0c5b8 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 16 Dec 2016 16:25:57 +0000 Subject: [PATCH 16/35] Add hardness to shipyard --- src/app/pages/ShipyardPage.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/pages/ShipyardPage.jsx b/src/app/pages/ShipyardPage.jsx index 136931a5..d579d884 100644 --- a/src/app/pages/ShipyardPage.jsx +++ b/src/app/pages/ShipyardPage.jsx @@ -145,6 +145,7 @@ export default class ShipyardPage extends Page { {fInt(s.speed)}{u['m/s']} {fInt(s.boost)}{u['m/s']} {fInt(s.baseArmour)} + {fInt(s.hardness)} {fInt(s.baseShieldStrength)}{u.MJ} {fInt(s.topSpeed)}{u['m/s']} {fInt(s.topBoost)}{u['m/s']} @@ -269,7 +270,7 @@ export default class ShipyardPage extends Page { {translate('manufacturer')} {translate('size')} {translate('agility')} - {translate('base')} + {translate('base')} {translate('max')} {translate('core module classes')} {translate('hardpoints')} @@ -282,6 +283,7 @@ export default class ShipyardPage extends Page { {translate('speed')} {translate('boost')} {translate('armour')} + {translate('hardness')} {translate('shields')} {translate('speed')} From fb090618da7821d129431bef12ddba3a2283459b Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 16 Dec 2016 20:37:06 +0000 Subject: [PATCH 17/35] Add 'Damage dealt' section --- ChangeLog.md | 1 + src/app/components/DamageDealt.jsx | 143 ++++++++++++ src/app/components/HardpointsSlotSection.jsx | 4 + src/app/components/ShipSelector.jsx | 88 ++++++++ src/app/pages/OutfittingPage.jsx | 53 ++--- src/app/shipyard/Module.js | 8 + src/less/app.less | 1 + src/less/shipselector.less | 218 +++++++++++++++++++ 8 files changed, 491 insertions(+), 25 deletions(-) create mode 100644 src/app/components/DamageDealt.jsx create mode 100644 src/app/components/ShipSelector.jsx create mode 100755 src/less/shipselector.less diff --git a/ChangeLog.md b/ChangeLog.md index 0e821ec8..5f7d1c94 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -13,6 +13,7 @@ * Obey restricted slot rules when adding all for internal slots * Version URLs to handle changes to ship specifications over time * Do not include disabled shield boosters in calculations + * Add 'Damage dealt' section #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx new file mode 100644 index 00000000..f0095013 --- /dev/null +++ b/src/app/components/DamageDealt.jsx @@ -0,0 +1,143 @@ +import React from 'react'; +import cn from 'classnames'; +import TranslatedComponent from './TranslatedComponent'; +import { Ships } from 'coriolis-data/dist'; +import { slotName, slotComparator } from '../utils/SlotFunctions'; +import ShipSelector from './ShipSelector'; + +/** + * Damage against a selected ship + */ +export default class DamageDealt 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._onShipChange = this._onShipChange.bind(this); + + this.state = { + predicate: 'n', + desc: true, + against: Ships['anaconda'], + }; + } + + /** + * Triggered when the comparator ship changes + */ + _onShipChange(s) { + this.setState({ against: Ships[s] }); + } + + /** + * 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 {Ship} against The ship to compare against + * @param {string} predicate Sort predicate + * @param {Boolean} desc Sort order descending + */ + _sort(ship, against, predicate, desc) { + let weaponList = ship.hardpoints; + let comp = slotComparator.bind(null, this.context.language.translate); + + switch (predicate) { + case 'n': comp = comp(null, desc); break; + case 'd': comp = comp((a, b) => a.m.getDps() - b.m.getDps(), desc); break; + case 'e': comp = comp((a, b) => (a.m.getPiercing() > a.m.hardness ? a.m.getDps() : a.m.getDps() * a.m.getPiercing() / a.m.hardness) - (b.m.getPiercing() > b.m.hardness ? b.m.getDps() : b.m.getDps() * b.m.getPiercing() / b.m.hardness), desc); break; + } + + weaponList.sort(comp); + } + + /** + * Render individual rows for hardpoints + * @param {Function} translate Translate function + * @param {Object} formats Localised formats map + * @param {Object} ship Our ship + * @param {Object} against The ship against which to compare + * @return {array} The individual rows + * + */ + _renderRows(translate, formats, ship, against) { + let rows = []; + + for (let hardpoint in ship.hardpoints) { + if (ship.hardpoints[hardpoint].m) { + const m = ship.hardpoints[hardpoint].m; + const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; + const effectiveness = m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness; + const effectiveDps = m.getDps() * effectiveness; + const effectiveSDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectiveness : effectiveDps; + + rows.push( + {classRating} {slotName(translate, ship.hardpoints[hardpoint])} + {formats.round1(effectiveDps)} + {formats.round1(effectiveSDps)} + {formats.pct(effectiveness)} + ); + } + } + + + return rows; + } + + /** + * Render damage dealt + * @return {React.Component} contents + */ + render() { + const { language, tooltip, termtip } = this.context; + const { formats, translate } = language; + + const ship = this.props.ship; + const against = this.state.against; + const hardness = against.properties.hardness; + + return ( + +

    {translate('damage dealt against')}

    + + + + + + + + + + + + {this._renderRows(translate, formats, ship, against)} + +
    {translate('weapon')}{translate('effective dps')}{translate('effective sdps')}{translate('effectiveness')}
    +
    + ); + } +} diff --git a/src/app/components/HardpointsSlotSection.jsx b/src/app/components/HardpointsSlotSection.jsx index 8eb212da..51bb79d4 100644 --- a/src/app/components/HardpointsSlotSection.jsx +++ b/src/app/components/HardpointsSlotSection.jsx @@ -133,6 +133,10 @@ export default class HardpointsSlotSection extends SlotSection {
  • +
    {translate('pa')}
    +
      +
    • {translate('pa')}
    • +
    {translate('nl')}
    • {translate('nl')}
    • diff --git a/src/app/components/ShipSelector.jsx b/src/app/components/ShipSelector.jsx new file mode 100644 index 00000000..bf1ee0ae --- /dev/null +++ b/src/app/components/ShipSelector.jsx @@ -0,0 +1,88 @@ +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 = { + onChange: React.PropTypes.func.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + */ + constructor(props) { + super(props); + + this.state = { shipId : 'adder' }; + } + + /** + * Generate the ships menu + * @return {React.Component} Menu + */ + _getShipsMenu() { + const _selectShip = this._selectShip; + const _openMenu = this._openMenu; + + let shipList = []; + + for (let s in Ships) { + shipList.push(
      {Ships[s].properties.name}
      ); + } + + return shipList; + } + + /** + * Handle opening the 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({ shipId: s }); + + this.context.openMenu(null); + this.props.onChange(s); + } + + /** + * Render ship selector + * @return {React.Component} contents + */ + render() { + const currentMenu = this.props.currentMenu; + const shipId = this.state.shipId; + + return ( +
      +
      +
      + {Ships[shipId].properties.name} + {currentMenu == 'wds' ? +
      e.stopPropagation() }> + {this._getShipsMenu()} +
      : null } +
      +
      +
      + ); + } +} diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 9e3793fc..4473e57b 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -17,6 +17,7 @@ import UtilitySlotSection from '../components/UtilitySlotSection'; import OffenceSummary from '../components/OffenceSummary'; import DefenceSummary from '../components/DefenceSummary'; import MovementSummary from '../components/MovementSummary'; +import DamageDealt from '../components/DamageDealt'; import LineChart from '../components/LineChart'; import PowerManagement from '../components/PowerManagement'; import CostSection from '../components/CostSection'; @@ -348,30 +349,8 @@ export default class OutfittingPage extends Page { -
      - - - - - - - - -
      - - - - - {formats.f2(fuelLevel * fuelCapacity)}{units.T} {formats.pct1(fuelLevel)} -
      +
      +
      @@ -406,4 +385,28 @@ export default class OutfittingPage extends Page { // func={state.speedChartFunc} // /> // - +//
      +// +// +// +// +// +// +// +// +//
      +// +// +// +// +// {formats.f2(fuelLevel * fuelCapacity)}{units.T} {formats.pct1(fuelLevel)} +//
      +//
      diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index a9f7bd07..4236d015 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -587,6 +587,14 @@ export default class Module { return this._getModifiedValue('shieldreinforcement'); } + /** + * Get the piercing for this module, taking in to account modifications + * @return {Number} the piercing for this module + */ + getPiercing() { + return this._getModifiedValue('piercing'); + } + /** * Get the bays for this module, taking in to account modifications * @return {Number} the bays for this module diff --git a/src/less/app.less b/src/less/app.less index 7f4564ec..0da7f358 100755 --- a/src/less/app.less +++ b/src/less/app.less @@ -16,6 +16,7 @@ @import 'tooltip'; @import 'buttons'; @import 'error'; +@import 'shipselector'; @import 'sortable'; @import 'loader'; diff --git a/src/less/shipselector.less b/src/less/shipselector.less new file mode 100755 index 00000000..ab5e9161 --- /dev/null +++ b/src/less/shipselector.less @@ -0,0 +1,218 @@ +.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; + // Less than 600px screen width: hide text + + &.disabled { + color: @warning-disabled; + cursor: default; + } + + &.selected { + background-color: @bgBlack; + } + + .menu-item-label { + margin-left: 1em; + display: inline-block; + + .smallTablet({ + display: none; + }); + } + } + + .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; + } +} From 059c2badf4b69f64f63903f81d4bf34ace7bed76 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 16 Dec 2016 20:38:58 +0000 Subject: [PATCH 18/35] Lint --- src/app/components/DamageDealt.jsx | 3 ++- src/app/components/ShipSelector.jsx | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx index f0095013..6cdf3dd8 100644 --- a/src/app/components/DamageDealt.jsx +++ b/src/app/components/DamageDealt.jsx @@ -32,7 +32,8 @@ export default class DamageDealt extends TranslatedComponent { } /** - * Triggered when the comparator ship changes + * Triggered when the ship changes + * @param {string} s the new ship ID */ _onShipChange(s) { this.setState({ against: Ships[s] }); diff --git a/src/app/components/ShipSelector.jsx b/src/app/components/ShipSelector.jsx index bf1ee0ae..948252bf 100644 --- a/src/app/components/ShipSelector.jsx +++ b/src/app/components/ShipSelector.jsx @@ -41,6 +41,7 @@ export default class ShipSelector extends TranslatedComponent { /** * Handle opening the menu + * @param {string} menu The ID of the opened menu * @param {SyntheticEvent} event Event */ _openMenu(menu, event) { From 32282141cf4f2c5805121fdea31f7fe992012df3 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 16 Dec 2016 21:09:11 +0000 Subject: [PATCH 19/35] Use ship rather than ship ID --- src/app/components/DamageDealt.jsx | 2 +- src/app/components/ShipSelector.jsx | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx index 6cdf3dd8..370dea4d 100644 --- a/src/app/components/DamageDealt.jsx +++ b/src/app/components/DamageDealt.jsx @@ -124,7 +124,7 @@ export default class DamageDealt extends TranslatedComponent { return (

      {translate('damage dealt against')}

      - + diff --git a/src/app/components/ShipSelector.jsx b/src/app/components/ShipSelector.jsx index 948252bf..75a6ee92 100644 --- a/src/app/components/ShipSelector.jsx +++ b/src/app/components/ShipSelector.jsx @@ -9,6 +9,7 @@ import { Rocket } from './SvgIcons'; */ export default class ShipSelector extends TranslatedComponent { static PropTypes = { + initial: React.PropTypes.object.isRequired, onChange: React.PropTypes.func.isRequired }; @@ -19,7 +20,7 @@ export default class ShipSelector extends TranslatedComponent { constructor(props) { super(props); - this.state = { shipId : 'adder' }; + this.state = { ship : this.props.initial }; } /** @@ -58,7 +59,7 @@ export default class ShipSelector extends TranslatedComponent { * @param {string} s The selected ship ID */ _selectShip(s) { - this.setState({ shipId: s }); + this.setState({ ship: Ships[s] }); this.context.openMenu(null); this.props.onChange(s); @@ -70,13 +71,13 @@ export default class ShipSelector extends TranslatedComponent { */ render() { const currentMenu = this.props.currentMenu; - const shipId = this.state.shipId; + const ship = this.state.ship; return (
      - {Ships[shipId].properties.name} + {ship.properties.name} {currentMenu == 'wds' ?
      e.stopPropagation() }> {this._getShipsMenu()} From 6ac69a638821f1207dd7c889bea08cdcf0f1ee01 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 16 Dec 2016 21:16:17 +0000 Subject: [PATCH 20/35] Add translation --- src/app/i18n/en.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index 91c49c19..df51dc92 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -87,8 +87,9 @@ export const terms = { ws: 'Frame Shift Wake Scanner', // Items on the outfitting page - // Notification of restricted slot for Orca/Beluga + // Notification of restricted slot emptyrestricted: 'empty (restricted)', + 'damage dealt against': 'Damage dealt against', // 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page ammunition: 'Ammo', From b8cff0c2fc4883cf5af141a7683ae33941c8e5d9 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sat, 17 Dec 2016 10:46:52 +0000 Subject: [PATCH 21/35] Add 'Damage received' section --- ChangeLog.md | 1 + src/app/components/DamageDealt.jsx | 153 +++++++++++---- src/app/components/DamageReceived.jsx | 259 ++++++++++++++++++++++++++ src/app/i18n/en.js | 3 + src/app/pages/OutfittingPage.jsx | 5 + src/app/shipyard/Ship.js | 10 +- src/less/icons.less | 2 + 7 files changed, 393 insertions(+), 40 deletions(-) create mode 100644 src/app/components/DamageReceived.jsx diff --git a/ChangeLog.md b/ChangeLog.md index 5f7d1c94..509edb97 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -14,6 +14,7 @@ * Version URLs to handle changes to ship specifications over time * Do not include disabled shield boosters in calculations * Add 'Damage dealt' section + * Add 'Damage received' section #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx index 370dea4d..d4ce6b0a 100644 --- a/src/app/components/DamageDealt.jsx +++ b/src/app/components/DamageDealt.jsx @@ -1,9 +1,44 @@ import React from 'react'; -import cn from 'classnames'; import TranslatedComponent from './TranslatedComponent'; import { Ships } from 'coriolis-data/dist'; -import { slotName, slotComparator } from '../utils/SlotFunctions'; import ShipSelector from './ShipSelector'; +import { nameComparator } from '../utils/SlotFunctions'; +import { MountFixed, MountGimballed, MountTurret } from './SvgIcons'; + +/** + * 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 @@ -14,6 +49,8 @@ export default class DamageDealt extends TranslatedComponent { code: React.PropTypes.string.isRequired }; + static DEFAULT_AGAINST = Ships['anaconda']; + /** * Constructor * @param {Object} props React Component properties @@ -27,16 +64,63 @@ export default class DamageDealt extends TranslatedComponent { this.state = { predicate: 'n', desc: true, - against: Ships['anaconda'], + against: DamageDealt.DEFAULT_AGAINST }; } + /** + * Set the initial weapons state + */ + componentWillMount() { + const weapons = this._calcWeapons(this.props.ship, this.state.against); + this.setState({ weapons: weapons }); + } + + /** + * Set the updated weapons state + */ + componentWillReceiveProps(nextProps, nextContext) { + const weapons = this._calcWeapons(this.props.ship, this.state.against); + this.setState({ weapons: weapons }); + return true; + } + + _calcWeapons(ship, against) { + // Create a list of the ship's weapons and include required stats - this is so that we muck around with re-ordering and the like on the fly + let weapons = []; + + for (let i = 0; i < ship.hardpoints.length; i++) { + if (ship.hardpoints[i].m) { + const m = ship.hardpoints[i].m; + const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; + const effectiveness = m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness; + const effectiveDps = m.getDps() * effectiveness; + const effectiveSDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectiveness : effectiveDps; + + weapons.push({id: i, + mount: m.mount, + name: m.name || m.grp, + classRating: classRating, + effectiveDps: effectiveDps, + effectiveSDps: effectiveSDps, + effectiveness: effectiveness}); + } + } + + return weapons; + } + /** * Triggered when the ship changes * @param {string} s the new ship ID */ _onShipChange(s) { - this.setState({ against: Ships[s] }); + const against = Ships[s]; + const weapons = this._calcWeapons(this.props.ship, against); + // This is not the correct 'this' +console.log('1) State against is' + this.state.against.properties.name); + this.setState({ against: against, weapons: weapons }); +console.log('2) State against is' + this.state.against.properties.name); } /** @@ -59,53 +143,52 @@ export default class DamageDealt extends TranslatedComponent { /** * Sorts the weapon list * @param {Ship} ship Ship instance - * @param {Ship} against The ship to compare against * @param {string} predicate Sort predicate * @param {Boolean} desc Sort order descending */ - _sort(ship, against, predicate, desc) { - let weaponList = ship.hardpoints; - let comp = slotComparator.bind(null, this.context.language.translate); + _sort(ship, predicate, desc) { + let comp = weaponComparator.bind(null, this.context.language.translate); switch (predicate) { case 'n': comp = comp(null, desc); break; - case 'd': comp = comp((a, b) => a.m.getDps() - b.m.getDps(), desc); break; - case 'e': comp = comp((a, b) => (a.m.getPiercing() > a.m.hardness ? a.m.getDps() : a.m.getDps() * a.m.getPiercing() / a.m.hardness) - (b.m.getPiercing() > b.m.hardness ? b.m.getDps() : b.m.getDps() * b.m.getPiercing() / b.m.hardness), desc); break; + case 'edps': comp = comp((a, b) => a.effectiveDps - b.effectiveDps, desc); break; + case 'esdps': comp = comp((a, b) => a.effectiveSDps - b.effectiveSDps, desc); break; + case 'e': comp = comp((a, b) => a.effectiveness - b.effectiveness, desc); break; } - weaponList.sort(comp); + this.state.weapons.sort(comp); } /** * Render individual rows for hardpoints * @param {Function} translate Translate function * @param {Object} formats Localised formats map - * @param {Object} ship Our ship - * @param {Object} against The ship against which to compare * @return {array} The individual rows * */ - _renderRows(translate, formats, ship, against) { + _renderRows(translate, formats) { + const { termtip, tooltip } = this.context; + let rows = []; - for (let hardpoint in ship.hardpoints) { - if (ship.hardpoints[hardpoint].m) { - const m = ship.hardpoints[hardpoint].m; - const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; - const effectiveness = m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness; - const effectiveDps = m.getDps() * effectiveness; - const effectiveSDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectiveness : effectiveDps; + if (this.state.weapons) { + for (let i = 0; i < this.state.weapons.length; i++) { + const weapon = this.state.weapons[i]; - rows.push(
      - - - - + rows.push( + + + + ); } } - return rows; } @@ -117,25 +200,23 @@ export default class DamageDealt extends TranslatedComponent { const { language, tooltip, termtip } = this.context; const { formats, translate } = language; - const ship = this.props.ship; - const against = this.state.against; - const hardness = against.properties.hardness; + const sortOrder = this._sortOrder; return (

      {translate('damage dealt against')}

      -
      {classRating} {slotName(translate, ship.hardpoints[hardpoint])}{formats.round1(effectiveDps)}{formats.round1(effectiveSDps)}{formats.pct(effectiveness)}
      + {weapon.mount == 'F' ? : null} + {weapon.mount == 'G' ? : null} + {weapon.mount == 'T' ? : null} + {weapon.classRating} {translate(weapon.name)} + {formats.round1(weapon.effectiveDps)}{formats.round1(weapon.effectiveSDps)}{formats.pct(weapon.effectiveness)}
      +
      - - - - + + + + - {this._renderRows(translate, formats, ship, against)} + {this._renderRows(translate, formats)}
      {translate('weapon')}{translate('effective dps')}{translate('effective sdps')}{translate('effectiveness')}{translate('weapon')}{translate('effective dps')}{translate('effective sdps')}{translate('effectiveness')}
      diff --git a/src/app/components/DamageReceived.jsx b/src/app/components/DamageReceived.jsx new file mode 100644 index 00000000..32b703ad --- /dev/null +++ b/src/app/components/DamageReceived.jsx @@ -0,0 +1,259 @@ +import React from 'react'; +import TranslatedComponent from './TranslatedComponent'; +import { Modules } from 'coriolis-data/dist'; +import { nameComparator } from '../utils/SlotFunctions'; +import { MountFixed, MountGimballed, MountTurret } from './SvgIcons'; + +/** + * 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.state = { + predicate: 'n', + desc: true + }; + } + + /** + * Set the initial weapons state + */ + componentWillMount() { + this.setState({ weapons: this._calcWeapons(this.props.ship) }); + } + + /** + * Set the updated weapons state + */ + componentWillReceiveProps(nextProps, nextContext) { + this.setState({ weapons: this._calcWeapons(nextProps.ship) }); + return true; + } + + _calcWeapons(ship) { + // Create a list of all weapons and include their stats - this is so that we can muck around with re-ordering and the like on the fly + let weapons = []; + + for (let grp in Modules.hardpoints) { + if (Modules.hardpoints[grp][0].damage && Modules.hardpoints[grp][0].type) { + for (let mId in Modules.hardpoints[grp]) { + const m = Modules.hardpoints[grp][mId]; + const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; + + // Basic values + let damage = m.damage; + let rpshot = m.roundspershot || 1; + let rof = m.rof || 1; + + // Base DPS + const baseDps = damage * rpshot * rof; + const baseSDps = m.clip ? (m.clip * baseDps / m.rof) / ((m.clip / m.rof) + m.reload) : baseDps; + + // Effective DPS taking in to account shield resistance + let effectivenessShields = 0; + if (m.type.indexOf('E') != -1) { + effectivenessShields += ship.shieldExplRes; + } + if (m.type.indexOf('K') != -1) { + effectivenessShields += ship.shieldKinRes; + } + if (m.type.indexOf('T') != -1) { + effectivenessShields += ship.shieldThermRes; + } + effectivenessShields /= m.type.length; + // Plasma accelerators deal absolute damage + if (m.grp == 'pa') effectivenessShields = 1; + const effectiveDpsShields = baseDps * effectivenessShields; + const effectiveSDpsShields = baseSDps * effectivenessShields; + + // Effective DPS taking in to account hull hardness and resistance + let effectivenessHull = 0; + if (m.type.indexOf('E') != -1) { + effectivenessHull += ship.hullExplRes; + } + if (m.type.indexOf('K') != -1) { + effectivenessHull += ship.hullKinRes; + } + if (m.type.indexOf('T') != -1) { + effectivenessHull += ship.hullThermRes; + } + effectivenessHull /= m.type.length; + // Plasma accelerators deal absolute damage (but could be reduced by hardness) + if (m.grp == 'pa') effectivenessHull = 1; + effectivenessHull *= Math.min(m.piercing / ship.hardness, 1); + const effectiveDpsHull = baseDps * effectivenessHull; + const effectiveSDpsHull = baseSDps * effectivenessHull; + + weapons.push({id: m.id, + classRating: classRating, + name: m.name || m.grp, + mount: m.mount, + effectiveDpsShields: effectiveDpsShields, + effectiveSDpsShields: effectiveSDpsShields, + effectivenessShields: effectivenessShields, + effectiveDpsHull: effectiveDpsHull, + effectiveSDpsHull: effectiveSDpsHull, + effectivenessHull: effectivenessHull}); + } + } + } + + return weapons; + } + + /** + * 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; + } + + /** + * Render damage received + * @return {React.Component} contents + */ + render() { + const { language, tooltip, termtip } = this.context; + const { formats, translate } = language; + + const sortOrder = this._sortOrder; + + return ( + +

      {translate('damage received by')}

      + + + + + + + + + + + + + + + + + + {this._renderRows(translate, formats)} + +
      {translate('weapon')}{translate('against shields')}{translate('against hull')}
      {translate('DPS')}{translate('SDPS')}{translate('effectiveness')}{translate('DPS')}{translate('SDPS')}{translate('effectiveness')}
      +
      + ); + } +} diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index df51dc92..6ffbe2ab 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -90,6 +90,9 @@ export const terms = { // Notification of restricted slot emptyrestricted: 'empty (restricted)', 'damage dealt against': 'Damage dealt against', + 'damage received by': 'Damage received by', + 'against shields': 'Against shields', + 'against hull': 'Against hull', // 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page ammunition: 'Ammo', diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 4473e57b..847eeb35 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -18,6 +18,7 @@ import OffenceSummary from '../components/OffenceSummary'; import DefenceSummary from '../components/DefenceSummary'; import MovementSummary from '../components/MovementSummary'; import DamageDealt from '../components/DamageDealt'; +import DamageReceived from '../components/DamageReceived'; import LineChart from '../components/LineChart'; import PowerManagement from '../components/PowerManagement'; import CostSection from '../components/CostSection'; @@ -353,6 +354,10 @@ export default class OutfittingPage extends Page { +
      + +
      + ); } diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 0f912e50..efcd7c9b 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -5,7 +5,7 @@ import Module from './Module'; import LZString from 'lz-string'; import * as _ from 'lodash'; import isEqual from 'lodash/lang'; -import { Modifications } from 'coriolis-data/dist'; +import { Ships, Modifications } from 'coriolis-data/dist'; const zlib = require('zlib'); const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh']; @@ -660,7 +660,7 @@ export default class Ship { if (version != 2) { // Alter as required due to changes in the (build) code from one version to the next - this.upgradeInternals(this.id, internal, 1 + this.standard.length + this.hardpoints.length, priorities, enabled, modifications, blueprints, version); + this.upgradeInternals(internal, 1 + this.standard.length + this.hardpoints.length, priorities, enabled, modifications, blueprints, version); } return this.buildWith( @@ -1675,13 +1675,13 @@ export default class Ship { * @param {array} blueprints the existing blueprints arrray * @param {int} version the version of the information */ - upgradeInternals(shipId, internals, offset, priorities, enableds, modifications, blueprints, version) { + upgradeInternals(internals, offset, priorities, enableds, modifications, blueprints, version) { if (version == 1) { // Version 2 reflects the addition of military slots. this means that we need to juggle the internals and their // associated information around to make holes in the appropriate places for (let slotId = 0; slotId < this.internal.length; slotId++) { if (this.internal[slotId].eligible && this.internal[slotId].eligible.mrp) { - // Found an MRP - push all of the existing items down one to compensate for the fact that they didn't exist before now + // Found a restricted military slot - push all of the existing items down one to compensate for the fact that they didn't exist before now internals.push.apply(internals, [0].concat(internals.splice(slotId).slice(0, -1))); const offsetSlotId = offset + slotId; @@ -1693,6 +1693,8 @@ export default class Ship { if (blueprints) { blueprints.push.apply(blueprints, [null].concat(blueprints.splice(offsetSlotId).slice(0, -1))); } } } + // Ensure that all items are the correct length + internals.splice(Ships[this.id].slots.internal.length); } } } diff --git a/src/less/icons.less b/src/less/icons.less index 9af0c0e6..999352a9 100755 --- a/src/less/icons.less +++ b/src/less/icons.less @@ -34,8 +34,10 @@ width: 1.1em; height: 1em; stoke: @fg; + stroke-width: 20; fill: transparent; + &.sm { width: 0.8em; height: 0.75em; From 51d7b6c9aa3b42a3cb4ee8b3571e66689579d6d2 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 18 Dec 2016 09:36:33 +0000 Subject: [PATCH 22/35] Add 'Piercing' information to hardpoints; add 'Hardness' information to ship summary --- ChangeLog.md | 2 ++ src/app/components/DamageDealt.jsx | 41 +++++++++++++++---------- src/app/components/DamageReceived.jsx | 29 ++++++++++------- src/app/components/HardpointSlot.jsx | 1 + src/app/components/ShipSummaryTable.jsx | 2 ++ src/app/pages/ShipyardPage.jsx | 6 ++-- src/app/shipyard/Ship.js | 1 - 7 files changed, 50 insertions(+), 32 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 509edb97..a2d989ce 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -15,6 +15,8 @@ * Do not include disabled shield boosters in calculations * Add 'Damage dealt' section * Add 'Damage received' section + * Add 'Piercing' information to hardpoints + * Add 'Hardness' information to ship summary #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx index d4ce6b0a..551084a5 100644 --- a/src/app/components/DamageDealt.jsx +++ b/src/app/components/DamageDealt.jsx @@ -73,20 +73,30 @@ export default class DamageDealt extends TranslatedComponent { */ componentWillMount() { const weapons = this._calcWeapons(this.props.ship, this.state.against); - this.setState({ weapons: weapons }); + this.setState({ weapons }); } /** - * Set the updated weapons state + * 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) { - const weapons = this._calcWeapons(this.props.ship, this.state.against); - this.setState({ weapons: weapons }); + if (nextProps.code != this.props.code) { + const weapons = this._calcWeapons(this.props.ship, this.state.against); + this.setState({ weapons }); + } return true; } + /** + * 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 + * @return {boolean} Returns the per-weapon damage + */ _calcWeapons(ship, against) { - // Create a list of the ship's weapons and include required stats - this is so that we muck around with re-ordering and the like on the fly let weapons = []; for (let i = 0; i < ship.hardpoints.length; i++) { @@ -97,13 +107,13 @@ export default class DamageDealt extends TranslatedComponent { const effectiveDps = m.getDps() * effectiveness; const effectiveSDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectiveness : effectiveDps; - weapons.push({id: i, - mount: m.mount, - name: m.name || m.grp, - classRating: classRating, - effectiveDps: effectiveDps, - effectiveSDps: effectiveSDps, - effectiveness: effectiveness}); + weapons.push({ id: i, + mount: m.mount, + name: m.name || m.grp, + classRating, + effectiveDps, + effectiveSDps, + effectiveness }); } } @@ -111,16 +121,13 @@ export default class DamageDealt extends TranslatedComponent { } /** - * Triggered when the ship changes + * Triggered when the ship we compare against changes * @param {string} s the new ship ID */ _onShipChange(s) { const against = Ships[s]; const weapons = this._calcWeapons(this.props.ship, against); - // This is not the correct 'this' -console.log('1) State against is' + this.state.against.properties.name); - this.setState({ against: against, weapons: weapons }); -console.log('2) State against is' + this.state.against.properties.name); + this.setState({ against, weapons }); } /** diff --git a/src/app/components/DamageReceived.jsx b/src/app/components/DamageReceived.jsx index 32b703ad..9760e23e 100644 --- a/src/app/components/DamageReceived.jsx +++ b/src/app/components/DamageReceived.jsx @@ -72,14 +72,21 @@ export default class DamageReceived extends TranslatedComponent { /** * 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) { this.setState({ weapons: this._calcWeapons(nextProps.ship) }); return true; } + /** + * Calculate the damage received by a ship + * @param {Object} ship The ship which will receive the damage + * @return {boolean} Returns the per-weapon damage + */ _calcWeapons(ship) { - // Create a list of all weapons and include their stats - this is so that we can muck around with re-ordering and the like on the fly let weapons = []; for (let grp in Modules.hardpoints) { @@ -132,16 +139,16 @@ export default class DamageReceived extends TranslatedComponent { const effectiveDpsHull = baseDps * effectivenessHull; const effectiveSDpsHull = baseSDps * effectivenessHull; - weapons.push({id: m.id, - classRating: classRating, - name: m.name || m.grp, - mount: m.mount, - effectiveDpsShields: effectiveDpsShields, - effectiveSDpsShields: effectiveSDpsShields, - effectivenessShields: effectivenessShields, - effectiveDpsHull: effectiveDpsHull, - effectiveSDpsHull: effectiveSDpsHull, - effectivenessHull: effectivenessHull}); + weapons.push({ id: m.id, + classRating, + name: m.name || m.grp, + mount: m.mount, + effectiveDpsShields, + effectiveSDpsShields, + effectivenessShields, + effectiveDpsHull, + effectiveSDpsHull, + effectivenessHull }); } } } diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index 6b4b28eb..5941f805 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -76,6 +76,7 @@ export default class HardpointSlot extends Slot { { m.getRange() ?
      {translate('range')} {formats.f1(m.getRange() / 1000)}{u.km}
      : null } { m.getShieldBoost() ?
      +{formats.pct1(m.getShieldBoost())}
      : null } { m.getAmmo() ?
      {translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}
      : null } + { m.getPiercing() ?
      {translate('piercing')}: {formats.int(m.getPiercing())}
      : null } { m.getJitter() ?
      {translate('jitter')}: {formats.f2(m.getJitter())}°
      : null } { showModuleResistances && m.getExplosiveResistance() ?
      {translate('explres')}: {formats.pct(m.getExplosiveResistance())}
      : null } { showModuleResistances && m.getKineticResistance() ?
      {translate('kinres')}: {formats.pct(m.getKineticResistance())}
      : null } diff --git a/src/app/components/ShipSummaryTable.jsx b/src/app/components/ShipSummaryTable.jsx index 9ab15a9d..54c66e87 100644 --- a/src/app/components/ShipSummaryTable.jsx +++ b/src/app/components/ShipSummaryTable.jsx @@ -43,6 +43,7 @@ export default class ShipSummaryTable extends TranslatedComponent { {translate('DPS')} {translate('EPS')} {translate('HPS')} + {translate('hardness')} {translate('armour')} {translate('shields')} {translate('mass')} @@ -71,6 +72,7 @@ export default class ShipSummaryTable extends TranslatedComponent { {f1(ship.totalDps)} {f1(ship.totalEps)} {f1(ship.totalHps)} + {int(ship.hardness)} {int(ship.armour)} {int(ship.shield)} {u.MJ} {ship.hullMass} {u.T} diff --git a/src/app/pages/ShipyardPage.jsx b/src/app/pages/ShipyardPage.jsx index d579d884..24a08a08 100644 --- a/src/app/pages/ShipyardPage.jsx +++ b/src/app/pages/ShipyardPage.jsx @@ -142,10 +142,10 @@ export default class ShipyardPage extends Page { {s.manufacturer} {translate(SizeMap[s.class])} {fInt(s.agility)} + {fInt(s.hardness)} {fInt(s.speed)}{u['m/s']} {fInt(s.boost)}{u['m/s']} {fInt(s.baseArmour)} - {fInt(s.hardness)} {fInt(s.baseShieldStrength)}{u.MJ} {fInt(s.topSpeed)}{u['m/s']} {fInt(s.topBoost)}{u['m/s']} @@ -270,7 +270,8 @@ export default class ShipyardPage extends Page { {translate('manufacturer')} {translate('size')} {translate('agility')} - {translate('base')} + {translate('hardness')} + {translate('base')} {translate('max')} {translate('core module classes')} {translate('hardpoints')} @@ -283,7 +284,6 @@ export default class ShipyardPage extends Page { {translate('speed')} {translate('boost')} {translate('armour')} - {translate('hardness')} {translate('shields')} {translate('speed')} diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index efcd7c9b..34f54f84 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -1666,7 +1666,6 @@ export default class Ship { /** * Upgrade information about internals with version changes - * @param {int} shipId the ID of the ship * @param {array} internals the internals from the ship code * @param {int} offset the offset of the internals information in the priorities etc. arrays * @param {array} priorities the existing priorities arrray From 5bf907809d91d707b56ac5421dee3bba0e076152 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 18 Dec 2016 21:26:30 +0000 Subject: [PATCH 23/35] Make weapons real modules to benefits from standard DPS etc. calculations --- src/app/components/DamageReceived.jsx | 30 ++++++++++++--------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/app/components/DamageReceived.jsx b/src/app/components/DamageReceived.jsx index 9760e23e..275f4029 100644 --- a/src/app/components/DamageReceived.jsx +++ b/src/app/components/DamageReceived.jsx @@ -3,6 +3,7 @@ import TranslatedComponent from './TranslatedComponent'; import { Modules } from 'coriolis-data/dist'; import { nameComparator } from '../utils/SlotFunctions'; import { MountFixed, MountGimballed, MountTurret } from './SvgIcons'; +import Module from '../shipyard/Module'; /** * Generates an internationalization friendly weapon comparator that will @@ -92,30 +93,25 @@ export default class DamageReceived extends TranslatedComponent { for (let grp in Modules.hardpoints) { if (Modules.hardpoints[grp][0].damage && Modules.hardpoints[grp][0].type) { for (let mId in Modules.hardpoints[grp]) { - const m = Modules.hardpoints[grp][mId]; + const m = new Module(Modules.hardpoints[grp][mId]); const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; - // Basic values - let damage = m.damage; - let rpshot = m.roundspershot || 1; - let rof = m.rof || 1; - // Base DPS - const baseDps = damage * rpshot * rof; - const baseSDps = m.clip ? (m.clip * baseDps / m.rof) / ((m.clip / m.rof) + m.reload) : baseDps; + const baseDps = m.getDps(); + const baseSDps = m.getClip() ? (m.getClip() * baseDps / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : baseDps; // Effective DPS taking in to account shield resistance let effectivenessShields = 0; - if (m.type.indexOf('E') != -1) { + if (m.getDamageType().indexOf('E') != -1) { effectivenessShields += ship.shieldExplRes; } - if (m.type.indexOf('K') != -1) { + if (m.getDamageType().indexOf('K') != -1) { effectivenessShields += ship.shieldKinRes; } - if (m.type.indexOf('T') != -1) { + if (m.getDamageType().indexOf('T') != -1) { effectivenessShields += ship.shieldThermRes; } - effectivenessShields /= m.type.length; + effectivenessShields /= m.getDamageType().length; // Plasma accelerators deal absolute damage if (m.grp == 'pa') effectivenessShields = 1; const effectiveDpsShields = baseDps * effectivenessShields; @@ -123,19 +119,19 @@ export default class DamageReceived extends TranslatedComponent { // Effective DPS taking in to account hull hardness and resistance let effectivenessHull = 0; - if (m.type.indexOf('E') != -1) { + if (m.getDamageType().indexOf('E') != -1) { effectivenessHull += ship.hullExplRes; } - if (m.type.indexOf('K') != -1) { + if (m.getDamageType().indexOf('K') != -1) { effectivenessHull += ship.hullKinRes; } - if (m.type.indexOf('T') != -1) { + if (m.getDamageType().indexOf('T') != -1) { effectivenessHull += ship.hullThermRes; } - effectivenessHull /= m.type.length; + effectivenessHull /= m.getDamageType().length; // Plasma accelerators deal absolute damage (but could be reduced by hardness) if (m.grp == 'pa') effectivenessHull = 1; - effectivenessHull *= Math.min(m.piercing / ship.hardness, 1); + effectivenessHull *= Math.min(m.getPiercing() / ship.hardness, 1); const effectiveDpsHull = baseDps * effectivenessHull; const effectiveSDpsHull = baseSDps * effectivenessHull; From f82122f29f968cd55859be25137b558def56e8a5 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 18 Dec 2016 21:37:21 +0000 Subject: [PATCH 24/35] Add module copy functionality - drag module whilst holding 'alt' to copy --- ChangeLog.md | 1 + src/app/components/HardpointSlot.jsx | 2 +- src/app/components/InternalSlot.jsx | 2 +- src/app/components/SlotSection.jsx | 36 ++++++++++++++++++---------- src/app/components/StandardSlot.jsx | 2 +- src/app/pages/AboutPage.jsx | 9 ------- src/app/shipyard/Module.js | 26 +++++++++++++------- 7 files changed, 45 insertions(+), 33 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index a2d989ce..db6b395f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -17,6 +17,7 @@ * Add 'Damage received' section * Add 'Piercing' information to hardpoints * Add 'Hardness' information to ship summary + * Add module copy functionality - drag module whilst holding 'alt' to copy #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index 5941f805..cc1d80ac 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -46,7 +46,7 @@ export default class HardpointSlot extends Slot { // Modifications tooltip shows blueprint and grade, if available let modTT = translate('modified'); - if (m && m.blueprint) { + if (m && m.blueprint && m.blueprint.name) { modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; if (m.blueprint.special && m.blueprint.special.id) { modTT += ', ' + translate(m.blueprint.special.name); diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index cfd6e7ff..cfa066b4 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -28,7 +28,7 @@ export default class InternalSlot extends Slot { // Modifications tooltip shows blueprint and grade, if available let modTT = translate('modified'); - if (m && m.blueprint) { + if (m && m.blueprint && m.blueprint.name) { modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; } diff --git a/src/app/components/SlotSection.jsx b/src/app/components/SlotSection.jsx index 4a339a33..d0789448 100644 --- a/src/app/components/SlotSection.jsx +++ b/src/app/components/SlotSection.jsx @@ -77,7 +77,7 @@ export default class SlotSection extends TranslatedComponent { _drag(originSlot, e) { e.dataTransfer.setData('text/html', e.currentTarget); e.dataTransfer.effectAllowed = 'all'; - this.setState({ originSlot }); + this.setState({ originSlot, copy: e.getModifierState('Alt') }); this._close(); } @@ -91,7 +91,9 @@ export default class SlotSection extends TranslatedComponent { e.stopPropagation(); let os = this.state.originSlot; if (os) { - e.dataTransfer.dropEffect = os != targetSlot && canMount(this.props.ship, targetSlot, os.m.grp, os.m.class) ? 'copyMove' : 'none'; + // Show correct icon + const effect = this.state.copy ? 'copy' : 'move'; + e.dataTransfer.dropEffect = os != targetSlot && canMount(this.props.ship, targetSlot, os.m.grp, os.m.class) ? effect : 'none'; this.setState({ targetSlot }); } else { e.dataTransfer.dropEffect = 'none'; @@ -114,20 +116,30 @@ export default class SlotSection extends TranslatedComponent { * the origin slot will be empty. */ _drop() { - let { originSlot, targetSlot } = this.state; + let { originSlot, targetSlot, copy } = this.state; let m = originSlot.m; - if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) { - // Swap modules if possible - 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 + if (copy) { + // We want to copy the module in to the target slot + if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) { + const mCopy = m.clone(); + this.props.ship.use(targetSlot, mCopy); + this.props.onChange(); + } + } else { + // We want to move the module in to the target slot, and swap back any module that was originally in the target slot + if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) { + // Swap modules if possible + 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 + } + this.props.ship.use(targetSlot, m); // update target slot + this.props.onChange(); } - this.props.ship.use(targetSlot, m); // update target slot - this.props.onChange(); } - this.setState({ originSlot: null, targetSlot: null }); + this.setState({ originSlot: null, targetSlot: null, copy: null }); } /** diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index 9bd56da9..ebbd5485 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -52,7 +52,7 @@ export default class StandardSlot extends TranslatedComponent { // Modifications tooltip shows blueprint and grade, if available let modTT = translate('modified'); - if (m && m.blueprint) { + if (m && m.blueprint && m.blueprint.name) { modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; } diff --git a/src/app/pages/AboutPage.jsx b/src/app/pages/AboutPage.jsx index 77579786..7cfaa1fd 100644 --- a/src/app/pages/AboutPage.jsx +++ b/src/app/pages/AboutPage.jsx @@ -39,15 +39,6 @@ export default class AboutPage extends Page {

      Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.

      -
      - - - - -
      - -

      Help keep the lights on! Donations will be used to cover costs of running and maintaining Coriolis. Thanks for helping!

      -

      Chat

      You can chat to us on our EDCD Discord server.

      ; diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 4236d015..37879de6 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -12,20 +12,28 @@ export default class Module { * @param {Object} params Module parameters. Either grp/id or template */ constructor(params) { - let properties = Object.assign({ grp: null, id: null, template: null, }, params); + let properties = Object.assign({ grp: null, id: null, template: null }, params); - let template; - if (properties.template == undefined) { - return ModuleUtils.findModule(properties.grp, properties.id); + if (properties.class != undefined) { + // We already have a fully-formed module; copy the data over + for (let p in properties) { this[p] = properties[p]; } + } else if (properties.template != undefined) { + // We have a template from coriolis-data; copy the data over + for (let p in properties.template) { this[p] = properties.template[p]; } } else { - template = properties.template; - if (template) { - // Copy all properties from coriolis-data template - for (let p in template) { this[p] = template[p]; } - } + // We don't have a template; find it given the group and ID + return ModuleUtils.findModule(properties.grp, properties.id); } } + /** + * Clone an existing module + * @return {Object} A clone of the existing module + */ + clone() { + return new Module(JSON.parse(JSON.stringify(this))); + } + /** * Get a value for a given modification * @param {Number} name The name of the modification From 2e42a328e0199a0bea4f334c78e84de35ad14c76 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 19 Dec 2016 17:56:54 +0000 Subject: [PATCH 25/35] Add base resistances to defence summary tooltip --- ChangeLog.md | 1 + src/app/components/DefenceSummary.jsx | 31 +++++++++++++++++++++------ src/app/shipyard/Ship.js | 9 ++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index db6b395f..13eb8550 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,6 +18,7 @@ * Add 'Piercing' information to hardpoints * Add 'Hardness' information to ship summary * Add module copy functionality - drag module whilst holding 'alt' to copy + * Add base resistances to defence summary tooltip #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/components/DefenceSummary.jsx b/src/app/components/DefenceSummary.jsx index 94831068..a20b1bb5 100644 --- a/src/app/components/DefenceSummary.jsx +++ b/src/app/components/DefenceSummary.jsx @@ -29,6 +29,8 @@ export default class DefenceSummary extends TranslatedComponent { let { formats, translate, units } = language; let hide = tooltip.bind(null, null); + const shieldGenerator = ship.findShieldGenerator(); + return (

      {translate('defence summary')}

      @@ -48,9 +50,18 @@ export default class DefenceSummary extends TranslatedComponent { {ship.shield ? {translate('damage from')} - {formats.pct1(ship.shieldExplRes || 1)} - {formats.pct1(ship.shieldKinRes || 1)} - {formats.pct1(ship.shieldThermRes || 1)} + +   + {formats.pct1(ship.shieldExplRes || 1)} + + +   + {formats.pct1(ship.shieldKinRes || 1)} + + +   + {formats.pct1(ship.shieldThermRes || 1)} + : null } { ship.shield && ship.shieldCells ? @@ -63,9 +74,17 @@ export default class DefenceSummary extends TranslatedComponent { {translate('damage from')} - {formats.pct1(ship.hullExplRes || 1)} - {formats.pct1(ship.hullKinRes || 1)} - {formats.pct1(ship.hullThermRes || 1)} + +   + {formats.pct1(ship.hullExplRes || 1)} + +   + {formats.pct1(ship.hullKinRes || 1)} + + +   + {formats.pct1(ship.hullThermRes || 1)} + {ship.modulearmour > 0 ? diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 34f54f84..9778fae2 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -295,6 +295,15 @@ export default class Ship { } } + /** + * Find the shield generator for this ship + * @return {object} The shield generator module for this ship + */ + findShieldGenerator() { + const slot = this.internal.find(slot => slot.m && ModuleUtils.isShieldGenerator(slot.m.grp)); + return slot ? slot.m : undefined; + } + /** * Serializes the ship to a string * @return {String} Serialized ship 'code' From bb7db144d60f691c6c3112431822753faef8807b Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 19 Dec 2016 21:50:53 +0000 Subject: [PATCH 26/35] Allow collapse/expand of damage sections --- src/app/components/DamageDealt.jsx | 20 +++++++++--- src/app/components/DamageReceived.jsx | 20 +++++++++--- src/app/components/SvgIcons.jsx | 46 +++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx index 551084a5..01ba84b2 100644 --- a/src/app/components/DamageDealt.jsx +++ b/src/app/components/DamageDealt.jsx @@ -3,7 +3,7 @@ import TranslatedComponent from './TranslatedComponent'; import { Ships } from 'coriolis-data/dist'; import ShipSelector from './ShipSelector'; import { nameComparator } from '../utils/SlotFunctions'; -import { MountFixed, MountGimballed, MountTurret } from './SvgIcons'; +import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons'; /** * Generates an internationalization friendly weapon comparator that will @@ -60,11 +60,13 @@ export default class DamageDealt extends TranslatedComponent { this._sort = this._sort.bind(this); this._onShipChange = this._onShipChange.bind(this); + this._onCollapseExpand = this._onCollapseExpand.bind(this); this.state = { predicate: 'n', desc: true, - against: DamageDealt.DEFAULT_AGAINST + against: DamageDealt.DEFAULT_AGAINST, + expanded: false }; } @@ -120,6 +122,13 @@ export default class DamageDealt extends TranslatedComponent { return weapons; } + /** + * 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 @@ -206,12 +215,15 @@ export default class DamageDealt extends TranslatedComponent { render() { const { language, tooltip, termtip } = this.context; const { formats, translate } = language; + const { expanded } = this.state; const sortOrder = this._sortOrder; + const onCollapseExpand = this._onCollapseExpand; return ( -

      {translate('damage dealt against')}

      +

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

      + {expanded ? @@ -225,7 +237,7 @@ export default class DamageDealt extends TranslatedComponent { {this._renderRows(translate, formats)} -
      +
      : null }
      ); } diff --git a/src/app/components/DamageReceived.jsx b/src/app/components/DamageReceived.jsx index 275f4029..f3d5e637 100644 --- a/src/app/components/DamageReceived.jsx +++ b/src/app/components/DamageReceived.jsx @@ -2,7 +2,7 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import { Modules } from 'coriolis-data/dist'; import { nameComparator } from '../utils/SlotFunctions'; -import { MountFixed, MountGimballed, MountTurret } from './SvgIcons'; +import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons'; import Module from '../shipyard/Module'; /** @@ -57,10 +57,12 @@ export default class DamageReceived extends TranslatedComponent { super(props); this._sort = this._sort.bind(this); + this._onCollapseExpand = this._onCollapseExpand.bind(this); this.state = { predicate: 'n', - desc: true + desc: true, + expanded: false }; } @@ -152,6 +154,13 @@ export default class DamageReceived extends TranslatedComponent { 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 @@ -230,12 +239,15 @@ export default class DamageReceived extends TranslatedComponent { render() { const { language, tooltip, termtip } = this.context; const { formats, translate } = language; + const { expanded } = this.state; const sortOrder = this._sortOrder; + const onCollapseExpand = this._onCollapseExpand; return ( -

      {translate('damage received by')}

      +

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

      + {expanded ? @@ -255,7 +267,7 @@ export default class DamageReceived extends TranslatedComponent { {this._renderRows(translate, formats)} -
      +
      : null }
      ); } diff --git a/src/app/components/SvgIcons.jsx b/src/app/components/SvgIcons.jsx index b762fbe0..6093b050 100644 --- a/src/app/components/SvgIcons.jsx +++ b/src/app/components/SvgIcons.jsx @@ -475,6 +475,52 @@ export class MountTurret extends SvgIcon { } } +/** + * Collapse section + */ +export class CollapseSection 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 + + + + + ; + } +} + +/** + * Expand section + */ +export class ExpandSection 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 + + + + + ; + } +} + /** * Rocket ship */ From e53ffd02734b228cd29e3daf79427f1732b53c5e Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Tue, 20 Dec 2016 13:58:00 +0000 Subject: [PATCH 27/35] Update shield recovery/regeneration calculations --- ChangeLog.md | 1 + src/app/components/InternalSlot.jsx | 2 ++ src/app/shipyard/Ship.js | 25 +++++++++++-------------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 13eb8550..2429a876 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -19,6 +19,7 @@ * Add 'Hardness' information to ship summary * Add module copy functionality - drag module whilst holding 'alt' to copy * Add base resistances to defence summary tooltip + * Update shield recovery/regeneration calculations #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index cfa066b4..1bb7bf4b 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -64,6 +64,8 @@ export default class InternalSlot extends Slot { { showModuleResistances && m.getExplosiveResistance() ?
      {translate('explres')}: {formats.pct(m.getExplosiveResistance())}
      : null } { showModuleResistances && m.getKineticResistance() ?
      {translate('kinres')}: {formats.pct(m.getKineticResistance())}
      : null } { showModuleResistances && m.getThermalResistance() ?
      {translate('thermres')}: {formats.pct(m.getThermalResistance())}
      : null } + { m.getRegenerationRate() ?
      {translate('regen')}: {formats.round1(m.getRegenerationRate())}{u.ps}
      : null } + { m.getBrokenRegenerationRate() ?
      {translate('brokenregen')}: {formats.round1(m.getBrokenRegenerationRate())}{u.ps}
      : null } { m && validMods.length > 0 ?
      : null } diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 9778fae2..22fd21d9 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -201,13 +201,11 @@ export default class Ship { * @return {Number} Recovery time in seconds */ calcShieldRecovery() { - if (this.shield > 0) { - const sgSlot = this.findInternalByGroup('sg'); - if (sgSlot != null) { - let brokenRegenRate = 1 + sgSlot.m.getModValue('brokenregen') / 10000; - // 50% of shield strength / recovery recharge rate + 15 second delay before recharge starts - return ((this.shield / 2) / (sgSlot.m.recover * brokenRegenRate)) + 15; - } + 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; } @@ -219,13 +217,12 @@ export default class Ship { * @return {Number} 50 - 100% Recharge time in seconds */ calcShieldRecharge() { - if (this.shield > 0) { - const sgSlot = this.findInternalByGroup('sg'); - if (sgSlot != null) { - let regenRate = 1 + sgSlot.m.getModValue('regen') / 10000; - // 50% -> 100% recharge time, Bi-Weave shields charge at 1.8 MJ/s - return (this.shield / 2) / ((sgSlot.m.grp == 'bsg' ? 1.8 : 1) * regenRate); - } + const shieldGenerator = this.findShieldGenerator(); + if (shieldGenerator) { + const regenRate = shieldGenerator.getRegenerationRate(); + + // 50% of shield strength / recharge rate + return (this.shield / 2) / regenRate; } return 0; } From 0ce8bfac7936803a44cee4cd5692eff373431aca Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sat, 24 Dec 2016 20:56:37 +0000 Subject: [PATCH 28/35] Fixes for #46 --- src/less/app.less | 1 + src/less/header.less | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/less/app.less b/src/less/app.less index 0da7f358..dfce7113 100755 --- a/src/less/app.less +++ b/src/less/app.less @@ -42,6 +42,7 @@ div, a, li { #coriolis { width: 100%; height: 100%; + padding-top: 48px; overflow-y: scroll; -webkit-overflow-scrolling: touch; } diff --git a/src/less/header.less b/src/less/header.less index 510e61a3..ceab8a36 100755 --- a/src/less/header.less +++ b/src/less/header.less @@ -20,7 +20,10 @@ header { line-height: 3em; font-family: @fTitle; vertical-align: middle; - position: relative; + position: absolute; + top: 0px; + left: 0px; + width: 100%; z-index: 2; .user-select-none(); @@ -186,6 +189,7 @@ header { } .no-wrap { + overflow-x: auto; white-space: nowrap; } @@ -200,4 +204,4 @@ header { margin:0px; text-transform: uppercase; } -} \ No newline at end of file +} From 091789c8195cd08bf0a7e38d1c600f202428612f Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sat, 24 Dec 2016 20:57:51 +0000 Subject: [PATCH 29/35] Revert overloading of 'reload' translation key --- src/app/i18n/en.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index 6ffbe2ab..0d0496de 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -155,7 +155,7 @@ export const terms = { range: 'Range', ranget: 'Range', // Range in time (for FSD interdictor) regen: 'Regeneration rate', - reload: 'Reload time', + reload: 'Reload', rof: 'Rate of fire', shield: 'Shield', shieldboost: 'Shield boost', From 5f036c586cdeb0ef63e0c69dd43857116d6cfe96 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sat, 24 Dec 2016 21:57:11 +0000 Subject: [PATCH 30/35] Use own URL shortener --- ChangeLog.md | 2 ++ src/app/utils/ShortenUrl.js | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 2429a876..d25c19f2 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,6 +20,8 @@ * Add module copy functionality - drag module whilst holding 'alt' to copy * Add base resistances to defence summary tooltip * Update shield recovery/regeneration calculations + * Pin menu to top of page + * Switch to custom shortlink method to avoid google length limitations #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/utils/ShortenUrl.js b/src/app/utils/ShortenUrl.js index e1db8eb3..f11efaa8 100644 --- a/src/app/utils/ShortenUrl.js +++ b/src/app/utils/ShortenUrl.js @@ -1,17 +1,21 @@ import request from 'superagent'; -const SHORTEN_API = 'https://www.googleapis.com/urlshortener/v1/url?key='; +export default function shorternUrl(url, success, error) { + shortenUrlEddp(url, success, error); +} + +const SHORTEN_API_GOOGLE = 'https://www.googleapis.com/urlshortener/v1/url?key='; /** * Shorten a URL using Google's URL shortener API * @param {string} url The URL to shorten * @param {function} success Success callback * @param {function} error Failure/Error callback */ -export default function shortenUrl(url, success, error) { +function shortenUrlGoogle(url, success, error) { if (window.navigator.onLine) { try { - request.post(SHORTEN_API + window.CORIOLIS_GAPI_KEY) + request.post(SHORTEN_API_GOOGLE + window.CORIOLIS_GAPI_KEY) .send({ longUrl: url }) .end(function(err, response) { if (err) { @@ -27,3 +31,30 @@ export default function shortenUrl(url, success, error) { error('Not Online'); } } + +const SHORTEN_API_EDDP = 'http://eddp.co/u'; +/** + * Shorten a URL using EDDP's URL shortener API + * @param {string} url The URL to shorten + * @param {function} success Success callback + * @param {function} error Failure/Error callback + */ +function shortenUrlEddp(url, success, error) { + if (window.navigator.onLine) { + try { + request.post(SHORTEN_API_EDDP) + .send(url) + .end(function(err, response) { + if (err) { + error('Bad Request'); + } else { + success(response.header['location']); + } + }); + } catch (e) { + error(e.message ? e.message : e); + } + } else { + error('Not Online'); + } +} From b40a2e96e04bf68240bf77a480828e918b7b9671 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sat, 24 Dec 2016 23:49:33 +0000 Subject: [PATCH 31/35] Fix for change to military slots --- __tests__/fixtures/expected-builds.json | 14 +++++++------- __tests__/test-import.js | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/__tests__/fixtures/expected-builds.json b/__tests__/fixtures/expected-builds.json index 8528cf1c..6cfc0f68 100644 --- a/__tests__/fixtures/expected-builds.json +++ b/__tests__/fixtures/expected-builds.json @@ -9,7 +9,7 @@ "Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==." }, "federal_dropship": { - "Cargo": "A0pdtiFflnddsif4-1717------05040448---020201.Iw18RQ==.Aw18RQ==." + "Cargo": "A0pdtiFflnddsif4-1717------05040448--020201.Iw18eQ==.Aw18eQ==." }, "asp": { "Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==." @@ -29,17 +29,17 @@ "Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==." }, "anaconda": { - "Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04--0303326b.AwRj4yo5dqg=.MwBhCYy6dvvARiA=.", - "Cargo": "A0patnFklndnsxf5----------------06050505040404--45030301.Iw18ZXEA.Aw18ZXEA.", - "Current": "A0patnFklndksxf5----------------06050505040404--03034524.Iw18ZXEA.Aw18ZXEA.", - "Explorer": "A0patnFklndksxf5--------0202------f7050505040s37--2f2i4524.AwRj4yVKJug=.AwhMIyumIRhEA===.", - "Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04----0303326b.Iw18ZXEA.Aw18ZXEA." + "Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b.AwRj4yo5dyg=.MwBhCYy6duvARiA=.", + "Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301.Iw18ZVA=.Aw18ZVA=.", + "Current": "A0patnFklndksxf5----------------06050505040404-03034524.Iw18ZVA=.Aw18ZVA=.", + "Explorer": "A0patnFklndksxf5--------0202------f7050505040s37-2f2i4524.AwRj4yVKJ9hA.AwhMIyumQRhEA===.", + "Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=." }, "diamondback_explorer": { "Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f.AwRj4zTI.AwiMIypI." }, "vulture": { - "Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a--5d27662j.AwRj4yOI.MwBhBYy7oJmAjLIA." + "Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA." }, "fer_de_lance": { "Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA." diff --git a/__tests__/test-import.js b/__tests__/test-import.js index 3081a819..3dab44fa 100644 --- a/__tests__/test-import.js +++ b/__tests__/test-import.js @@ -142,7 +142,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04----0303326b.AwRj4zNLZI%3D%3D.CwBhCYzBGW9qCTSq15xA.&bn=Test%20My%20Ship'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.&bn=Test%20My%20Ship'); }); it('catches an invalid build', function() { @@ -167,7 +167,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04----0303326b.AwRj4zNLZI%3D%3D.CwBhCYzBGW9qCTSq15xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMcAABTINwTEgAAAA%3D%3D&bn=Test%20My%20Ship'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMcAABTINwTEgAAAA%3D%3D&bn=Test%20My%20Ship'); }); }); @@ -238,7 +238,7 @@ describe('Import Modal', function() { expect(modal.state.singleBuild).toBe(true); clickProceed(); expect(MockRouter.go.mock.calls.length).toBe(1); - expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr------.AwRj4zNapI%3D%3D.CwRgDBldUExuBiZA.H4sIAAAAAAAAA02SP0vDUBTFb1qTtE3xtTFqav0Tbfy3KHRQB8FRO7gJOuioU%2FEDODgILrqKn0DQQUEUB3Hr0smhYhcp%2BiEEEVvf9VwhmuVw3zu%2Fe959eTH0EhF9G5A%2ByyRSl8yc2saSE7pPrCSkt24RlVyPyL9JABpuM3uzmtk9hs1JPSAk2sk9deHvfjH7XprIq6KHTb0YJY3bDtHEA8rROqCxHYSkzzvMmRcs1RzSOaXXo5k6I5DCnk1kNj6YrQoa3TM4%2Fip6OKM3ouBOFmJWcabl%2BURD10jKLWCvVN0k6m%2BBngqCYI2d%2Fx6zlgG%2BXwR%2B2RXhn3DUPcbibIw8%2Bg0WsibkvJBXqFRZLo1Lkl%2FBWKz0cjS7vwJxmijzIqGIOpLgXAxqQ51bgURCEfUkUD4GvQv0KJBIKKK6wYwcpHCmGyNfcW3nWUhCFUqlDiWuJwbN4EpOC35eJBRRDhj29erfk28h2rmQJGkKv7CZKH0yF08QZ70B8bbxAbigK1Fw8IH%2Fwp6GP9nE0qjLaw7G%2FDs8mt0QP4m1UZafh38AuKZDe4MCAAA%3D&bn=Imported%20Federal%20Corvette'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr-----.AwRj4zNaqA%3D%3D.CwRgDBldUExuBjpA.H4sIAAAAAAAAA02SP0vDUBTFb1qTtE3xtTFqav0Tbfy3KHRQB8FRO7gJOuioU%2FEDODgILrqKn0DQQUEUB3Hr0smhYhcp%2BiEEEVvf9VwhmuVw3zu%2Fe959eTH0EhF9G5A%2ByyRSl8yc2saSE7pPrCSkt24RlVyPyL9JABpuM3uzmtk9hs1JPSAk2sk9deHvfjH7XprIq6KHTb0YJY3bDtHEA8rROqCxHYSkzzvMmRcs1RzSOaXXo5k6I5DCnk1kNj6YrQoa3TM4%2Fip6OKM3ouBOFmJWcabl%2BURD10jKLWCvVN0k6m%2BBngqCYI2d%2Fx6zlgG%2BXwR%2B2RXhn3DUPcbibIw8%2Bg0WsibkvJBXqFRZLo1Lkl%2FBWKz0cjS7vwJxmijzIqGIOpLgXAxqQ51bgURCEfUkUD4GvQv0KJBIKKK6wYwcpHCmGyNfcW3nWUhCFUqlDiWuJwbN4EpOC35eJBRRDhj29erfk28h2rmQJGkKv7CZKH0yF08QZ70B8bbxAbigK1Fw8IH%2Fwp6GP9nE0qjLaw7G%2FDs8mt0QP4m1UZafh38AuKZDe4MCAAA%3D&bn=Imported%20Federal%20Corvette'); }); it('imports a valid v4 build', function() { From be1bfeb6f34811ca7d2f964119ad688c43f9d428 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 2 Jan 2017 12:05:22 +0000 Subject: [PATCH 32/35] Ensure that information is not lost on narrow screens. Fix for #48 --- src/less/app.less | 1 + src/less/header.less | 1 + 2 files changed, 2 insertions(+) diff --git a/src/less/app.less b/src/less/app.less index dfce7113..ccff47d7 100755 --- a/src/less/app.less +++ b/src/less/app.less @@ -45,6 +45,7 @@ div, a, li { padding-top: 48px; overflow-y: scroll; -webkit-overflow-scrolling: touch; + box-sizing: border-box; } .page { diff --git a/src/less/header.less b/src/less/header.less index ceab8a36..07a24a5c 100755 --- a/src/less/header.less +++ b/src/less/header.less @@ -25,6 +25,7 @@ header { left: 0px; width: 100%; z-index: 2; + box-sizing: border-box; .user-select-none(); .menu { From 40f213c8830b2feddb751ca3c1ac4c80fd0dac14 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 2 Jan 2017 12:05:55 +0000 Subject: [PATCH 33/35] Do not lose ship selector selection on narrow screens --- src/less/shipselector.less | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/less/shipselector.less b/src/less/shipselector.less index ab5e9161..5764bdc5 100755 --- a/src/less/shipselector.less +++ b/src/less/shipselector.less @@ -32,25 +32,6 @@ cursor: pointer; color: @warning; text-transform: uppercase; - // Less than 600px screen width: hide text - - &.disabled { - color: @warning-disabled; - cursor: default; - } - - &.selected { - background-color: @bgBlack; - } - - .menu-item-label { - margin-left: 1em; - display: inline-block; - - .smallTablet({ - display: none; - }); - } } .menu-list { From 7855d0e17199d6079e8d5be6a54ecde07d885359 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 2 Jan 2017 12:06:44 +0000 Subject: [PATCH 34/35] Reinstate jump range graph --- ChangeLog.md | 3 +++ src/app/pages/OutfittingPage.jsx | 41 ++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index d25c19f2..4209abda 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -22,6 +22,9 @@ * Update shield recovery/regeneration calculations * Pin menu to top of page * Switch to custom shortlink method to avoid google length limitations + * Ensure that information is not lost on narrow screens + * Do not lose ship selector selection on narrow screens + * Reinstate jump range graph #2.2.5 * Calculate rate of fire for multi-burst weapons diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 847eeb35..1edadf1c 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -338,7 +338,7 @@ export default class OutfittingPage extends Page { -
      +
      @@ -350,6 +350,43 @@ export default class OutfittingPage extends Page {
      +
      +

      {translate('jump range')}

      + + + + + + + + + +
      + + + + + {formats.f2(fuelLevel * fuelCapacity)}{units.T} {formats.pct1(fuelLevel)} +
      +
      +
      @@ -357,8 +394,8 @@ export default class OutfittingPage extends Page {
      - + ); } } From 06f4abdf8b6397eead85580f4291f1058c64e0e1 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Tue, 10 Jan 2017 19:12:19 +0000 Subject: [PATCH 35/35] Import builds with military slots --- src/app/shipyard/Ship.js | 4 +++- src/app/utils/CompanionApiUtils.js | 22 ++++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 22fd21d9..0c016384 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -1164,7 +1164,9 @@ export default class Ship { } // We apply diminishing returns to the boosted value - shieldBoost = Math.min(shieldBoost, (1 - Math.pow(Math.E, -0.7 * shieldBoost)) * 2.5); + // (no we don't; FD pulled back on this idea. But leave this here in case they reinstate it) + // shieldBoost = Math.min(shieldBoost, (1 - Math.pow(Math.E, -0.7 * shieldBoost)) * 2.5); + shield = shield * shieldBoost; this.shield = shield; diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index d0da068a..11030548 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -241,18 +241,28 @@ export function shipFromJson(json) { // Add internal compartments let internalSlotNum = 1; + let militarySlotNum = 1; for (let i in shipTemplate.slots.internal) { const internalClassNum = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].class : shipTemplate.slots.internal[i]; + const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false; + // The internal slot might be a standard or a military slot. Military slots have a different naming system let internalSlot = null; - while (internalSlot === null && internalSlotNum < 99) { - // Slot numbers are not contiguous so handle skips - const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + internalClassNum; - if (json.modules[internalName]) { - internalSlot = json.modules[internalName]; + if (isMilitary) { + const internalName = 'Military0' + militarySlotNum; + internalSlot = json.modules[internalName]; + militarySlotNum++; + } else { + while (internalSlot === null && internalSlotNum < 99) { + // Slot numbers are not contiguous so handle skips + const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + internalClassNum; + if (json.modules[internalName]) { + internalSlot = json.modules[internalName]; + } + internalSlotNum++; } - internalSlotNum++; } + if (!internalSlot) { // This can happen with old imports that don't contain new slots } else if (!internalSlot.module) {