From 7d99d59790bf7934365eeac5f325ac6422f4b54b Mon Sep 17 00:00:00 2001 From: Grzegorz Date: Mon, 10 Oct 2016 17:39:17 +0200 Subject: [PATCH 01/44] pl option in menu --- src/app/i18n/Language.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/i18n/Language.jsx b/src/app/i18n/Language.jsx index dc237c24..fcb1c17f 100644 --- a/src/app/i18n/Language.jsx +++ b/src/app/i18n/Language.jsx @@ -5,6 +5,7 @@ import * as ES from './es'; import * as FR from './fr'; import * as IT from './it'; import * as RU from './ru'; +import * as PL from './pl'; import d3 from 'd3'; let fallbackTerms = EN.terms; @@ -23,6 +24,7 @@ export function getLanguage(langCode) { case 'fr': lang = FR; break; case 'it': lang = IT; break; case 'ru': lang = RU; break; + case 'pl': lang = PL; break; default: lang = EN; } @@ -78,5 +80,6 @@ export const Languages = { it: 'Italiano', es: 'Español', fr: 'Français', - ru: 'ру́сский' + ru: 'ру́сский', + pl: 'polski' }; From 5634fbd568b16dfc86792055c87312e9a996b284 Mon Sep 17 00:00:00 2001 From: Grzegorz Date: Mon, 10 Oct 2016 17:47:37 +0200 Subject: [PATCH 02/44] pl language --- src/app/i18n/pl.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/app/i18n/pl.js diff --git a/src/app/i18n/pl.js b/src/app/i18n/pl.js new file mode 100644 index 00000000..8444532e --- /dev/null +++ b/src/app/i18n/pl.js @@ -0,0 +1,77 @@ +export const formats = { + decimal: '.', + thousands: ',', + grouping: [3], + currency: ['$', ''], + dateTime: '%a %b %e %X %Y', + date: '%m/%d/%Y', + time: '%H:%M:%S', + periods: ['AM', 'PM'], + days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'], + shortDays: ['Nie', 'Pon', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob'], + months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'], + shortMonths: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'] +}; + +export const terms = { + PHRASE_ALT_ALL: 'Alt + kliknięcie by wypełnić wszystkie sloty', + PHRASE_BACKUP_DESC: 'Kopia zapasowa wszystkich danych Coriolis w celu zapisu lub przeniesienia na inne urządzenie/przeglądarkę', + PHRASE_CONFIRMATION: 'Czy jesteś pewien?', + PHRASE_EXPORT_DESC: 'Szczegółowy eksport schematu w formacie JSON w celu użycia na innych stronach i narzędziach', + PHRASE_FASTEST_RANGE: 'Maksymalna ilość skoków na najwyższym zasięgu', + PHRASE_IMPORT: 'Wklej tu JSON lub importuj', + PHRASE_LADEN: 'Masa statku + paliwo + ładunek', + PHRASE_NO_BUILDS: 'Nie dodano schematu do porównania!', + PHRASE_NO_RETROCH: 'Brak zmian retrofit', + PHRASE_SELECT_BUILDS: 'Wybierz schematy do porównania', + PHRASE_SG_RECHARGE: 'Czas od 50% do 100% naładowania', + PHRASE_SG_RECOVER: 'Odnowienie (do 50%) po upadku', + PHRASE_UNLADEN: 'Masa statku z wyłączeniem paliwa i ładunku', + PHRASE_UPDATE_RDY: 'Dostępna aktualizacja! Naciśnij by odświeżyć', + + // Other languages fallback to these values + // Only Translate to other languages if the name is different in-game + am: 'Auto Field-Maintenance Unit', + bh: 'Bulkheads', + bl: 'Beam Laser', + bsg: 'Bi-Weave Shield Generator', + c: 'Cannon', + cc: 'Collector Limpet Controller', + cm: 'Countermeasure', + cr: 'Cargo Rack', + cs: 'Cargo Scanner', + dc: 'Docking Computer', + fc: 'Fragment Cannon', + fi: 'FSD Interdictor', + fs: 'Fuel Scoop', + fsd: 'Frame Shift Drive', + ft: 'Fuel Tank', + fx: 'Fuel Transfer Limpet Controller', + hb: 'Hatch Breaker Limpet Controller', + hr: 'Hull Reinforcement Package', + kw: 'Kill Warrant Scanner', + ls: 'Life Support', + mc: 'Multi-cannon', + ml: 'Mining Laser', + mr: 'Missile Rack', + nl: 'Mine Launcher', + pa: 'Plasma Accelerator', + pas: 'Planetary Approach Suite', + pc: 'Prospector Limpet Controller', + pd: 'power distributor', + pl: 'Pulse Laser', + pp: 'Power Plant', + psg: 'Prismatic Shield Generator', + pv: 'Planetary Vehicle Hangar', + rf: 'Refinery', + rg: 'Rail Gun', + s: 'Sensors', + sb: 'Shield Booster', + sc: 'Scanner', + scb: 'Shield Cell Bank', + sg: 'Shield Generator', + t: 'thrusters', + tp: 'Torpedo Pylon', + ul: 'Burst Laser', + ws: 'Frame Shift Wake Scanner' +}; From 108ab3b1ee1471c8b36732340c372b793e7764db Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 7 Nov 2016 10:15:20 +0000 Subject: [PATCH 03/44] Update DPS/HPS/EPS in real-time as modifiers change --- ChangeLog.md | 5 ++++ src/app/shipyard/Ship.js | 53 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 ChangeLog.md diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 00000000..ae2c14b8 --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,5 @@ +#2.2.x + * Update DPS/HPS/EPS in real-time as modifiers change + * Use coriolis-data 2.2.2: + * Add distributor draw modifier to shield generators + * Remove modifiers for sensors diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 25e35110..d1451e7a 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -437,6 +437,11 @@ export default class Ship { } else if (name == 'hullboost') { m.setModValue(name, value); this.updateArmour(); + } else if (name == 'burst' || name == 'clip' || name == 'damage' || name == 'distdraw' || name == 'jitter' || name == 'piercing' || name == 'range' || name == 'reload' || name == 'rof' || name == 'thermload') { + m.setModValue(name, value); + this.recalculateDps(); + this.recalculateHps(); + this.recalculateEps(); } else { // Generic m.setModValue(name, value); @@ -820,6 +825,54 @@ export default class Ship { return this; } + /** + * Calculate damage per second for weapons + * @return {this} The ship instance (for chaining operations) + */ + recalculateDps() { + let totalDps = 0; + + for (let slotNum in this.hardpoints) { + const slot = this.hardpoints[slotNum]; + if (slot.m && slot.enabled && slot.m.getDps()) { + totalDps += slot.m.getDps(); + } + } + this.totalDps = totalDps; + } + + /** + * Calculate heat per second for weapons + * @return {this} The ship instance (for chaining operations) + */ + recalculateHps() { + let totalHps = 0; + + for (let slotNum in this.hardpoints) { + const slot = this.hardpoints[slotNum]; + if (slot.m && slot.enabled && slot.m.getHps()) { + totalHps += slot.m.getHps(); + } + } + this.totalHps = totalHps; + } + + /** + * Calculate energy per second for weapons + * @return {this} The ship instance (for chaining operations) + */ + recalculateEps() { + let totalEps = 0; + + for (let slotNum in this.hardpoints) { + const slot = this.hardpoints[slotNum]; + if (slot.m && slot.enabled && slot.m.getEps()) { + totalEps += slot.m.getEps(); + } + } + this.totalEps = totalEps; + } + /** * Update power calculations when amount generated changes * @return {this} The ship instance (for chaining operations) From 616ed0bf1049cf7a76cc4c1488b4d978730a61fe Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 7 Nov 2016 17:11:39 +0000 Subject: [PATCH 04/44] Show modification icon for modified modules Fix for #14 --- ChangeLog.md | 1 + src/app/components/HardpointSlot.jsx | 6 ++-- src/app/components/InternalSlot.jsx | 4 +-- src/app/components/StandardSlot.jsx | 4 +-- src/app/components/SvgIcons.jsx | 46 ++++++++++++++++++++++++++++ src/less/icons.less | 31 +++++++++++++++++++ 6 files changed, 86 insertions(+), 6 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index ae2c14b8..1b7ac9b7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,3 +3,4 @@ * Use coriolis-data 2.2.2: * Add distributor draw modifier to shield generators * Remove modifiers for sensors + * Show modification icon for modified modules diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index 9ecc6dae..e89e1770 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -1,6 +1,6 @@ import React from 'react'; import Slot from './Slot'; -import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications } from './SvgIcons'; +import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons'; import { Modifications } from 'coriolis-data/dist'; import { stopCtxPropagation } from '../utils/UtilityFunctions'; @@ -51,7 +51,9 @@ export default class HardpointSlot extends Slot { {m.type && m.type.match('K') ? : ''} {m.type && m.type.match('T') ? : ''} {m.type && m.type.match('E') ? : ''} - {classRating} {translate(m.name || m.grp)} + {classRating} {translate(m.name || m.grp)}{ Object.keys(m.mods).length > 0 ? : null } + +
{formats.round(m.getMass())}{u.T}
diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index 318bb85b..ce1e25f5 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -1,6 +1,6 @@ import React from 'react'; import Slot from './Slot'; -import { ListModifications } from './SvgIcons'; +import { ListModifications, Modified } from './SvgIcons'; import { Modifications } from 'coriolis-data/dist'; import { stopCtxPropagation } from '../utils/UtilityFunctions'; @@ -27,7 +27,7 @@ export default class InternalSlot extends Slot { let mass = m.getMass() || m.cargo || m.fuel || 0; return
-
{classRating} {translate(m.name || m.grp)}
+
{classRating} {translate(m.name || m.grp)}{Object.keys(m.mods).length > 0 ? : ''}
{formats.round(mass)}{u.T}
diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index 8140f7f0..3b54fd7c 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -5,7 +5,7 @@ import { jumpRange } from '../shipyard/Calculations'; import { diffDetails } from '../utils/SlotFunctions'; import AvailableModulesMenu from './AvailableModulesMenu'; import ModificationsMenu from './ModificationsMenu'; -import { ListModifications } from './SvgIcons'; +import { ListModifications, Modified } from './SvgIcons'; import { Modifications } from 'coriolis-data/dist'; import { stopCtxPropagation } from '../utils/UtilityFunctions'; @@ -79,7 +79,7 @@ export default class StandardSlot extends TranslatedComponent {
{slot.maxClass}
-
{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}
+
{classRating}{translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}{ Object.keys(m.mods).length > 0 ? : null }
{formats.round(mass)}{units.T}
diff --git a/src/app/components/SvgIcons.jsx b/src/app/components/SvgIcons.jsx index 50c13e16..b762fbe0 100644 --- a/src/app/components/SvgIcons.jsx +++ b/src/app/components/SvgIcons.jsx @@ -497,7 +497,19 @@ export class ListModifications extends SvgIcon { * @return {String} view box */ viewBox() { return '0 0 200 200'; } + /** + * Render the Icon + * @return {React.Component} SVG Icon + */ + render() { + return ( + + {this.svg()} + + ); + } + /** * Generate the SVG * @return {React.Component} SVG Contents @@ -507,6 +519,40 @@ export class ListModifications extends SvgIcon { } } +/** + * Modified (engineers) + */ +export class Modified extends SvgIcon { + /** + * Overriden view box + * @return {String} view box + */ + viewBox() { return '0 0 200 200'; } + + /** + * Render the Icon + * @return {React.Component} SVG Icon + */ + render() { + return ( + + {this.svg()} + + ); + } + + /** + * Generate the SVG + * @return {React.Component} SVG Contents + */ + svg() { + return + + + ; + } +} + /** * Hammer */ diff --git a/src/less/icons.less b/src/less/icons.less index 63c8316f..91362fd6 100755 --- a/src/less/icons.less +++ b/src/less/icons.less @@ -1,4 +1,5 @@ +// Standard icons .icon { display: inline-block; vertical-align: middle; @@ -25,3 +26,33 @@ height: 2em; } } + +// Modifiction icons - hard-code stroke/fill +.modicon { + display: inline-block; + vertical-align: middle; + width: 1.1em; + height: 1em; + stoke: @fg; + fill: transparent; + + &.sm { + width: 0.8em; + height: 0.75em; + } + + &.tn { + width: 0.6em; + height: 0.5em; + } + + &.lg { + width: 1.6em; + height: 1.5em; + } + + &.xl { + width: 2.1em; + height: 2em; + } +} From 5f0b851de787dc0b8dbdb207e231ee4dfd0ff568 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Tue, 8 Nov 2016 09:05:54 +0000 Subject: [PATCH 05/44] Take modifications in to account when deciding whether to issue a warning on a standard module. Fix for #16. --- src/app/components/StandardSlot.jsx | 2 +- src/app/components/StandardSlotSection.jsx | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index 3b54fd7c..dddf9edc 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -79,7 +79,7 @@ export default class StandardSlot extends TranslatedComponent {
{slot.maxClass}
-
{classRating}{translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}{ Object.keys(m.mods).length > 0 ? : null }
+
{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}{ Object.keys(m.mods).length > 0 ? : null }
{formats.round(mass)}{units.T}
diff --git a/src/app/components/StandardSlotSection.jsx b/src/app/components/StandardSlotSection.jsx index 48e53656..7a591b76 100644 --- a/src/app/components/StandardSlotSection.jsx +++ b/src/app/components/StandardSlotSection.jsx @@ -2,6 +2,7 @@ import React from 'react'; import cn from 'classnames'; import SlotSection from './SlotSection'; import StandardSlot from './StandardSlot'; +import Module from '../shipyard/Module'; import { diffDetails } from '../utils/SlotFunctions'; import * as ModuleUtils from '../shipyard/ModuleUtils'; import * as ShipRoles from '../shipyard/ShipRoles'; @@ -114,7 +115,7 @@ export default class StandardSlotSection extends SlotSection { selected={currentMenu == st[0]} onChange={this.props.onChange} ship={ship} - warning={m => m.pgen < ship.powerRetracted} + warning={m => m instanceof Module ? m.getPowerGeneration() < ship.powerRetracted : m.pgen< ship.powerRetracted} />; slots[2] = m.maxmass < (ship.ladenMass - st[1].mass + m.mass)} + warning={m => m instanceof Module ? m.getMaxMass() < (ship.ladenMass - st[1].mass + m.mass) : m.maxmass < (ship.ladenMass - st[1].mass + m.mass)} />; @@ -161,7 +162,7 @@ export default class StandardSlotSection extends SlotSection { selected={currentMenu == st[4]} onChange={this.props.onChange} ship={ship} - warning= {m => m.engcap < ship.boostEnergy} + warning={m => m instanceof Module ? m.getEnginesCapacity() < ship.boostEnergy : m.engcap < ship.boostEnergy} />; slots[6] = Date: Tue, 8 Nov 2016 09:25:01 +0000 Subject: [PATCH 06/44] Fix tooltip DPS --- src/app/shipyard/.Module.js.swp | Bin 0 -> 16384 bytes src/app/utils/SlotFunctions.js | 81 ++------------------------------ 2 files changed, 5 insertions(+), 76 deletions(-) create mode 100644 src/app/shipyard/.Module.js.swp diff --git a/src/app/shipyard/.Module.js.swp b/src/app/shipyard/.Module.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..79910c69aef3bd016933764b7c1ff3f65efb3afa GIT binary patch literal 16384 zcmeI3e~27c9l)nGtybG8P5cp1cxkY=cfH%aYYGL=)Y@KpcO}=vJ1@l6G|uh3y_@UI z&N?%j%k^?r3lULig|?ImS}7v_QA}HnXh0}M8c<55P(>jHZHu*_)ISsqTK#KB& z-rj^}3&JdXvO90S@B7~O``*0o`@WmZ4IiAGQr8!+v-o_#vi@}c&O@L1!RbrKKVn&n z^S+dB`nlEhqoFCgd{YqIHFO{hie6B*z4g_;gu;?`%1)^qgl^!uQ7H<`CA(HDMGJ0i z*$$o3RN&M-U0jUTSI_hAdm#6~|Ca~i+7{~`x_oikZTT&qi;}PA_kYvRYFhr~j{9$>_fMqd|LM5@ zuJpQ+e&3rm&}b(u&cAXGvebmR^d1-!VPfl3d{N{oPj6d zD2&2x7=j(}LD&vk;g!oR>v7l*ldv0JdmrWSGMt4cUHu&dzE$exB621>#g&CNF zNtl3}z=CHkv#bZFRDm2f$nxrFxM0XPA-!$FvZ{qXNCmi088hDV_e zUj&YCtVQ@3{Fy_RKf-U|G59Wg2Ofr7VLNPve=*j-hBNRiJPkgKfXssdc#DrxYM^Av zzv{+N$MvwPR&J~Nb2?mA@q$)CEp~m|Q-0lx-J0j>Pz4q4xRLVBTOCQ=aUEw9A5^h@ zms_1zZdJvBvdiV5UX4YUTXD;F>;}~cm8F3Ji?3DDuF`%>XPq{~y375deRsK{m_6|xz(Ua$vd`S!6jW=+}0GMhwQ*RT6Z6m&JI zi(n5-(v?bcls>zlLnhYV3d8Q|YI!-+STQA~mO@int*d#Piub_CjzP;f2{Vk;x}oer z=iO?o=R-GMZjRGrqa@S#H0yUAmW`x(U{Ebs(qYq>19CMR+4DM+HKwwvi+opmHkRv! zMf1A4)}V1+)uCBB&%-LTq?Iz5wxRHO*w?X|hdLsp$dcW-(fU@(t*C;89M*4^f00ys z8b1b9VMJYHB2xco8X`oCM+}lWNSaWQM2aLxRBd0I_#lF!JW-m42g;g_NYy(;{BY7Y z5pI<=Do9(+D#IeVJ}{X!GsK?_AN?C^c4+%*WhM@Zn5#;OMiXYu4-T)+OK(bteHv1lXuh(%PNzWa@D>LG2?FzYRk%IlGeh2 zhcI?^q$*+HtHwLhLyjHWO$BsOK4z(DwPcyXrVtIbv=tj|r~P01Af+2-_Q||AOtLZ1xE> z0?)xC@DMx*_rWB56g~u(!C%PjpM{5^4lzu_Z7>Dna520{zW+Qt1CPN9?1KU5hfClh zu;3iI|0(zy+zWM>g%Vis68ZjF_yznNeg;p$JX{AwQ1B1({=dT;@H#vS9#r5E$k{+Y z6yPd&ku!kr!MEY-a2P%ZhhQ4yjNm!)|1%(G1mA*h!Z+XqTn`qUBjT4l% zv~H3;FfI&QM`P2$Et!lV=QrmP6_+$bHmnoV%a6 zgl%#imiNFO+xLT}HJ|1plPyw9Meg0*Eon*h-j1@zzSHccN%n5$B9k3bYjZnVQWwsS z1#QQyHeoGV2+WV5(w1QG$Vd_hqZe(?+}qWedowyyv~+W(+`oX%w0$`be&?p8lQh`oNK??} z#<8w$9LwlN(bCP0a{t1)(LA^74^8{bSz^SYi9GYo#cEnk7~39P(rohdZhyE;^e8z3 nHLFKA%Q|IyZ>5|*_-lgXn$@P`WaF*=Fl#jMmoQP%Ic)y}*lb_t literal 0 HcmV?d00001 diff --git a/src/app/utils/SlotFunctions.js b/src/app/utils/SlotFunctions.js index 74697c7f..a9481315 100644 --- a/src/app/utils/SlotFunctions.js +++ b/src/app/utils/SlotFunctions.js @@ -93,69 +93,6 @@ export function slotComparator(translate, propComparator, desc) { }; } -const PROP_BLACKLIST = { - eddbID: 1, - edID: 1, - id: 1, - index: 1, - 'class': 1, - rating: 1, - maxfuel: 1, - fuelmul: 1, - fuelpower: 1, - optmass: 1, - maxmass: 1, - minmass: 1, - passive: 1, - thermload: 1, - ammocost: 1, - activepower: 1, - cooldown: 1, - chargeup: 1, - optmul: 1, - minmul: 1, - maxmul: 1, - ssdam: 1, - mjdps: 1, - mjeps: 1, - mass: 1, - cost: 1, - recover: 1, - wepcap: 1, - weprate: 1, - engcap: 1, - engrate: 1, - syscap: 1, - sysrate: 1, - breachdps: 1, - breachmin: 1, - breachmax: 1, - integrity: 1 -}; - -const TERM_LOOKUP = { - pgen: 'power', - armouradd: 'armour', - rof: 'ROF', - dps: 'DPS' -}; - -const FORMAT_LOOKUP = { - time: 'time' -}; - -const UNIT_LOOKUP = { - fuel: 'T', - cargo: 'T', - rate: 'kgs', - range: 'km', - recharge: 'MJ', - rangeLS: 'Ls', - power: 'MJ', - pgen: 'MJ', - rof: 'ps' -}; - /** * Determine the appropriate class based on diff value * @param {Number} a Potential Module (cannot be null) @@ -214,23 +151,15 @@ export function diffDetails(language, m, mm) { let mmMass = mm ? mm.getMass() : 0; if (mMass != mmMass) propDiffs.push(
{translate('mass')}: {diff(formats.round, mMass, mmMass)}{units.T}
); + let mPowerGeneration = m.pgen || 0; + let mmPowerGeneration = mm ? mm.getPowerGeneration() : 0; + if (mPowerGeneration != mmPowerGeneration) propDiffs.push(
{translate('pgen')}: {diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MJ}
); + let mPowerUsage = m.power || 0; let mmPowerUsage = mm ? mm.getPowerUsage() : 0; if (mPowerUsage != mmPowerUsage) propDiffs.push(
{translate('power')}: {diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MJ}
); -// for (let p in m) { -// if (!PROP_BLACKLIST[p] && !isNaN(m[p])) { -// let mVal = m[p] === null ? Infinity : m[p]; -// let mmVal = mm[p] === null ? Infinity : mm[p]; -// let format = formats[FORMAT_LOOKUP[p]] || formats.round; -// propDiffs.push(
-// {`${translate(TERM_LOOKUP[p] || p)}: `} -// {diff(format, mVal, mmVal)}{units[UNIT_LOOKUP[p]]} -//
); -// } -// } - - let mDps = m.damage * (m.rpshot || 1) * m.rof || 0; + let mDps = m.damage * (m.rpshot || 1) * (m.rof || 1) || 0; let mmDps = mm ? mm.getDps() || 0 : 0; if (mDps != mmDps) propDiffs.push(
{translate('dps')}: {diff(formats.round, mDps, mmDps)}
); From c1bc514e6b50107ea8da7d1c0f985affdf3d2c09 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Tue, 8 Nov 2016 09:29:32 +0000 Subject: [PATCH 07/44] Remove swapfile --- src/app/shipyard/.Module.js.swp | Bin 16384 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/app/shipyard/.Module.js.swp diff --git a/src/app/shipyard/.Module.js.swp b/src/app/shipyard/.Module.js.swp deleted file mode 100644 index 79910c69aef3bd016933764b7c1ff3f65efb3afa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI3e~27c9l)nGtybG8P5cp1cxkY=cfH%aYYGL=)Y@KpcO}=vJ1@l6G|uh3y_@UI z&N?%j%k^?r3lULig|?ImS}7v_QA}HnXh0}M8c<55P(>jHZHu*_)ISsqTK#KB& z-rj^}3&JdXvO90S@B7~O``*0o`@WmZ4IiAGQr8!+v-o_#vi@}c&O@L1!RbrKKVn&n z^S+dB`nlEhqoFCgd{YqIHFO{hie6B*z4g_;gu;?`%1)^qgl^!uQ7H<`CA(HDMGJ0i z*$$o3RN&M-U0jUTSI_hAdm#6~|Ca~i+7{~`x_oikZTT&qi;}PA_kYvRYFhr~j{9$>_fMqd|LM5@ zuJpQ+e&3rm&}b(u&cAXGvebmR^d1-!VPfl3d{N{oPj6d zD2&2x7=j(}LD&vk;g!oR>v7l*ldv0JdmrWSGMt4cUHu&dzE$exB621>#g&CNF zNtl3}z=CHkv#bZFRDm2f$nxrFxM0XPA-!$FvZ{qXNCmi088hDV_e zUj&YCtVQ@3{Fy_RKf-U|G59Wg2Ofr7VLNPve=*j-hBNRiJPkgKfXssdc#DrxYM^Av zzv{+N$MvwPR&J~Nb2?mA@q$)CEp~m|Q-0lx-J0j>Pz4q4xRLVBTOCQ=aUEw9A5^h@ zms_1zZdJvBvdiV5UX4YUTXD;F>;}~cm8F3Ji?3DDuF`%>XPq{~y375deRsK{m_6|xz(Ua$vd`S!6jW=+}0GMhwQ*RT6Z6m&JI zi(n5-(v?bcls>zlLnhYV3d8Q|YI!-+STQA~mO@int*d#Piub_CjzP;f2{Vk;x}oer z=iO?o=R-GMZjRGrqa@S#H0yUAmW`x(U{Ebs(qYq>19CMR+4DM+HKwwvi+opmHkRv! zMf1A4)}V1+)uCBB&%-LTq?Iz5wxRHO*w?X|hdLsp$dcW-(fU@(t*C;89M*4^f00ys z8b1b9VMJYHB2xco8X`oCM+}lWNSaWQM2aLxRBd0I_#lF!JW-m42g;g_NYy(;{BY7Y z5pI<=Do9(+D#IeVJ}{X!GsK?_AN?C^c4+%*WhM@Zn5#;OMiXYu4-T)+OK(bteHv1lXuh(%PNzWa@D>LG2?FzYRk%IlGeh2 zhcI?^q$*+HtHwLhLyjHWO$BsOK4z(DwPcyXrVtIbv=tj|r~P01Af+2-_Q||AOtLZ1xE> z0?)xC@DMx*_rWB56g~u(!C%PjpM{5^4lzu_Z7>Dna520{zW+Qt1CPN9?1KU5hfClh zu;3iI|0(zy+zWM>g%Vis68ZjF_yznNeg;p$JX{AwQ1B1({=dT;@H#vS9#r5E$k{+Y z6yPd&ku!kr!MEY-a2P%ZhhQ4yjNm!)|1%(G1mA*h!Z+XqTn`qUBjT4l% zv~H3;FfI&QM`P2$Et!lV=QrmP6_+$bHmnoV%a6 zgl%#imiNFO+xLT}HJ|1plPyw9Meg0*Eon*h-j1@zzSHccN%n5$B9k3bYjZnVQWwsS z1#QQyHeoGV2+WV5(w1QG$Vd_hqZe(?+}qWedowyyv~+W(+`oX%w0$`be&?p8lQh`oNK??} z#<8w$9LwlN(bCP0a{t1)(LA^74^8{bSz^SYi9GYo#cEnk7~39P(rohdZhyE;^e8z3 nHLFKA%Q|IyZ>5|*_-lgXn$@P`WaF*=Fl#jMmoQP%Ic)y}*lb_t From 38eaebefc02d20271a96eed883e0e30d316ff954 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Wed, 9 Nov 2016 16:32:11 +0000 Subject: [PATCH 08/44] Fix import and export of ships with modifications, bump schema version to 4 --- ChangeLog.md | 4 + .../anaconda-test-detailed-export-v4.json | 300 +++++++++++++++ .../fixtures/ed-shipyard-import-valid.json | 12 +- __tests__/fixtures/expected-builds.json | 50 +-- __tests__/fixtures/valid-backup.json | 5 +- __tests__/test-import.js | 21 +- __tests__/test-serializer.js | 8 +- __tests__/test-ship.js | 2 +- src/app/components/CostSection.jsx | 6 +- src/app/shipyard/Constants.js | 4 + src/app/shipyard/Module.js | 7 +- src/app/shipyard/ModuleUtils.js | 10 + src/app/shipyard/Serializer.js | 31 +- src/app/shipyard/Ship.js | 14 +- src/schemas/ship-loadout/4.json | 356 ++++++++++++++++++ 15 files changed, 756 insertions(+), 74 deletions(-) create mode 100644 __tests__/fixtures/anaconda-test-detailed-export-v4.json create mode 100644 src/schemas/ship-loadout/4.json diff --git a/ChangeLog.md b/ChangeLog.md index 1b7ac9b7..4e137a21 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,3 +4,7 @@ * Add distributor draw modifier to shield generators * Remove modifiers for sensors * Show modification icon for modified modules + * Take modifications in to account when deciding whether to issue a warning on a standard module + * Fix hardpoint comparison DPS number when selecting an alternate module + * Ensure that retrofit tab only shows changed modules + * Fix import and export of ships with modifications, bump schema version to 4 diff --git a/__tests__/fixtures/anaconda-test-detailed-export-v4.json b/__tests__/fixtures/anaconda-test-detailed-export-v4.json new file mode 100644 index 00000000..cda62b18 --- /dev/null +++ b/__tests__/fixtures/anaconda-test-detailed-export-v4.json @@ -0,0 +1,300 @@ +{ + "$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#", + "name": "Test My Ship", + "ship": "Anaconda", + "references": [ + { + "name": "Coriolis.io", + "url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==?bn=Test%20My%20Ship", + "old-code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==", + "code": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==", + "shipId": "anaconda" + } + ], + "components": { + "standard": { + "bulkheads": "Reactive Surface Composite", + "cargoHatch": { + "enabled": false, + "priority": 5 + }, + "powerPlant": { + "class": 8, + "rating": "A", + "enabled": true, + "priority": 1, + "modifications": { + "pgen": 1000 + } + }, + "thrusters": { + "class": 6, + "rating": "A", + "enabled": true, + "priority": 1 + }, + "frameShiftDrive": { + "class": 6, + "rating": "A", + "enabled": true, + "priority": 3 + }, + "lifeSupport": { + "class": 5, + "rating": "A", + "enabled": true, + "priority": 1 + }, + "powerDistributor": { + "class": 8, + "rating": "A", + "enabled": true, + "priority": 1 + }, + "sensors": { + "class": 8, + "rating": "A", + "enabled": true, + "priority": 1 + }, + "fuelTank": { + "class": 5, + "rating": "C", + "enabled": true, + "priority": 1 + } + }, + "hardpoints": [ + { + "class": 4, + "rating": "A", + "enabled": true, + "priority": 2, + "group": "Plasma Accelerator", + "mount": "Fixed" + }, + { + "class": 3, + "rating": "D", + "enabled": true, + "priority": 2, + "group": "Beam Laser", + "mount": "Turret" + }, + { + "class": 3, + "rating": "D", + "enabled": true, + "priority": 2, + "group": "Beam Laser", + "mount": "Turret" + }, + { + "class": 3, + "rating": "D", + "enabled": true, + "priority": 2, + "group": "Beam Laser", + "mount": "Turret" + }, + { + "class": 2, + "rating": "E", + "enabled": true, + "priority": 2, + "group": "Cannon", + "mount": "Turret" + }, + { + "class": 2, + "rating": "E", + "enabled": true, + "priority": 2, + "group": "Cannon", + "mount": "Turret" + }, + { + "class": 1, + "rating": "F", + "enabled": true, + "priority": 2, + "group": "Beam Laser", + "mount": "Turret" + }, + { + "class": 1, + "rating": "F", + "enabled": true, + "priority": 2, + "group": "Beam Laser", + "mount": "Turret" + } + ], + "utility": [ + { + "class": 0, + "rating": "A", + "enabled": true, + "priority": 1, + "group": "Shield Booster" + }, + { + "class": 0, + "rating": "A", + "enabled": true, + "priority": 1, + "group": "Shield Booster" + }, + null, + { + "class": 0, + "rating": "C", + "enabled": true, + "priority": 2, + "group": "Kill Warrant Scanner" + }, + { + "class": 0, + "rating": "C", + "enabled": true, + "priority": 2, + "group": "Cargo Scanner" + }, + { + "class": 0, + "rating": "F", + "enabled": false, + "priority": 1, + "group": "Electronic Countermeasure", + "name": "Electronic Countermeasure" + }, + { + "class": 0, + "rating": "I", + "enabled": true, + "priority": 1, + "group": "Chaff Launcher", + "name": "Chaff Launcher" + }, + { + "class": 0, + "rating": "I", + "enabled": true, + "priority": 2, + "group": "Point Defence", + "name": "Point Defence" + } + ], + "internal": [ + { + "class": 7, + "rating": "A", + "enabled": true, + "priority": 1, + "group": "Shield Generator" + }, + { + "class": 6, + "rating": "A", + "enabled": true, + "priority": 1, + "group": "Shield Cell Bank" + }, + { + "class": 6, + "rating": "E", + "enabled": true, + "priority": 1, + "group": "Cargo Rack" + }, + { + "class": 5, + "rating": "D", + "enabled": true, + "priority": 1, + "group": "Hull Reinforcement Package" + }, + { + "class": 5, + "rating": "E", + "enabled": true, + "priority": 1, + "group": "Cargo Rack" + }, + null, + null, + { + "class": 4, + "rating": "E", + "enabled": true, + "priority": 1, + "group": "Cargo Rack" + }, + { + "class": 4, + "rating": "E", + "enabled": true, + "priority": 1, + "group": "Cargo Rack" + }, + { + "class": 4, + "rating": "A", + "enabled": true, + "priority": 3, + "group": "Fuel Scoop" + }, + { + "class": 2, + "rating": "A", + "enabled": true, + "priority": 3, + "group": "Frame Shift Drive Interdictor" + } + ] + }, + "stats": { + "class": 3, + "fighterHangars": 1, + "hullCost": 141889930, + "speed": 180, + "topSpeed": 186.5, + "boost": 240, + "boostEnergy": 27, + "topBoost": 248.62, + "topSpeed": 186.46, + "totalCost": 882362058, + "totalDps": 97.74, + "totalEps": 22.71, + "totalHps": 677.29, + "agility": 2, + "baseShieldStrength": 350, + "baseArmour": 945, + "hullExplRes": 0, + "hullKinRes": 0, + "hullMass": 400, + "hullThermRes": 0, + "masslock": 23, + "pipSpeed": 0.14, + "moduleCostMultiplier": 1, + "fuelCapacity": 32, + "cargoCapacity": 128, + "ladenMass": 1339.2, + "armour": 2227.5, + "baseArmour": 525, + "unladenMass": 1179.2, + "powerAvailable": 39.6, + "powerRetracted": 23.33, + "powerDeployed": 34.76, + "unladenRange": 18.49, + "fullTankRange": 18.12, + "ladenRange": 16.39, + "unladenFastestRange": 73.21, + "ladenFastestRange": 66.15, + "maxJumpCount": 4, + "shield": 833, + "shieldExplRes": 0, + "shieldKinRes": 0, + "shieldThermRes": 0 + } +} diff --git a/__tests__/fixtures/ed-shipyard-import-valid.json b/__tests__/fixtures/ed-shipyard-import-valid.json index b056d143..68c62411 100644 --- a/__tests__/fixtures/ed-shipyard-import-valid.json +++ b/__tests__/fixtures/ed-shipyard-import-valid.json @@ -2,31 +2,31 @@ { "shipId": "anaconda", "buildName": "Imported Anaconda", - "buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=", + "buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=.", "buildText": "[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n" }, { "shipId": "anaconda", "buildName": "Imported Anaconda", - "buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=", + "buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=.", "buildText": "\n\n \t[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n" }, { "shipId": "cobra_mk_iii", "buildName": "Imported Cobra Mk III", - "buildCode": "0patcFeldd5sdf41712222503040202490f242h.Iw1-kA==.Aw1-kA==", + "buildCode": "0patcFeldd5sdf41712222503040202490f242h.Iw1-kA==.Aw1-kA==.", "buildText": "[Cobra Mk III]\nM: 1F/F Pulse Laser\nM: 1G/G Burst Laser\nS: 1E/T Fragment Cannon\nS: 1G/T Multi-cannon\nU: 0I Point Defence\nU: 0A Shield Booster\n\nBH: 1I Lightweight Alloy\nRB: 4A Power Plant\nTM: 4C Thrusters\nFH: 4E Frame Shift Drive\nEC: 3D Life Support\nPC: 2A Power Distributor\nSS: 3D Sensors\nFS: 4C Fuel Tank (Capacity: 16)\n\n4: 3E Cargo Rack (Capacity: 8)\n4: 3E Cargo Rack (Capacity: 8)\n4: 4E Shield Generator\n2: 2C Auto Field-Maintenance Unit\n2: 1E Standard Docking Computer\n2: 1E Basic Discovery Scanner\n---\nShield: 112.29 MJ\nPower : 10.45 MW retracted (67%)\n 12.16 MW deployed (78%)\n 15.60 MW available\nCargo : 16 T\nFuel : 16 T\nMass : 235.5 T empty\n 267.5 T full\nRange : 10.69 LY unladen\n 10.05 LY laden\nPrice : 2,929,040 CR\nRe-Buy: 146,452 CR @ 95% insurance\n" }, { "shipId": "type_9_heavy", "buildName": "Imported Type-9 Heavy", - "buildCode": "3pftsFklkdisif57e2k2f2h110001020306054j03022f01242i.Iw18eQ==.Aw18eQ==", + "buildCode": "3pftsFklkdisif57e2k2f2h110001020306054j03022f01242i.Iw18eQ==.Aw18eQ==.", "buildText": "[Type-9 Heavy]\nM: 2D/G Fragment Cannon\nM: 2I/F Mine Launcher\nM: 2B/FD Missile Rack\nS: 1I/FS Torpedo Pylon\nS: 1F/F Burst Laser\nU: 0I Chaff Launcher\nU: 0F Electronic Countermeasure\nU: 0I Heat Sink Launcher\nU: 0I Point Defence\n\nBH: 1I Mirrored Surface Composite\nRB: 5A Power Plant\nTM: 7D Thrusters\nFH: 6A Frame Shift Drive\nEC: 5A Life Support\nPC: 4D Power Distributor\nSS: 4D Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n8: 7E Cargo Rack (Capacity: 128)\n7: 6E Cargo Rack (Capacity: 64)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 3E Cargo Rack (Capacity: 8)\n4: 1C Advanced Discovery Scanner\n3: 2E Cargo Rack (Capacity: 4)\n3: 1E Standard Docking Computer\n2: 1C Detailed Surface Scanner\n" }, { "shipId": "vulture", "buildName": "Imported Vulture", - "buildCode": "4patfFalddksif31e1e0e0j04044a0n532jf1.Iw19kA==.Aw19kA==", + "buildCode": "4patfFalddksif31e1e0e0j04044a0n532jf1.Iw19kA==.Aw19kA==.", "buildText": "[Vulture]\nL: 3E/G Pulse Laser\nL: 3E/G Pulse Laser\nU: 0A Frame Shift Wake Scanner\nU: 0A Kill Warrant Scanner\nU: 0A Shield Booster\nU: 0A Shield Booster\n\nBH: 1I Reactive Surface Composite\nRB: 4A Power Plant\nTM: 5A Thrusters\nFH: 4A Frame Shift Drive\nEC: 3D Life Support\nPC: 5A Power Distributor\nSS: 4D Sensors\nFS: 3C Fuel Tank (Capacity: 8)\n\n5: 5A Shield Generator\n4: 4A Auto Field-Maintenance Unit\n2: 2A Shield Cell Bank\n1: 1A Fuel Scoop\n1: 1C Fuel Tank (Capacity: 2)" } -] \ No newline at end of file +] diff --git a/__tests__/fixtures/expected-builds.json b/__tests__/fixtures/expected-builds.json index d7f12000..2b985df4 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": "0p0tdFal8d8s8f4-----04040303430101.Iw1-kA==.Aw1-kA==.", + "Miner": "0p5tdFal8d8s8f42l2l---040403451q0101.Iw1-kA==.Aw1-kA==.", + "Hopper": "0p0tdFal8d0s8f41717---030302024300-.Iw1-kA==.Aw1-kA==." }, "type_7_transport": { - "Cargo": "0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==", - "Miner": "0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==" + "Cargo": "0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.", + "Miner": "0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==." }, "federal_dropship": { - "Cargo": "0pdtiFflnddsif4-1717------05040448020201.Iw18aQ==.Aw18aQ==" + "Cargo": "0pdtiFflnddsif4-1717------05040448020201.Iw18aQ==.Aw18aQ==." }, "asp": { - "Miner": "2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==" + "Miner": "2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==." }, "imperial_clipper": { - "Cargo": "0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==", - "Dream": "2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==", - "Current": "0patkFflndfskf4----------------.Iw18aQ==.Aw18aQ==" + "Cargo": "0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.", + "Dream": "2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==.", + "Current": "0patkFflndfskf4----------------.Iw18aQ==.Aw18aQ==." }, "type_9_heavy": { - "Current": "0patsFklndnsif6---------0706054a0303020224.Iw18eQ==.Aw18eQ==" + "Current": "0patsFklndnsif6---------0706054a0303020224.Iw18eQ==.Aw18eQ==." }, "python": { - "Cargo": "0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==", - "Miner": "0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==", - "Dream": "2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==", - "Missile": "0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==" + "Cargo": "0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.", + "Miner": "0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==.", + "Dream": "2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==.", + "Missile": "0pttoFjljdystf52f2g2d2ePh----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": "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=." }, "diamondback_explorer": { - "Explorer": "0p0tdFfldddsdf5---0202--320p432i2f.Iw1-kA==.Aw1-kA==" + "Explorer": "0p0tdFfldddsdf5---0202--320p432i2f.Iw1-kA==.Aw1-kA==." }, "vulture": { - "Bounty Hunter": "3patcFalddksff31e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA==" + "Bounty Hunter": "3patcFalddksff31e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA==." }, "fer_de_lance": { - "Attack": "2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ==" + "Attack": "2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ==." }, "eagle": { - "Figther": "4p0t5F5l3d5s5f20p0p24-40532j-.Iw1-EA==.Aw1-EA==" + "Figther": "4p0t5F5l3d5s5f20p0p24-40532j-.Iw1-EA==.Aw1-EA==." } -} \ No newline at end of file +} diff --git a/__tests__/fixtures/valid-backup.json b/__tests__/fixtures/valid-backup.json index 683511e1..a705dd3a 100644 --- a/__tests__/fixtures/valid-backup.json +++ b/__tests__/fixtures/valid-backup.json @@ -33,7 +33,8 @@ }, "anaconda": { "Dream": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100034k5n05050404040303326b.AwRj4yo5dig=.MwBhEYy6duwEziA=", - "Cargo": "03A7D6A5D4D8D5C----------------060505054d040403030301.AwRj4yuqg===.Aw18ZlA=" + "Cargo": "03A7D6A5D4D8D5C----------------060505054d040403030301.AwRj4yuqg===.Aw18ZlA=", + "Modified": "0pyttFolodDsyf5------1717--------05044j-03----2h00.Iw18ZlA=.Aw18ZlA=.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==" }, "diamondback_explorer": { "Explorer": "02A4D5A3D3D3D5C-------320p432i2f.AwRj4zTI.AwiMIypI" @@ -63,4 +64,4 @@ 1, 1 ] -} \ No newline at end of file +} diff --git a/__tests__/test-import.js b/__tests__/test-import.js index 27aa9a4c..4c62b00c 100644 --- a/__tests__/test-import.js +++ b/__tests__/test-import.js @@ -129,7 +129,7 @@ describe('Import Modal', function() { }); }); - describe('Import Detailed Build', function() { + describe('Import Detailed V3 Build', function() { beforeEach(reset); @@ -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/4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda/4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.?bn=Test%20My%20Ship'); }); it('catches an invalid build', function() { @@ -154,6 +154,23 @@ describe('Import Modal', function() { }); }); + describe('Import Detailed V4 Build', function() { + + beforeEach(reset); + + it('imports a valid v4 build', function() { + const importData = require('./fixtures/anaconda-test-detailed-export-v4'); + pasteText(JSON.stringify(importData)); + + expect(modal.state.importValid).toBeTruthy(); + expect(modal.state.errorMsg).toEqual(null); + 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/4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==?bn=Test%20My%20Ship'); + }); + }); + describe('Import Detaild Builds Array', function() { beforeEach(reset); diff --git a/__tests__/test-serializer.js b/__tests__/test-serializer.js index c03aff75..59260558 100644 --- a/__tests__/test-serializer.js +++ b/__tests__/test-serializer.js @@ -4,16 +4,16 @@ import * as Serializer from '../src/app/shipyard/Serializer'; import jsen from 'jsen'; describe("Serializer", function() { - const anacondaTestExport = require.requireActual('./fixtures/anaconda-test-detailed-export-v3'); + const anacondaTestExport = require.requireActual('./fixtures/anaconda-test-detailed-export-v4'); const code = anacondaTestExport.references[0].code; const anaconda = Ships.anaconda; - const validate = jsen(require('../src/schemas/ship-loadout/3')); + const validate = jsen(require('../src/schemas/ship-loadout/4')); describe("To Detailed Build", function() { let testBuild = new Ship('anaconda', anaconda.properties, anaconda.slots).buildFrom(code); let exportData = Serializer.toDetailedBuild('Test My Ship', testBuild); - it("conforms to the v3 ship-loadout schema", function() { + it("conforms to the v4 ship-loadout schema", function() { expect(validate(exportData)).toBe(true); }); @@ -31,7 +31,7 @@ describe("Serializer", function() { const builds = require('./fixtures/expected-builds'); const exportData = Serializer.toDetailedExport(builds); - it("conforms to the v3 ship-loadout schema", function() { + it("conforms to the v4 ship-loadout schema", function() { expect(exportData instanceof Array).toBe(true); for (let detailedBuild of exportData) { diff --git a/__tests__/test-ship.js b/__tests__/test-ship.js index 00ef39cc..9d52f01c 100644 --- a/__tests__/test-ship.js +++ b/__tests__/test-ship.js @@ -24,7 +24,7 @@ describe("Ship", function() { expect(ship.fuelCapacity).toBeGreaterThan(0, s + ' fuelCapacity'); expect(ship.unladenFastestRange).toBeGreaterThan(0, s + ' unladenFastestRange'); expect(ship.ladenFastestRange).toBeGreaterThan(0, s + ' ladenFastestRange'); - expect(ship.shieldStrength).toBeGreaterThan(0, s + ' shieldStrength'); + expect(ship.shield).toBeGreaterThan(0, s + ' shield'); expect(ship.armour).toBeGreaterThan(0, s + ' armour'); expect(ship.topSpeed).toBeGreaterThan(0, s + ' topSpeed'); } diff --git a/src/app/components/CostSection.jsx b/src/app/components/CostSection.jsx index 699f5d20..4c59dc25 100644 --- a/src/app/components/CostSection.jsx +++ b/src/app/components/CostSection.jsx @@ -379,7 +379,7 @@ export default class CostSection extends TranslatedComponent { {translate('retrofit from')} - {options} @@ -419,7 +419,9 @@ export default class CostSection extends TranslatedComponent { let retroSlotGroup = retrofitShip[g]; let slotGroup = ship[g]; for (i = 0, l = slotGroup.length; i < l; i++) { - if (slotGroup[i].m != retroSlotGroup[i].m) { + const modId = slotGroup[i].m ? slotGroup[i].m.eddbID : null; + const retroModId = retroSlotGroup[i].m ? retroSlotGroup[i].m.eddbID : null; + if (modId != retroModId) { item = { netCost: 0, retroItem: retroSlotGroup[i] }; if (slotGroup[i].m) { item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp; diff --git a/src/app/shipyard/Constants.js b/src/app/shipyard/Constants.js index 566bcf06..39bfd57d 100755 --- a/src/app/shipyard/Constants.js +++ b/src/app/shipyard/Constants.js @@ -51,15 +51,19 @@ export const ModuleGroupToName = { bl: 'Beam Laser', ul: 'Burst Laser', c: 'Cannon', + ch: 'Chaff Launcher', cs: 'Cargo Scanner', cm: 'Countermeasure', + ec: 'Electronic Countermeasure', fc: 'Fragment Cannon', + hs: 'Heat Sink Launcher', ws: 'Frame Shift Wake Scanner', kw: 'Kill Warrant Scanner', nl: 'Mine Launcher', ml: 'Mining Laser', mr: 'Missile Rack', pa: 'Plasma Accelerator', + po: 'Point Defence', mc: 'Multi-cannon', pl: 'Pulse Laser', rg: 'Rail Gun', diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index d367cc9a..03044e07 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -11,7 +11,7 @@ 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) { @@ -23,7 +23,6 @@ export default class Module { for (let p in template) { this[p] = template[p]; } } } - this.mods = {}; } /** @@ -41,6 +40,10 @@ export default class Module { * @param {Number} value The value of the modification, as a decimal value where 1 is 100% */ setModValue(name, value) { + if (!this.mods) { + this.mods = {}; + } + if (value == null || value == 0) { delete this.mods[name]; } else { diff --git a/src/app/shipyard/ModuleUtils.js b/src/app/shipyard/ModuleUtils.js index dc0b95e6..4949fd85 100755 --- a/src/app/shipyard/ModuleUtils.js +++ b/src/app/shipyard/ModuleUtils.js @@ -212,6 +212,16 @@ export function findHardpoint(groupName, clss, rating, name, mount, missile) { */ export function findHardpointId(groupName, clss, rating, name, mount, missile) { let h = this.findHardpoint(groupName, clss, rating, name, mount, missile); + if (h) { + return h.id; + } + + // Countermeasures used to be lumped in a single group but have been broken, out. If we have been given a groupName of 'Countermeasure' then + // rely on the unique name to find it + if (groupName === 'cm' || groupName === 'Countermeasure') { + h = this.findHardpoint(null, clss, rating, name, mount, missile); + } + return h ? h.id : 0; } diff --git a/src/app/shipyard/Serializer.js b/src/app/shipyard/Serializer.js index 1fb3e477..00719f00 100644 --- a/src/app/shipyard/Serializer.js +++ b/src/app/shipyard/Serializer.js @@ -115,32 +115,12 @@ export function toDetailedBuild(buildName, ship) { return data; }; -/** - * Instantiates a ship from a ship-loadout object, using the code - * @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 shipData = Ships[shipId]; - let ship = new Ship(shipId, shipData.properties, shipData.slots); - - return ship.buildFrom(detailedBuild.references[0].code); -}; - /** * Instantiates a ship from a ship-loadout object * @param {Object} detailedBuild ship-loadout object * @return {Ship} Ship instance */ -export function oldfromDetailedBuild(detailedBuild) { +export function fromDetailedBuild(detailedBuild) { let shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase()); if (!shipId) { @@ -154,6 +134,7 @@ export function oldfromDetailedBuild(detailedBuild) { 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); if (bulkheads < 0) { throw 'Invalid bulkheads: ' + stn.bulkheads; @@ -165,6 +146,7 @@ export function oldfromDetailedBuild(detailedBuild) { } 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); return stn[c].class + stn[c].rating; }); @@ -185,8 +167,13 @@ export function oldfromDetailedBuild(detailedBuild) { 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.m ? c.m.modifications : null)), + comps.utility.map(c => (c && c.m ? c.m.modifications : null)), + comps.internal.map(c => (c && c.m ? c.m.modifications : null)) + ); - ship.buildWith({ bulkheads, standard, hardpoints, internal }, priorities, enabled); + ship.buildWith({ bulkheads, standard, hardpoints, internal }, priorities, enabled, modifications); return ship; }; diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index d1451e7a..bb2f0558 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -486,7 +486,6 @@ export default class Ship { this.bulkheads.m.mods = mods && mods[0] ? mods[0] : {}; this.cargoHatch.priority = priorities ? priorities[0] * 1 : 0; this.cargoHatch.enabled = enabled ? enabled[0] * 1 : true; - this.cargoHatch.mods = mods ? mods[0] : {}; for (i = 0; i < cl; i++) { standard[i].cat = 0; @@ -562,7 +561,7 @@ export default class Ship { let standard = new Array(this.standard.length), hardpoints = new Array(this.hardpoints.length), internal = new Array(this.internal.length), - mods = new Array(1 + this.standard.length + this.hardpoints.length + this.internal.length), + modifications = new Array(1 + this.standard.length + this.hardpoints.length + this.internal.length), parts = serializedString.split('.'), priorities = null, enabled = null, @@ -579,10 +578,10 @@ export default class Ship { if (parts[3]) { const modstr = parts[3].replace(/-/g, '/'); if (modstr.match(':')) { - this.decodeModificationsString(modstr, mods); + this.decodeModificationsString(modstr, modifications); } else { try { - this.decodeModificationsStruct(zlib.gunzipSync(new Buffer(modstr, 'base64')), mods); + this.decodeModificationsStruct(zlib.gunzipSync(new Buffer(modstr, 'base64')), modifications); } catch (err) { // Could be out-of-date URL; ignore } @@ -600,7 +599,7 @@ export default class Ship { }, priorities, enabled, - mods + modifications ); }; @@ -1051,10 +1050,10 @@ export default class Ship { } /** - * Update the modifications string + * Update the modifications string in a human-readable format * @return {this} The ship instance (for chaining operations) */ - oldupdateModificationsString() { + debugupdateModificationsString() { let allMods = new Array(); let bulkheadMods = new Array(); @@ -1272,7 +1271,6 @@ export default class Ship { case 1: this.serialized.hardpoints = null; break; case 2: this.serialized.internal = null; } - this.serialized.modifications = null; } return this; } diff --git a/src/schemas/ship-loadout/4.json b/src/schemas/ship-loadout/4.json new file mode 100644 index 00000000..cf6876b9 --- /dev/null +++ b/src/schemas/ship-loadout/4.json @@ -0,0 +1,356 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#", + "title": "Ship Loadout", + "type": "object", + "description": "The details for a specific ship build/loadout", + "required": ["name", "ship", "components"], + "properties": { + "name": { + "description": "The name of the build/loadout", + "type": "string", + "minLength": 1 + }, + "ship": { + "description": "The full display name of the ship", + "type": "string", + "minimum": 3 + }, + "manufacturer": { + "description": "The ship manufacturer", + "type": "string" + }, + "references" : { + "description": "3rd Party references and/or links to this build/loadout", + "type": "array", + "items": { + "type": "object", + "required": ["name","url"], + "additionalProperties": true, + "properties": { + "name": { + "description": "The name of the 3rd party, .e.g 'Coriolis.io' or 'E:D Shipyard'", + "type": "string" + }, + "url": { + "description": "The link/url to the 3rd party referencing this build/loadout", + "type": "string" + } + } + } + }, + "components": { + "description": "The components used by this build", + "type": "object", + "additionalProperties": false, + "required": ["standard", "internal", "hardpoints", "utility"], + "properties": { + "standard": { + "description": "The set of standard components across all ships", + "type": "object", + "additionalProperties": false, + "required": ["bulkheads", "powerPlant", "thrusters", "frameShiftDrive", "lifeSupport", "powerDistributor", "sensors", "fuelTank", "cargoHatch"], + "properties": { + "bulkheads": { + "enum": ["Lightweight Alloy", "Reinforced Alloy", "Military Grade Composite", "Mirrored Surface Composite", "Reactive Surface Composite"] + }, + "cargoHatch": { + "required": ["enabled", "priority"], + "properties": { + "enabled": { "type": "boolean" }, + "priority": { "type": "integer", "minimum": 1, "maximum": 5 } + } + }, + "powerPlant": { + "required": ["class", "rating", "enabled", "priority"], + "properties": { + "class": { "type": "integer", "minimum": 2, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" }, + "enabled": { "type": "boolean" }, + "priority": { "type": "integer", "minimum": 1, "maximum": 5 }, + "modifications": { "type": "object" } + } + }, + "thrusters": { + "required": ["class", "rating", "enabled", "priority"], + "properties": { + "class": { "type": "integer", "minimum": 2, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" }, + "enabled": { "type": "boolean" }, + "priority": { "type": "integer", "minimum": 1, "maximum": 5 }, + "name": { + "description": "The name identifing the thrusters (if applicable), e.g. 'Enhanced Performance'", + "type": "string" + }, + "modifications": { "type": "object" } + } + }, + "frameShiftDrive": { + "required": ["class", "rating", "enabled", "priority"], + "properties": { + "class": { "type": "integer", "minimum": 2, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" }, + "enabled": { "type": "boolean" }, + "priority": { "type": "integer", "minimum": 1, "maximum": 5 }, + "modifications": { "type": "object" } + } + }, + "lifeSupport": { + "required": ["class", "rating", "enabled", "priority"], + "properties": { + "class": { "type": "integer", "minimum": 1, "maximum": 6 }, + "rating": { "$ref": "#/definitions/standardRatings" }, + "enabled": { "type": "boolean" }, + "priority": { "type": "integer", "minimum": 1, "maximum": 5 }, + "modifications": { "type": "object" } + } + }, + "powerDistributor": { + "required": ["class", "rating", "enabled", "priority"], + "properties": { + "class": { "type": "integer", "minimum": 1, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" }, + "enabled": { "type": "boolean" }, + "priority": { "type": "integer", "minimum": 1, "maximum": 5 }, + "modifications": { "type": "object" } + } + }, + "sensors": { + "required": ["class", "rating", "enabled", "priority"], + "properties": { + "class": { "type": "integer", "minimum": 1, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" }, + "enabled": { "type": "boolean" }, + "priority": { "type": "integer", "minimum": 1, "maximum": 5 }, + "modifications": { "type": "object" } + } + }, + "fuelTank": { + "required": ["class", "rating", "enabled", "priority"], + "properties": { + "class": { "type": "integer", "minimum": 1, "maximum": 6 }, + "rating": { "$ref": "#/definitions/standardRatings" }, + "enabled": { "type": "boolean" }, + "priority": { "type": "integer", "minimum": 1, "maximum": 5 }, + "modifications": { "type": "object" } + } + } + } + }, + "internal": { + "type": "array", + "items": { + "type": ["object", "null"], + "required": ["class", "rating", "enabled", "priority", "group"], + "properties" : { + "class": { "type": "integer", "minimum": 1, "maximum": 8 }, + "rating": { "$ref": "#/definitions/standardRatings" }, + "enabled": { "type": "boolean" }, + "priority": { "type": "integer", "minimum": 1, "maximum": 5 }, + "group": { + "description": "The group of the component, e.g. 'Shield Generator', or 'Cargo Rack'", + "type": "string" + }, + "name": { + "description": "The name identifying the component (if applicable), e.g. 'Advance Discovery Scanner', or 'Detailed Surface Scanner'", + "type": "string" + }, + "modifications": { "type": "object" } + } + }, + "minItems": 3 + }, + "hardpoints": { + "type": "array", + "items": { + "type": ["object", "null"], + "required": ["class", "rating", "enabled", "priority", "group", "mount"], + "properties" : { + "class": { "type": "integer", "minimum": 1, "maximum": 4 }, + "rating": { "$ref": "#/definitions/allRatings" }, + "enabled": { "type": "boolean" }, + "priority": { "type": "integer", "minimum": 1, "maximum": 5 }, + "mount": { "type": "string", "enum": ["Fixed", "Gimballed", "Turret"] }, + "group": { + "description": "The group of the component, e.g. 'Beam Laser', or 'Missile Rack'", + "type": "string" + }, + "name": { + "description": "The name identifing the component (if applicable), e.g. 'Retributor', or 'Mining Lance'", + "type": "string" + }, + "modifications": { "type": "object" } + } + }, + "minItems": 1 + }, + "utility": { + "type": "array", + "items": { + "type": ["object", "null"], + "required": ["class", "rating", "enabled", "priority", "group"], + "properties" : { + "class": { "type": "integer", "minimum": 0, "maximum": 0 }, + "rating": { "$ref": "#/definitions/allRatings" }, + "enabled": { "type": "boolean" }, + "priority": { "type": "integer", "minimum": 1, "maximum": 5 }, + "group": { + "description": "The group of the component, e.g. 'Shield Booster', or 'Kill Warrant Scanner'", + "type": "string" + }, + "name": { + "description": "The name identifing the component (if applicable), e.g. 'Point Defence', or 'Electronic Countermeasure'", + "type": "string" + }, + "modifications": { "type": "object" } + } + }, + "minItems": 1 + } + } + }, + "stats": { + "description": "Optional statistics from the build", + "type": "object", + "additionalProperties": true, + "properties": { + "agility": { + "type": "integer", + "minimum": 0 + }, + "armour": { + "description": "Sum of base armour + any hull reinforcements", + "type": "integer", + "minimum": 1 + }, + "armourAdded":{ + "description": "Armour added through Hull reinforcement", + "type": "integer", + "minimum": 0 + }, + "baseShieldStrength": { + "type": "integer", + "minimum": 1 + }, + "baseArmour": { + "type": "integer", + "minimum": 1 + }, + "boost": { + "description": "Maximum boost speed of the ships (4 pips, straight-line)", + "type": "number", + "minimum": 0 + }, + "cargoCapacity": { + "type": "integer", + "minimum": 0 + }, + "class": { + "description": "Ship Class/Size [Small, Medium, Large]", + "enum": [1,2,3] + }, + "totalDps": { + "description": "Total damage dealt per second of all weapons", + "type": "number", + "minimum": 0 + }, + "totalEps": { + "description": "Total energy consumed per second of all weapons", + "type": "number", + "minimum": 0 + }, + "totalHps": { + "description": "Total heat generated per second of all weapons", + "type": "number", + "minimum": 0 + }, + "hullCost": { + "description": "Cost of the ship's hull", + "type": "integer", + "minimum": 1 + }, + "hullMass": { + "description": "Mass of the Ship hull only", + "type": "number", + "minimum": 1 + }, + "hullExplRes": { + "description": "Resistance of the hull to explosive attacks", + "type": "number" + }, + "hullKinRes": { + "description": "Resistance of the hull to kinetic attacks", + "type": "number" + }, + "hullThermRes": { + "description": "Resistance of the hull to thermal attacks", + "type": "number" + }, + "fuelCapacity": { + "type": "integer", + "minimum": 1 + }, + "fullTankRange": { + "description": "Single Jump range with a full tank (unladenMass + fuel)", + "type": "number", + "minimum": 0 + }, + "ladenMass": { + "description": "Mass of the Ship + fuel + cargo (hull + all components + fuel tank + cargo capacity)", + "type": "number", + "minimum": 1 + }, + "ladenRange": { + "description": "Single Jump range with full cargo load, see ladenMass", + "type": "number", + "minimum": 0 + }, + "masslock": { + "description": "Mass Lock Factor of the Ship", + "type": "integer", + "minimum": 1 + }, + "shield": { + "description": "Shield strengh in Mega Joules (Mj)", + "type": "number", + "minimum": 0 + }, + "shieldExplRes": { + "description": "Resistance of the shield to explosive attacks", + "type": "number" + }, + "shieldKinRes": { + "description": "Resistance of the shield to kinetic attacks", + "type": "number" + }, + "shieldThermRes": { + "description": "Resistance of the shield to thermal attacks", + "type": "number" + }, + "speed": { + "description": "Maximum speed of the ships (4 pips, straight-line)", + "type": "number", + "minimum": 1 + }, + "totalCost": { + "type": "integer", + "minimum": 1 + }, + "unladenRange": { + "description": "Single Jump range when unladen, see unladenMass", + "type": "number", + "minimum": 0 + }, + "unladenMass": { + "description": "Mass of the Ship (hull + all components)", + "type": "number", + "minimum": 1 + } + } + } + }, + "definitions": { + "standardRatings": { "enum": ["A", "B", "C", "D", "E", "F", "G", "H"] }, + "allRatings": { "enum": ["A", "B", "C", "D", "E", "F", "G", "H", "I" ] } + } +} From f13a987388a7c638d7f12700f32e1a751bdf2d18 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Wed, 9 Nov 2016 16:40:22 +0000 Subject: [PATCH 09/44] Lint tidy-ups --- src/app/components/CostSection.jsx | 4 ++-- src/app/components/StandardSlotSection.jsx | 2 +- src/app/shipyard/Ship.js | 12 ++++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/app/components/CostSection.jsx b/src/app/components/CostSection.jsx index 4c59dc25..254eabc0 100644 --- a/src/app/components/CostSection.jsx +++ b/src/app/components/CostSection.jsx @@ -419,8 +419,8 @@ export default class CostSection extends TranslatedComponent { let retroSlotGroup = retrofitShip[g]; let slotGroup = ship[g]; for (i = 0, l = slotGroup.length; i < l; i++) { - const modId = slotGroup[i].m ? slotGroup[i].m.eddbID : null; - const retroModId = retroSlotGroup[i].m ? retroSlotGroup[i].m.eddbID : null; + const modId = slotGroup[i].m ? slotGroup[i].m.eddbID : null; + const retroModId = retroSlotGroup[i].m ? retroSlotGroup[i].m.eddbID : null; if (modId != retroModId) { item = { netCost: 0, retroItem: retroSlotGroup[i] }; if (slotGroup[i].m) { diff --git a/src/app/components/StandardSlotSection.jsx b/src/app/components/StandardSlotSection.jsx index 7a591b76..0150b37c 100644 --- a/src/app/components/StandardSlotSection.jsx +++ b/src/app/components/StandardSlotSection.jsx @@ -115,7 +115,7 @@ export default class StandardSlotSection extends SlotSection { selected={currentMenu == st[0]} onChange={this.props.onChange} ship={ship} - warning={m => m instanceof Module ? m.getPowerGeneration() < ship.powerRetracted : m.pgen< ship.powerRetracted} + warning={m => m instanceof Module ? m.getPowerGeneration() < ship.powerRetracted : m.pgen < ship.powerRetracted} />; slots[2] = Date: Wed, 9 Nov 2016 18:57:45 +0000 Subject: [PATCH 10/44] Handle potentially null modifications object --- ChangeLog.md | 3 +++ src/app/components/HardpointSlot.jsx | 2 +- src/app/components/InternalSlot.jsx | 2 +- src/app/components/StandardSlot.jsx | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 4e137a21..afea02ee 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,9 @@ * Use coriolis-data 2.2.2: * Add distributor draw modifier to shield generators * Remove modifiers for sensors + * Add initial loadout passenger cabins for Beluga + * Add initial loadout passenger cabins for Orca + * Update costs and initial loadouts for Keelback and Type-7 * Show modification icon for modified modules * Take modifications in to account when deciding whether to issue a warning on a standard module * Fix hardpoint comparison DPS number when selecting an alternate module diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index e89e1770..00ddafb4 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -51,7 +51,7 @@ export default class HardpointSlot extends Slot { {m.type && m.type.match('K') ? : ''} {m.type && m.type.match('T') ? : ''} {m.type && m.type.match('E') ? : ''} - {classRating} {translate(m.name || m.grp)}{ Object.keys(m.mods).length > 0 ? : null } + {classRating} {translate(m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? : null }
{formats.round(m.getMass())}{u.T}
diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index ce1e25f5..84e12a8a 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -27,7 +27,7 @@ export default class InternalSlot extends Slot { let mass = m.getMass() || m.cargo || m.fuel || 0; return
-
{classRating} {translate(m.name || m.grp)}{Object.keys(m.mods).length > 0 ? : ''}
+
{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? : ''}
{formats.round(mass)}{u.T}
diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index dddf9edc..8f8537eb 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -79,7 +79,7 @@ export default class StandardSlot extends TranslatedComponent {
{slot.maxClass}
-
{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}{ Object.keys(m.mods).length > 0 ? : null }
+
{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? : null }
{formats.round(mass)}{units.T}
From 0688faac93e027110bd4745dcaa8bf75b3617b36 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Thu, 10 Nov 2016 00:13:56 +0000 Subject: [PATCH 11/44] Added offence and defence summary --- src/app/components/DefenceSummary.jsx | 89 ++++++++ src/app/components/OffenceSummary.jsx | 103 +++++++++ src/app/components/ShipSummaryTable.jsx | 21 +- src/app/pages/OutfittingPage.jsx | 40 ++-- src/app/shipyard/Module.js | 23 +- src/app/shipyard/Ship.js | 270 ++++++++++++++++++------ 6 files changed, 426 insertions(+), 120 deletions(-) create mode 100644 src/app/components/DefenceSummary.jsx create mode 100644 src/app/components/OffenceSummary.jsx diff --git a/src/app/components/DefenceSummary.jsx b/src/app/components/DefenceSummary.jsx new file mode 100644 index 00000000..909ce89f --- /dev/null +++ b/src/app/components/DefenceSummary.jsx @@ -0,0 +1,89 @@ +import React from 'react'; +import cn from 'classnames'; +import TranslatedComponent from './TranslatedComponent'; + +/** + * Defence summary + */ +export default class DefenceSummary extends TranslatedComponent { + static PropTypes = { + ship: React.PropTypes.object.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + */ + constructor(props) { + super(props); + } + + /** + * Render defence summary + * @return {React.Component} contents + */ + render() { + let ship = this.props.ship; + let { language, tooltip, termtip } = this.context; + let { formats, translate, units } = language; + let hide = tooltip.bind(null, null); + + let sgClassNames = cn({ muted: !ship.findInternalByGroup('sg') }); + + return ( +
+

{translate('defence summary')}

+
+ + + + + + + + + + + + + + { ship.shield ? + + + + + + : null } + + + + + + + +
{translate('damage to')}{translate('damage from')}
{translate('explosive')}{translate('kinetic')}{translate('thermal')}
{translate('shields')}{ship.shieldExplRes ? formats.pct(ship.shieldExplRes) : '-'}{ship.shieldKinRes ? formats.pct(ship.shieldKinRes) : '-'}{ship.shieldThermRes ? formats.pct(ship.shieldThermRes) : '-'}
{translate('hull')}{formats.pct(ship.hullExplRes)}{formats.pct(ship.hullKinRes)}{formats.pct(ship.hullThermRes)}
+
+ { ship.shield ? + + + + + + + + + + + + + + + + + + +
{translate('shields')}
{translate('strength')}{translate('recovery')}{translate('recovery')}
{formats.int(ship.shield)} {units.MJ}{ship.shield ? formats.time(ship.calcShieldRecovery()) : '-'}{ship.shield ? formats.time(ship.calcShieldRecharge()) : '-'}
: null } +
+ ); + } +} diff --git a/src/app/components/OffenceSummary.jsx b/src/app/components/OffenceSummary.jsx new file mode 100644 index 00000000..a32f3ec0 --- /dev/null +++ b/src/app/components/OffenceSummary.jsx @@ -0,0 +1,103 @@ +import React from 'react'; +import cn from 'classnames'; +import TranslatedComponent from './TranslatedComponent'; + +/** + * Offence summary + */ +export default class OffenceSummary extends TranslatedComponent { + static PropTypes = { + ship: React.PropTypes.object.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + */ + constructor(props) { + super(props); + } + + /** + * Render offence summary + * @return {React.Component} contents + */ + render() { + let ship = this.props.ship; + let { language, tooltip, termtip } = this.context; + let { formats, translate, units } = language; + let hide = tooltip.bind(null, null); + + return ( +
+

{translate('offence summary')}

+
+ + + + + + + + + + + + + + + + + + + + +
{translate('dps')}
{translate('explosive')}{translate('kinetic')}{translate('thermal')}{translate('total')}
{formats.f1(ship.totalExplDps)}{formats.f1(ship.totalKinDps)}{formats.f1(ship.totalThermDps)}{formats.f1(ship.totalDps)}
+
+ + + + + + + + + + + + + + + + + + + + +
{translate('sustained dps')}
{translate('explosive')}{translate('kinetic')}{translate('thermal')}{translate('total')}
{formats.f1(ship.totalExplSDps)}{formats.f1(ship.totalKinSDps)}{formats.f1(ship.totalThermSDps)}{formats.f1(ship.totalSDps)}
+
+ + + + + + + + + + + + + + + + + + + + +
{translate('dpe')}
{translate('explosive')}{translate('kinetic')}{translate('thermal')}{translate('total')}
{formats.f1(ship.totalExplDpe)}{formats.f1(ship.totalKinDpe)}{formats.f1(ship.totalThermDpe)}{formats.f1(ship.totalDpe)}
+
+ ); + } +} diff --git a/src/app/components/ShipSummaryTable.jsx b/src/app/components/ShipSummaryTable.jsx index e5069369..9ab15a9d 100644 --- a/src/app/components/ShipSummaryTable.jsx +++ b/src/app/components/ShipSummaryTable.jsx @@ -34,20 +34,6 @@ export default class ShipSummaryTable extends TranslatedComponent { sgRecharge = time(ship.calcShieldRecharge()); } - // {translate('shield resistance')} - // {translate('hull resistance')} - // {translate('explosive')} - // {translate('kinetic')} - // {translate('thermal')} - // {translate('explosive')} - // {translate('kinetic')} - // {translate('thermal')} - // {pct(ship.shieldExplRes)} - // {pct(ship.shieldKinRes)} - // {pct(ship.shieldThermRes)} - // {pct(ship.hullExplRes)} - // {pct(ship.hullKinRes)} - // {pct(ship.hullThermRes)} return
@@ -58,7 +44,7 @@ export default class ShipSummaryTable extends TranslatedComponent { - + @@ -67,9 +53,6 @@ export default class ShipSummaryTable extends TranslatedComponent { - - - @@ -90,8 +73,6 @@ export default class ShipSummaryTable extends TranslatedComponent { - - diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 839c6b00..6c97fa5c 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -8,13 +8,14 @@ import Persist from '../stores/Persist'; import Ship from '../shipyard/Ship'; import { toDetailedBuild } from '../shipyard/Serializer'; import { outfitURL } from '../utils/UrlGenerators'; - import { FloppyDisk, Bin, Switch, Download, Reload, Fuel } from '../components/SvgIcons'; import ShipSummaryTable from '../components/ShipSummaryTable'; import StandardSlotSection from '../components/StandardSlotSection'; import HardpointsSlotSection from '../components/HardpointsSlotSection'; import InternalSlotSection from '../components/InternalSlotSection'; import UtilitySlotSection from '../components/UtilitySlotSection'; +import OffenceSummary from '../components/OffenceSummary'; +import DefenceSummary from '../components/DefenceSummary'; import LineChart from '../components/LineChart'; import PowerManagement from '../components/PowerManagement'; import CostSection from '../components/CostSection'; @@ -324,31 +325,11 @@ export default class OutfittingPage extends Page {
-

{translate('jump range')}

- +
-

{translate('total range')}

- +
@@ -397,3 +378,16 @@ export default class OutfittingPage extends Page { ); } } +//
+//

{translate('jump range')}

+// +//
diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 03044e07..a7f152de 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -54,16 +54,23 @@ export default class Module { /** * Helper to obtain a modified value using standard multipliers - * @param {String} name the name of the modifier to obtain - * @return {Number} the mass of this module + * @param {String} name the name of the modifier to obtain + * @param {Boolean} additive Optional true if the value is additive rather than multiplicative + * @return {Number} the mass of this module */ - _getModifiedValue(name) { + _getModifiedValue(name, additive) { let result = 0; if (this[name]) { result = this[name]; if (result) { - let mult = this.getModValue(name); - if (mult) { result = result * (1 + mult); } + const modValue = this.getModValue(name); + if (modValue) { + if (additive) { + result = result + modValue; + } else { + result = result * (1 + modValue); + } + } } } return result; @@ -193,7 +200,7 @@ export default class Module { * @return {Number} the kinetic resistance of this module */ getKineticResistance() { - return this._getModifiedValue('kinres'); + return this._getModifiedValue('kinres', true); } /** @@ -201,7 +208,7 @@ export default class Module { * @return {Number} the thermal resistance of this module */ getThermalResistance() { - return this._getModifiedValue('thermres'); + return this._getModifiedValue('thermres', true); } /** @@ -209,7 +216,7 @@ export default class Module { * @return {Number} the explosive resistance of this module */ getExplosiveResistance() { - return this._getModifiedValue('explres'); + return this._getModifiedValue('explres', true); } /** diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 0de48a80..e7cdb415 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -194,10 +194,12 @@ export default class Ship { */ calcShieldRecovery() { if (this.shield > 0) { - let sgSlot = this.findInternalByGroup('sg'); - let brokenRegenRate = 1 + sgSlot.m.getModValue('brokenregen'); - // 50% of shield strength / recovery recharge rate + 15 second delay before recharge starts - return ((this.shield / 2) / (sgSlot.m.recover * brokenRegenRate)) + 15; + const sgSlot = this.findInternalByGroup('sg'); + if (sgSlot != null) { + let brokenRegenRate = 1 + sgSlot.m.getModValue('brokenregen'); + // 50% of shield strength / recovery recharge rate + 15 second delay before recharge starts + return ((this.shield / 2) / (sgSlot.m.recover * brokenRegenRate)) + 15; + } } return 0; } @@ -210,10 +212,12 @@ export default class Ship { */ calcShieldRecharge() { if (this.shield > 0) { - let sgSlot = this.findInternalByGroup('sg'); - let regenRate = 1 + sgSlot.m.getModValue('regen'); - // 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 sgSlot = this.findInternalByGroup('sg'); + if (sgSlot != null) { + let regenRate = 1 + sgSlot.m.getModValue('regen'); + // 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); + } } return 0; } @@ -401,15 +405,15 @@ export default class Ship { */ setModification(m, name, value) { // Handle special cases - if (name == 'pgen') { + if (name === 'pgen') { // Power generation m.setModValue(name, value); this.updatePowerGenerated(); - } else if (name == 'power') { + } else if (name === 'power') { // Power usage m.setModValue(name, value); this.updatePowerUsed(); - } else if (name == 'mass') { + } else if (name === 'mass') { // Mass let oldMass = m.getMass(); m.setModValue(name, value); @@ -418,32 +422,37 @@ export default class Ship { this.ladenMass = this.ladenMass - oldMass + newMass; this.updateTopSpeed(); this.updateJumpStats(); - } else if (name == 'maxfuel') { + } else if (name === 'maxfuel') { m.setModValue(name, value); this.updateJumpStats(); - } else if (name == 'optmass') { + } else if (name === 'optmass') { m.setModValue(name, value); // Could be for any of thrusters, FSD or shield this.updateTopSpeed(); this.updateJumpStats(); - this.updateShield(); - } else if (name == 'optmul') { + this.recalculateShield(); + } else if (name === 'optmul') { m.setModValue(name, value); // Could be for any of thrusters, FSD or shield this.updateTopSpeed(); this.updateJumpStats(); - this.updateShield(); - } else if (name == 'shieldboost') { + this.recalculateShield(); + } else if (name === 'shieldboost') { m.setModValue(name, value); - this.updateShield(); - } else if (name == 'hullboost') { + this.recalculateShield(); + } else if (name === 'hullboost' || name === 'hullreinforcement') { m.setModValue(name, value); - this.updateArmour(); - } else if (name == 'burst' || name == 'clip' || name == 'damage' || name == 'distdraw' || name == 'jitter' || name == 'piercing' || name == 'range' || name == 'reload' || name == 'rof' || name == 'thermload') { + this.recalculateArmour(); + } else if (name === 'burst' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') { m.setModValue(name, value); this.recalculateDps(); this.recalculateHps(); this.recalculateEps(); + } else if (name === 'explres' || name === 'kinres' || name === 'thermres') { + m.setModValue(name, value); + // Could be for shields or armour + this.recalculateArmour(); + this.recalculateShield(); } else { // Generic m.setModValue(name, value); @@ -473,7 +482,18 @@ export default class Ship { this.shield = this.baseShieldStrength; this.totalCost = this.m.incCost ? this.m.discountedCost : 0; this.unladenMass = this.hullMass; + this.totalDpe = 0; + this.totalExplDpe = 0; + this.totalKinDpe = 0; + this.totalThermDpe = 0; this.totalDps = 0; + this.totalExplDps = 0; + this.totalKinDps = 0; + this.totalThermDps = 0; + this.totalSDps = 0; + this.totalExplSDps = 0; + this.totalKinSDps = 0; + this.totalThermSDps = 0; this.totalEps = 0; this.totalHps = 0; this.shieldExplRes = 0; @@ -544,8 +564,11 @@ export default class Ship { this.updatePowerGenerated() .updatePowerUsed() .updateJumpStats() - .updateShield() - .updateArmour() + .recalculateShield() + .recalculateArmour() + .recalculateDps() + .recalculateEps() + .recalculateHps() .updateTopSpeed(); } @@ -688,20 +711,23 @@ export default class Ship { slot.enabled = enabled; if (slot.m) { if (ModuleUtils.isShieldGenerator(slot.m.grp) || slot.m.grp == 'sb') { - this.updateShield(); + this.recalculateShield(); } + + this.updatePowerUsed(); + this.updatePowerEnabledString(); + if (slot.m.getDps()) { - this.totalDps += slot.m.getDps() * (enabled ? 1 : -1); - } - if (slot.m.getEps()) { - this.totalEps += slot.m.getEps() * (enabled ? 1 : -1); + this.recalculateDps(); } + if (slot.m.getHps()) { - this.totalHps += slot.m.getHps() * (enabled ? 1 : -1); + this.recalculateHps(); } - this.updatePowerUsed(); - this.updatePowerEnabledString(); + if (slot.m.getEps()) { + this.recalculateEps(); + } } } return this; @@ -738,6 +764,9 @@ export default class Ship { updateStats(slot, n, old, preventUpdate) { let powerGeneratedChange = slot == this.standard[0]; let powerUsedChange = false; + let dpsChanged = n && n.getDps() || old && old.getDps(); + 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'); @@ -761,16 +790,6 @@ export default class Ship { powerUsedChange = true; } - if (old.getDps()) { - this.totalDps -= old.getDps(); - } - if (old.getEps()) { - this.totalEps -= old.getEps(); - } - if (old.getHps()) { - this.totalHps -= old.getHps(); - } - this.unladenMass -= old.getMass() || 0; } @@ -792,22 +811,21 @@ export default class Ship { powerUsedChange = true; } - if (n.getDps()) { - this.totalDps += n.getDps(); - } - if (n.getEps()) { - this.totalEps += n.getEps(); - } - if (n.getHps()) { - this.totalHps += n.getHps(); - } - this.unladenMass += n.getMass() || 0; } this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity; if (!preventUpdate) { + if (dpsChanged) { + this.recalculateDps(); + } + if (epsChanged) { + this.recalculateEps(); + } + if (hpsChanged) { + this.recalculateHps(); + } if (powerGeneratedChange) { this.updatePowerGenerated(); } @@ -815,10 +833,10 @@ export default class Ship { this.updatePowerUsed(); } if (armourChange) { - this.updateArmour(); + this.recalculateArmour(); } if (shieldChange) { - this.updateShield(); + this.recalculateShield(); } this.updateTopSpeed(); this.updateJumpStats(); @@ -826,20 +844,108 @@ export default class Ship { return this; } + /** + * Calculate diminishing returns value, where values below a given limit are returned + * as-is, and values between the lower and upper limit of the diminishing returns are + * given at half value. + * Commonly used for resistances. + * @param {Number} val The value + * @param {Number} drll The lower limit for diminishing returns + * @param {Number} drul The upper limit for diminishing returns + * @return {this} The ship instance (for chaining operations) + */ + diminishingReturns(val, drll, drul) { + if (val > drll) { + val = drll + (val - drll) / 2; + } + if (val > drul) { + val = drul; + } + return val; + } + /** * Calculate damage per second for weapons * @return {this} The ship instance (for chaining operations) */ recalculateDps() { + let totalDpe = 0; + let totalExplDpe = 0; + let totalKinDpe = 0; + let totalThermDpe = 0; let totalDps = 0; + let totalExplDps = 0; + let totalKinDps = 0; + let totalThermDps = 0; + let totalSDps = 0; + let totalExplSDps = 0; + let totalKinSDps = 0; + let totalThermSDps = 0; for (let slotNum in this.hardpoints) { const slot = this.hardpoints[slotNum]; if (slot.m && slot.enabled && slot.m.getDps()) { - totalDps += slot.m.getDps(); + const dpe = slot.m.getDps() / slot.m.getEps(); + const dps = slot.m.getDps(); + const sdps = slot.m.getClip() ? (slot.m.getClip() * slot.m.getDps() / slot.m.getRoF()) / ((slot.m.getClip() / slot.m.getRoF()) + slot.m.getReload()) : dps; + + totalDpe += dpe; + totalDps += dps; + totalSDps += sdps; + if (slot.m.type === 'E') { + totalExplDpe += dpe; + totalExplDps += dps; + totalExplSDps += sdps; + } + if (slot.m.type === 'K') { + totalKinDpe += dpe; + totalKinDps += dps; + totalKinSDps += sdps; + } + if (slot.m.type === 'T') { + totalThermDpe += dpe; + totalThermDps += dps; + totalThermSDps += sdps; + } + if (slot.m.type === 'EK') { + totalExplDpe += dpe / 2; + totalKinDpe += dpe / 2; + totalExplDps += dps / 2; + totalKinDps += dps / 2; + totalExplSDps += sdps / 2; + totalKinSDps += sdps / 2; + } + if (slot.m.type === 'ET') { + totalExplDpe += dpe / 2; + totalThermDpe += dpe / 2; + totalExplDps += dps / 2; + totalThermDps += dps / 2; + totalExplSDps += sdps / 2; + totalThermSDps += sdps / 2; + } + if (slot.m.type === 'KT') { + totalKinDpe += dpe / 2; + totalThermDpe += dpe / 2; + totalKinDps += dps / 2; + totalThermDps += dps / 2; + totalKinSDps += sdps / 2; + totalThermSDps += sdps / 2; + } } } + + this.totalDpe = totalDpe; + this.totalExplDpe = totalExplDpe; + this.totalKinDpe = totalKinDpe; + this.totalThermDpe = totalThermDpe; this.totalDps = totalDps; + this.totalExplDps = totalExplDps; + this.totalKinDps = totalKinDps; + this.totalThermDps = totalThermDps; + this.totalSDps = totalSDps; + this.totalExplSDps = totalExplSDps; + this.totalKinSDps = totalKinSDps; + this.totalThermSDps = totalThermSDps; return this; } @@ -958,34 +1064,51 @@ export default class Ship { * Update shield * @return {this} The ship instance (for chaining operations) */ - updateShield() { - // Base shield from generator - let baseShield = 0; - let sgSlot = this.findInternalByGroup('sg'); - if (sgSlot && sgSlot.enabled) { - baseShield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1); - } + recalculateShield() { + let shield = 0; + let shieldExplRes = null; + let shieldKinRes = null; + let shieldThermRes = null; - let shield = baseShield; - - // Shield from boosters - for (let slot of this.hardpoints) { - if (slot.m && slot.m.grp == 'sb') { - shield += baseShield * slot.m.getShieldBoost(); + 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; + shieldExplRes = 1 - sgSlot.m.getExplosiveResistance(); + shieldKinRes = 1 - sgSlot.m.getKineticResistance(); + shieldThermRes = 1 - sgSlot.m.getThermalResistance(); + + // Shield from boosters + for (let slot of this.hardpoints) { + if (slot.m && slot.m.grp == 'sb') { + shield += baseShield * slot.m.getShieldBoost(); + shieldExplRes *= (1 - slot.m.getExplosiveResistance()); + shieldKinRes *= (1 - slot.m.getKineticResistance()); + shieldThermRes *= (1 - slot.m.getThermalResistance()); + } } } + 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; + this.shieldThermRes = shieldThermRes ? 1 - this.diminishingReturns(1 - shieldThermRes, 0.5, 0.75) : null; + return this; } /** - * Update armour + * Update armour and hull resistances * @return {this} The ship instance (for chaining operations) */ - updateArmour() { + recalculateArmour() { // Armour from bulkheads let bulkhead = this.bulkheads.m; let armour = this.baseArmour + (this.baseArmour * bulkhead.getHullBoost()); + let hullExplRes = 1 - bulkhead.getExplosiveResistance(); + let hullKinRes = 1 - bulkhead.getKineticResistance(); + let hullThermRes = 1 - bulkhead.getThermalResistance(); // Armour from HRPs for (let slot of this.internal) { @@ -993,9 +1116,18 @@ export default class Ship { armour += slot.m.getHullReinforcement(); // Hull boost for HRPs is applied against the ship's base armour armour += this.baseArmour * slot.m.getModValue('hullboost'); + + hullExplRes *= (1 - slot.m.getExplosiveResistance()); + hullKinRes *= (1 - slot.m.getKineticResistance()); + hullThermRes *= (1 - slot.m.getThermalResistance()); } } + this.armour = armour; + 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); + return this; } From 5b81a0b25f5a4ed1e8cc9aebb88c3bb9808b34b2 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Thu, 10 Nov 2016 15:18:05 +0000 Subject: [PATCH 12/44] Fix modification value for additive modifications --- src/app/shipyard/Module.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index a7f152de..dbc3488e 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -59,22 +59,20 @@ export default class Module { * @return {Number} the mass of this module */ _getModifiedValue(name, additive) { - let result = 0; - if (this[name]) { - result = this[name]; - if (result) { - const modValue = this.getModValue(name); - if (modValue) { - if (additive) { - result = result + modValue; - } else { - result = result * (1 + modValue); - } + let result = this[name] || (additive ? 0 : null); // Additive NULL === 0 + if (result != null) { + const modValue = this.getModValue(name); + if (modValue) { + if (additive) { + result = result + modValue; + } else { + result = result * (1 + modValue); } } } return result; } + /** * Get the power generation of this module, taking in to account modifications * @return {Number} the power generation of this module From cf6d32ea04ebbaa99845496fd8dfbe8d297b7105 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Thu, 10 Nov 2016 22:15:42 +0000 Subject: [PATCH 13/44] Enable boost display even if power distributor is disabled --- ChangeLog.md | 1 + src/app/shipyard/Ship.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index afea02ee..7848244e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -11,3 +11,4 @@ * Fix hardpoint comparison DPS number when selecting an alternate module * Ensure that retrofit tab only shows changed modules * Fix import and export of ships with modifications, bump schema version to 4 + * Enable boost display even if power distributor is disabled diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index e7cdb415..e71f8ac6 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -126,7 +126,6 @@ export default class Ship { */ canBoost() { return this.canThrust() && // Thrusters operational - this.getSlotStatus(this.standard[4]) == 3 && // Power distributor operational this.boostEnergy <= this.standard[4].m.getEnginesCapacity(); // PD capacitor is sufficient for boost } From 87e903e473e9320928e41f91ca6b1250447b8e32 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 11 Nov 2016 00:15:49 +0000 Subject: [PATCH 14/44] Add 'Offence summary' and 'Defence summary' components --- ChangeLog.md | 2 + src/app/components/DefenceSummary.jsx | 74 +++++++----------- src/app/components/OffenceSummary.jsx | 106 +++++++++----------------- src/app/i18n/en.js | 7 +- src/app/shipyard/Module.js | 16 ++++ src/app/shipyard/Ship.js | 28 ++++++- src/less/icons.less | 6 ++ 7 files changed, 119 insertions(+), 120 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 7848244e..db3c3fef 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,3 +12,5 @@ * Ensure that retrofit tab only shows changed modules * Fix import and export of ships with modifications, bump schema version to 4 * Enable boost display even if power distributor is disabled + * Calculate breakdown of ship offensive and defensive stats + * Add 'Offence summary' and 'Defence summary' components diff --git a/src/app/components/DefenceSummary.jsx b/src/app/components/DefenceSummary.jsx index 909ce89f..2c77f947 100644 --- a/src/app/components/DefenceSummary.jsx +++ b/src/app/components/DefenceSummary.jsx @@ -1,6 +1,7 @@ import React from 'react'; import cn from 'classnames'; import TranslatedComponent from './TranslatedComponent'; +import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons'; /** * Defence summary @@ -28,62 +29,41 @@ export default class DefenceSummary extends TranslatedComponent { let { formats, translate, units } = language; let hide = tooltip.bind(null, null); - let sgClassNames = cn({ muted: !ship.findInternalByGroup('sg') }); - return ( -
-

{translate('defence summary')}

-
-
{translate('EPS')} {translate('HPS')} {translate('armour')}{translate('shields')}{translate('shields')} {translate('mass')} {translate('cargo')} {translate('fuel')}{translate('MLF')}
{translate('strength')}{translate('recovery')}{translate('recharge')} {translate('hull')} {translate('unladen')} {translate('laden')}{f1(ship.totalHps)} {int(ship.armour)} {int(ship.shield)} {u.MJ}{sgRecover}{sgRecharge} {ship.hullMass} {u.T} {int(ship.unladenMass)} {u.T} {int(ship.ladenMass)} {u.T}
- - - - - - - - - - - - - { ship.shield ? + +

{translate('defence summary')}

+
{translate('damage to')}{translate('damage from')}
{translate('explosive')}{translate('kinetic')}{translate('thermal')}
+ + {ship.shield ? - - - - + : null } - - - - - - - -
{translate('shields')}{ship.shieldExplRes ? formats.pct(ship.shieldExplRes) : '-'}{ship.shieldKinRes ? formats.pct(ship.shieldKinRes) : '-'}{ship.shieldThermRes ? formats.pct(ship.shieldThermRes) : '-'}

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

{translate('hull')}{formats.pct(ship.hullExplRes)}{formats.pct(ship.hullKinRes)}{formats.pct(ship.hullThermRes)}
-
- { ship.shield ? - - + {ship.shield ? - - + + + + + : null } + + { ship.shield && ship.shieldCells ? - - - + + : null } + + + - - + - - - + + + + -
{translate('shields')}
{translate('damage from')} {formats.pct1(ship.shieldExplRes || 1)} {formats.pct1(ship.shieldKinRes || 1)} {formats.pct1(ship.shieldThermRes || 1)}
{translate('strength')}{translate('recovery')}{translate('recovery')}

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

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

{formats.int(ship.shield)} {units.MJ}{ship.shield ? formats.time(ship.calcShieldRecovery()) : '-'}{ship.shield ? formats.time(ship.calcShieldRecharge()) : '-'}{translate('damage from')} {formats.pct1(ship.hullExplRes || 1)} {formats.pct1(ship.hullKinRes || 1)} {formats.pct1(ship.hullThermRes || 1)}
: null } -
+ + ); } } diff --git a/src/app/components/OffenceSummary.jsx b/src/app/components/OffenceSummary.jsx index a32f3ec0..4b4237c0 100644 --- a/src/app/components/OffenceSummary.jsx +++ b/src/app/components/OffenceSummary.jsx @@ -1,6 +1,7 @@ import React from 'react'; import cn from 'classnames'; import TranslatedComponent from './TranslatedComponent'; +import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons'; /** * Offence summary @@ -29,75 +30,42 @@ export default class OffenceSummary extends TranslatedComponent { let hide = tooltip.bind(null, null); return ( -
-

{translate('offence summary')}

-
- - - - - - - - - - - - - - - - - - - - -
{translate('dps')}
{translate('explosive')}{translate('kinetic')}{translate('thermal')}{translate('total')}
{formats.f1(ship.totalExplDps)}{formats.f1(ship.totalKinDps)}{formats.f1(ship.totalThermDps)}{formats.f1(ship.totalDps)}
-
- - - - - - - - - - - - - - - - - - - - -
{translate('sustained dps')}
{translate('explosive')}{translate('kinetic')}{translate('thermal')}{translate('total')}
{formats.f1(ship.totalExplSDps)}{formats.f1(ship.totalKinSDps)}{formats.f1(ship.totalThermSDps)}{formats.f1(ship.totalSDps)}
-
- - - - - - - - - - - - - - - - - - - - -
{translate('dpe')}
{translate('explosive')}{translate('kinetic')}{translate('thermal')}{translate('total')}
{formats.f1(ship.totalExplDpe)}{formats.f1(ship.totalKinDpe)}{formats.f1(ship.totalThermDpe)}{formats.f1(ship.totalDpe)}
-
+ +

{translate('offence summary')}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

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

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

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

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

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

{translate('damage by')} {formats.f1(ship.totalExplDpe)} {formats.f1(ship.totalKinDpe)} {formats.f1(ship.totalThermDpe)}
+
); } } diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index 6276f0d1..cc70e86c 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -92,14 +92,18 @@ export const terms = { // Unit for seconds secs: 's', - // Hardpoint abbreviations + // Weapon, offence and defence dpe: 'Damage per MJ of energy', dps: 'Damage per second', + sdps: 'Sustained damage per second', dpssdps: 'Damage per second (sustained damage per second)', eps: 'Energy per second', epsseps: 'Energy per second (sustained energy per second)', hps: 'Heat per second', hpsshps: 'Heat per second (sustained heat per second)', + 'damage by': 'Damage by', + 'damage from': 'Damage from', + 'shield cells': 'Shield cells', // Modifications ammo: 'Ammunition maximum', @@ -134,6 +138,7 @@ export const terms = { rof: 'Rate of fire', shield: 'Shield', shieldboost: 'Shield boost', + shieldreinforcement: 'Shield reinforcement', spinup: 'Spin up time', syscap: 'Systems capacity', sysrate: 'Systems recharge rate', diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index dbc3488e..cb1a5d25 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -492,4 +492,20 @@ export default class Module { return this._getModifiedValue('hullboost'); } + /** + * Get the shield reinforcement for this module, taking in to account modifications + * @return {Number} the shield reinforcement for this module + */ + getShieldReinforcement() { + return this._getModifiedValue('shieldreinforcement'); + } + + /** + * Get the cells for this module, taking in to account modifications + * @return {Number} the cells for this module + */ + getCells() { + return this._getModifiedValue('cells'); + } + } diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index e71f8ac6..0de12dec 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -236,9 +236,8 @@ export default class Ship { sg = sgSlot.m; } - // TODO obtain shield boost - // return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, this.shieldMultiplier + (multiplierDelta || 0)); - return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 0 + (multiplierDelta || 0)); + // TODO Not accurate if the ship has modified shield boosters + return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 1 + (multiplierDelta || 0)); } /** @@ -442,6 +441,9 @@ export default class Ship { } else if (name === 'hullboost' || name === 'hullreinforcement') { m.setModValue(name, value); this.recalculateArmour(); + } else if (name === 'shieldreinforcement') { + m.setModValue(name, value); + this.recalculateShieldCells(); } else if (name === 'burst' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') { m.setModValue(name, value); this.recalculateDps(); @@ -479,6 +481,7 @@ export default class Ship { this.ladenMass = 0; this.armour = this.baseArmour; this.shield = this.baseShieldStrength; + this.shieldCells = 0; this.totalCost = this.m.incCost ? this.m.discountedCost : 0; this.unladenMass = this.hullMass; this.totalDpe = 0; @@ -564,6 +567,7 @@ export default class Ship { .updatePowerUsed() .updateJumpStats() .recalculateShield() + .recalculateShieldCells() .recalculateArmour() .recalculateDps() .recalculateEps() @@ -1097,6 +1101,24 @@ export default class Ship { return this; } + /** + * Update shield cells + * @return {this} The ship instance (for chaining operations) + */ + recalculateShieldCells() { + let shieldCells = 0; + + for (let slot of this.internal) { + if (slot.m && slot.m.grp == 'scb') { + shieldCells += slot.m.getShieldReinforcement() * slot.m.getCells(); + } + } + + this.shieldCells = shieldCells; + + return this; + } + /** * Update armour and hull resistances * @return {this} The ship instance (for chaining operations) diff --git a/src/less/icons.less b/src/less/icons.less index 91362fd6..9af0c0e6 100755 --- a/src/less/icons.less +++ b/src/less/icons.less @@ -56,3 +56,9 @@ height: 2em; } } + +.summary { + stroke: @fg; + stroke-width: 10; + fill: @fg; +} From ad570534a0911cd7904403cc3700f8226602eaf9 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 11 Nov 2016 00:21:53 +0000 Subject: [PATCH 15/44] Update test outputs --- .../anaconda-test-detailed-export-v4.json | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/__tests__/fixtures/anaconda-test-detailed-export-v4.json b/__tests__/fixtures/anaconda-test-detailed-export-v4.json index cda62b18..4bda3ad3 100644 --- a/__tests__/fixtures/anaconda-test-detailed-export-v4.json +++ b/__tests__/fixtures/anaconda-test-detailed-export-v4.json @@ -264,16 +264,28 @@ "topBoost": 248.62, "topSpeed": 186.46, "totalCost": 882362058, + "totalDpe": 127.26, "totalDps": 97.74, "totalEps": 22.71, "totalHps": 677.29, + "totalExplDpe": 0, + "totalExplDps": 0, + "totalExplSDps": 0, + "totalHps": 33.28, + "totalKinDpe": 103.97, + "totalKinDps": 28.92, + "totalKinSDps": 21.23, + "totalSDps": 85.77, + "totalThermDpe": 23.29, + "totalThermDps": 68.82, + "totalThermSDps": 64.53, "agility": 2, "baseShieldStrength": 350, "baseArmour": 945, - "hullExplRes": 0, - "hullKinRes": 0, + "hullExplRes": 0.78, + "hullKinRes": 0.73, "hullMass": 400, - "hullThermRes": 0, + "hullThermRes": 1.37, "masslock": 23, "pipSpeed": 0.14, "moduleCostMultiplier": 1, @@ -293,8 +305,9 @@ "ladenFastestRange": 66.15, "maxJumpCount": 4, "shield": 833, - "shieldExplRes": 0, - "shieldKinRes": 0, - "shieldThermRes": 0 + "shieldCells": 1840, + "shieldExplRes": 0.5, + "shieldKinRes": 0.6, + "shieldThermRes": 1.2 } } From 782603727a8906bb4d007b807fde43eb0af1fff1 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 11 Nov 2016 00:39:14 +0000 Subject: [PATCH 16/44] Re-add recovery/recharge times and tweak styling --- src/app/components/DefenceSummary.jsx | 10 ++++++++-- src/app/components/OffenceSummary.jsx | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/app/components/DefenceSummary.jsx b/src/app/components/DefenceSummary.jsx index 2c77f947..d9ec54a9 100644 --- a/src/app/components/DefenceSummary.jsx +++ b/src/app/components/DefenceSummary.jsx @@ -32,13 +32,20 @@ export default class DefenceSummary extends TranslatedComponent { return (

{translate('defence summary')}

- +
{ship.shield ? : null } {ship.shield ? + + + + + + : null } + {ship.shield ? @@ -54,7 +61,6 @@ export default class DefenceSummary extends TranslatedComponent { - diff --git a/src/app/components/OffenceSummary.jsx b/src/app/components/OffenceSummary.jsx index 4b4237c0..6ba0a93d 100644 --- a/src/app/components/OffenceSummary.jsx +++ b/src/app/components/OffenceSummary.jsx @@ -32,7 +32,7 @@ export default class OffenceSummary extends TranslatedComponent { return (

{translate('offence summary')}

-

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

{translate('recovery')}{formats.time(ship.calcShieldRecovery())}{translate('recharge')}{formats.time(ship.calcShieldRecharge())}
{translate('damage from')} {formats.pct1(ship.shieldExplRes || 1)}

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

{translate('damage from')} {formats.pct1(ship.hullExplRes || 1)}
+
From 606eabfec74dbfd123b3de6feb42aab976254662 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 11 Nov 2016 11:15:56 +0000 Subject: [PATCH 17/44] Fix issues with losing precision due to using decimal modification values. Validate modification information --- src/app/components/InternalSlot.jsx | 2 +- src/app/components/Modification.jsx | 12 ++++----- src/app/shipyard/Module.js | 20 +++++++-------- src/app/shipyard/Ship.js | 38 ++++++++++++++++++++--------- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index 84e12a8a..fb81b260 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -49,7 +49,7 @@ export default class InternalSlot extends Slot { { m.rangeLS ?
{translate('range')}: {m.rangeLS}{u.Ls}
: null } { m.rangeLS === null ?
∞{u.Ls}
: null } { m.rangeRating ?
{translate('range')}: {m.rangeRating}
: null } - { m.getHullReinforcement() ?
+{formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost'))} {translate('armour')}
: null } + { m.getHullReinforcement() ?
+{formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)} {translate('armour')}
: null } { m.passengers ?
{translate('passengers')}: {m.passengers}
: null } { m && validMods.length > 0 ?
: null } diff --git a/src/app/components/Modification.jsx b/src/app/components/Modification.jsx index c73d0607..f9a74df4 100644 --- a/src/app/components/Modification.jsx +++ b/src/app/components/Modification.jsx @@ -24,7 +24,7 @@ export default class ModificationsMenu extends TranslatedComponent { constructor(props, context) { super(props); this.state = {}; - this.state.value = this.props.m.getModValue(this.props.name) * 100 || 0; + this.state.value = this.props.m.getModValue(this.props.name) / 100 || 0; } /** @@ -32,14 +32,14 @@ export default class ModificationsMenu extends TranslatedComponent { * @param {Number} value The value to set */ _updateValue(value) { - let scaledValue = Math.floor(Number(value) * 100) / 10000; + let scaledValue = Math.floor(Number(value) * 100); // Limit to +1000% / -100% - if (scaledValue > 10) { - scaledValue = 10; + if (scaledValue > 10000) { + scaledValue = 10000; value = 1000; } - if (scaledValue < -1) { - scaledValue = -1; + if (scaledValue < -1000) { + scaledValue = -1000; value = -100; } let m = this.props.m; diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index cb1a5d25..8eb3befc 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -28,16 +28,16 @@ export default class Module { /** * Get a value for a given modification * @param {Number} name The name of the modification - * @return {Number} The value of the modification, as a decimal value where 1 is 100% + * @return {Number} The value of the modification, as an integer value scaled so that 1.23% == 123 */ getModValue(name) { - return this.mods && this.mods[name] ? this.mods[name] / 10000 : null; + return this.mods && this.mods[name] ? this.mods[name] : null; } /** * Set a value for a given modification ID * @param {Number} name The name of the modification - * @param {Number} value The value of the modification, as a decimal value where 1 is 100% + * @param {Number} value The value of the modification, as an integer scaled so that -2.34% == -234 */ setModValue(name, value) { if (!this.mods) { @@ -47,8 +47,8 @@ export default class Module { if (value == null || value == 0) { delete this.mods[name]; } else { - // Store value with 2dp - this.mods[name] = Math.round(value * 10000); + // Round just to be sure + this.mods[name] = Math.round(value); } } @@ -61,7 +61,7 @@ export default class Module { _getModifiedValue(name, additive) { let result = this[name] || (additive ? 0 : null); // Additive NULL === 0 if (result != null) { - const modValue = this.getModValue(name); + const modValue = this.getModValue(name) / 10000; if (modValue) { if (additive) { result = result + modValue; @@ -299,7 +299,7 @@ export default class Module { if (this['minmass']) { result = this['minmass']; if (result) { - let mult = this.getModValue('optmass'); + let mult = this.getModValue('optmass') / 10000; if (mult) { result = result * (1 + mult); } } } @@ -324,7 +324,7 @@ export default class Module { if (this['maxmass']) { result = this['maxmass']; if (result) { - let mult = this.getModValue('optmass'); + let mult = this.getModValue('optmass') / 10000; if (mult) { result = result * (1 + mult); } } } @@ -341,7 +341,7 @@ export default class Module { if (this['minmul']) { result = this['minmul']; if (result) { - let mult = this.getModValue('optmul'); + let mult = this.getModValue('optmul') / 10000; if (mult) { result = result * (1 + mult); } } } @@ -366,7 +366,7 @@ export default class Module { if (this['maxmul']) { result = this['maxmul']; if (result) { - let mult = this.getModValue('optmul'); + let mult = this.getModValue('optmul') / 10000; if (mult) { result = result * (1 + mult); } } } diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 0de12dec..11b5db17 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -195,7 +195,7 @@ export default class Ship { if (this.shield > 0) { const sgSlot = this.findInternalByGroup('sg'); if (sgSlot != null) { - let brokenRegenRate = 1 + sgSlot.m.getModValue('brokenregen'); + 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; } @@ -213,7 +213,7 @@ export default class Ship { if (this.shield > 0) { const sgSlot = this.findInternalByGroup('sg'); if (sgSlot != null) { - let regenRate = 1 + sgSlot.m.getModValue('regen'); + 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); } @@ -399,7 +399,7 @@ export default class Ship { * Set a modification value * @param {Object} m The module to change * @param {Object} name The name of the modification to change - * @param {Number} value The new value of the modification + * @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123 */ setModification(m, name, value) { // Handle special cases @@ -1136,7 +1136,7 @@ export default class Ship { if (slot.m && slot.m.grp == 'hr') { armour += slot.m.getHullReinforcement(); // Hull boost for HRPs is applied against the ship's base armour - armour += this.baseArmour * slot.m.getModValue('hullboost'); + armour += this.baseArmour * slot.m.getModValue('hullboost') / 10000; hullExplRes *= (1 - slot.m.getExplosiveResistance()); hullKinRes *= (1 - slot.m.getKineticResistance()); @@ -1220,7 +1220,7 @@ export default class Ship { let bulkheadMods = new Array(); if (this.bulkheads.m && this.bulkheads.m.mods) { for (let modKey in this.bulkheads.m.mods) { - bulkheadMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(this.bulkheads.m.getModValue(modKey) * 10000)); + bulkheadMods.push(Modifications.modifiers.indexOf(modKey) + ':' + this.bulkheads.m.getModValue(modKey)); } } allMods.push(bulkheadMods.join(';')); @@ -1229,7 +1229,7 @@ export default class Ship { let slotMods = new Array(); if (slot.m && slot.m.mods) { for (let modKey in slot.m.mods) { - slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000)); + slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + slot.m.getModValue(modKey)); } } allMods.push(slotMods.join(';')); @@ -1238,7 +1238,7 @@ export default class Ship { let slotMods = new Array(); if (slot.m && slot.m.mods) { for (let modKey in slot.m.mods) { - slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000)); + slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + slot.m.getModValue(modKey)); } } allMods.push(slotMods.join(';')); @@ -1247,7 +1247,7 @@ export default class Ship { let slotMods = new Array(); if (slot.m && slot.m.mods) { for (let modKey in slot.m.mods) { - slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000)); + slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + slot.m.getModValue(modKey)); } } allMods.push(slotMods.join(';')); @@ -1295,7 +1295,10 @@ export default class Ship { let bulkheadMods = new Array(); if (this.bulkheads.m && this.bulkheads.m.mods) { for (let modKey in this.bulkheads.m.mods) { - bulkheadMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(this.bulkheads.m.getModValue(modKey) * 10000) }); + // Filter out invalid modifications + if (Modifications.validity['bh'] && Modifications.validity['bh'].indexOf(modKey) != -1) { + bulkheadMods.push({ id: Modifications.modifiers.indexOf(modKey), value: this.bulkheads.m.getModValue(modKey) }); + } } } slots.push(bulkheadMods); @@ -1304,7 +1307,10 @@ export default class Ship { let slotMods = new Array(); if (slot.m && slot.m.mods) { for (let modKey in slot.m.mods) { - slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) }); + // Filter out invalid modifications + if (Modifications.validity[slot.m.grp] && Modifications.validity[slot.m.grp].indexOf(modKey) != -1) { + slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: slot.m.getModValue(modKey) }); + } } } slots.push(slotMods); @@ -1314,7 +1320,10 @@ export default class Ship { let slotMods = new Array(); if (slot.m && slot.m.mods) { for (let modKey in slot.m.mods) { - slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) }); + // Filter out invalid modifications + if (Modifications.validity[slot.m.grp] && Modifications.validity[slot.m.grp].indexOf(modKey) != -1) { + slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: slot.m.getModValue(modKey) }); + } } } slots.push(slotMods); @@ -1324,7 +1333,10 @@ export default class Ship { let slotMods = new Array(); if (slot.m && slot.m.mods) { for (let modKey in slot.m.mods) { - slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) }); + // Filter out invalid modifications + if (Modifications.validity[slot.m.grp] && Modifications.validity[slot.m.grp].indexOf(modKey) != -1) { + slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: slot.m.getModValue(modKey) }); + } } } slots.push(slotMods); @@ -1350,6 +1362,7 @@ export default class Ship { for (let slotMod of slot) { buffer.writeInt8(slotMod.id, curpos++); buffer.writeInt32LE(slotMod.value, curpos); + // console.log('ENCODE Slot ' + i + ': ' + Modifications.modifiers[slotMod.id] + ' = ' + slotMod.value); curpos += 4; } buffer.writeInt8(-1, curpos++); @@ -1382,6 +1395,7 @@ export default class Ship { while (modificationId != -1) { let modificationValue = buffer.readInt32LE(curpos); curpos += 4; + // console.log('DECODE Slot ' + slot + ': ' + Modifications.modifiers[modificationId] + ' = ' + modificationValue); modifications[Modifications.modifiers[modificationId]] = modificationValue; modificationId = buffer.readInt8(curpos++); } From 3d4f6d786120626b1d47e82ac4a4ac2ac069c342 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 11 Nov 2016 12:04:30 +0000 Subject: [PATCH 18/44] Slightly friendlier modifications --- src/app/components/Modification.jsx | 11 ++++++----- src/app/shipyard/Ship.js | 5 +++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/app/components/Modification.jsx b/src/app/components/Modification.jsx index f9a74df4..b68d6e10 100644 --- a/src/app/components/Modification.jsx +++ b/src/app/components/Modification.jsx @@ -34,14 +34,15 @@ export default class ModificationsMenu extends TranslatedComponent { _updateValue(value) { let scaledValue = Math.floor(Number(value) * 100); // Limit to +1000% / -100% - if (scaledValue > 10000) { - scaledValue = 10000; + if (scaledValue > 100000) { + scaledValue = 100000; value = 1000; } - if (scaledValue < -1000) { - scaledValue = -1000; + if (scaledValue < -10000) { + scaledValue = -10000; value = -100; } + let m = this.props.m; let name = this.props.name; let ship = this.props.ship; @@ -62,7 +63,7 @@ export default class ModificationsMenu extends TranslatedComponent { return (
{translate(name)}{name === 'jitter' ? ' (°)' : ' (%)'}
- +
); } diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 11b5db17..50610b21 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -402,6 +402,11 @@ export default class Ship { * @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123 */ setModification(m, name, value) { + if (isNaN(value)) { + // Value passed is invalid; reset it to 0 + value = 0; + } + // Handle special cases if (name === 'pgen') { // Power generation From c96693c4397915a452ea94dadba22f31da763355 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 11 Nov 2016 12:19:08 +0000 Subject: [PATCH 19/44] Tidy up descriptions; allow total cost to be non-integer --- src/schemas/ship-loadout/4.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/schemas/ship-loadout/4.json b/src/schemas/ship-loadout/4.json index cf6876b9..d3158f9f 100644 --- a/src/schemas/ship-loadout/4.json +++ b/src/schemas/ship-loadout/4.json @@ -275,15 +275,15 @@ "minimum": 1 }, "hullExplRes": { - "description": "Resistance of the hull to explosive attacks", + "description": "Multiplier for explosive damage to hull", "type": "number" }, "hullKinRes": { - "description": "Resistance of the hull to kinetic attacks", + "description": "Multiplier for kinetic damage to hull", "type": "number" }, "hullThermRes": { - "description": "Resistance of the hull to thermal attacks", + "description": "Multiplier for thermal damage to hull", "type": "number" }, "fuelCapacity": { @@ -311,20 +311,20 @@ "minimum": 1 }, "shield": { - "description": "Shield strengh in Mega Joules (Mj)", + "description": "Shield strength in Mega Joules (Mj)", "type": "number", "minimum": 0 }, "shieldExplRes": { - "description": "Resistance of the shield to explosive attacks", + "description": "Multiplier for explosive damage to shields", "type": "number" }, "shieldKinRes": { - "description": "Resistance of the shield to kinetic attacks", + "description": "Multiplier for kinetic damage to shields", "type": "number" }, "shieldThermRes": { - "description": "Resistance of the shield to thermal attacks", + "description": "Multiplier for thermal damage to shields", "type": "number" }, "speed": { @@ -333,8 +333,8 @@ "minimum": 1 }, "totalCost": { - "type": "integer", - "minimum": 1 + "description": "Total cost of the loadout, including discounts", + "type": "number" }, "unladenRange": { "description": "Single Jump range when unladen, see unladenMass", From f489257f86faca0f444919b6916c53906c012634 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Fri, 11 Nov 2016 12:30:32 +0000 Subject: [PATCH 20/44] Update shield cell numbers when appropriate --- src/app/shipyard/Ship.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 50610b21..ad7b1cd1 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -718,9 +718,12 @@ export default class Ship { if (slot.enabled != enabled) { // Enabled state is changing slot.enabled = enabled; if (slot.m) { - if (ModuleUtils.isShieldGenerator(slot.m.grp) || slot.m.grp == 'sb') { + if (ModuleUtils.isShieldGenerator(slot.m.grp) || slot.m.grp === 'sb') { this.recalculateShield(); } + if (slot.m.grp === 'scb') { + this.recalculateShieldCells(); + } this.updatePowerUsed(); this.updatePowerEnabledString(); @@ -776,9 +779,11 @@ 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'); + + 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'); - 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'); + let shieldCellsChange = (n && n.grp === 'scb') || (old && old.grp === 'scb'); if (old) { // Old modul now being removed switch (old.grp) { @@ -846,6 +851,9 @@ export default class Ship { if (shieldChange) { this.recalculateShield(); } + if (shieldCellsChange) { + this.recalculateShieldCells(); + } this.updateTopSpeed(); this.updateJumpStats(); } From 9556f28ba469d6482f5d0b2be7a416b17503ed99 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sat, 12 Nov 2016 12:02:52 +0000 Subject: [PATCH 21/44] Add ability to import directly from companion API output --- ChangeLog.md | 3 + .../fixtures/companion-api-import-1.json | 1288 +++++++++++++++++ __tests__/test-import.js | 17 + src/app/components/ModalImport.jsx | 23 +- src/app/shipyard/Ship.js | 26 +- src/app/utils/CompanionApiUtils.js | 317 ++++ 6 files changed, 1660 insertions(+), 14 deletions(-) create mode 100644 __tests__/fixtures/companion-api-import-1.json create mode 100644 src/app/utils/CompanionApiUtils.js diff --git a/ChangeLog.md b/ChangeLog.md index db3c3fef..069f7fa3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,8 @@ * Add initial loadout passenger cabins for Beluga * Add initial loadout passenger cabins for Orca * Update costs and initial loadouts for Keelback and Type-7 + * Add resistances for hull reinforcement packages + * Added modifier actions to create modifications from raw data * Show modification icon for modified modules * Take modifications in to account when deciding whether to issue a warning on a standard module * Fix hardpoint comparison DPS number when selecting an alternate module @@ -14,3 +16,4 @@ * Enable boost display even if power distributor is disabled * Calculate breakdown of ship offensive and defensive stats * Add 'Offence summary' and 'Defence summary' components + * Add ability to import directly from companion API output diff --git a/__tests__/fixtures/companion-api-import-1.json b/__tests__/fixtures/companion-api-import-1.json new file mode 100644 index 00000000..9546ffae --- /dev/null +++ b/__tests__/fixtures/companion-api-import-1.json @@ -0,0 +1,1288 @@ +{ + "cargo": { + "capacity": 128, + "items": [ + { + "commodity": "ancientrelic", + "marked": 0, + "masq": null, + "mission": null, + "origin": 2, + "owner": 822212, + "powerplayOrigin": null, + "qty": 1, + "value": 0, + "xyz": null + } + ], + "lock": 359606096, + "qty": 1, + "ts": { + "sec": 1478848090, + "usec": 568000 + } + }, + "cockpitBreached": false, + "free": false, + "fuel": { + "main": { + "capacity": 96, + "level": 96 + }, + "reserve": { + "capacity": 1.13, + "level": 1.13 + }, + "superchargedFSD": 0 + }, + "health": { + "hull": 1000000, + "integrity": 30447, + "paintwork": 30448, + "shield": 1000000, + "shieldup": true + }, + "id": 21, + "modules": { + "Armour": { + "module": { + "free": false, + "health": 1000000, + "id": 128049372, + "name": "Federation_Corvette_Armour_Grade3", + "on": true, + "priority": 1, + "unloaned": 0, + "value": 143796630 + } + }, + "Bobble01": [], + "Bobble02": [], + "Bobble03": [], + "Bobble04": [], + "Bobble05": [], + "Bobble06": [], + "Bobble07": [], + "Bobble08": [], + "Bobble09": [], + "Bobble10": [], + "Decal1": { + "module": { + "free": false, + "health": 1000000, + "id": 128667742, + "name": "Decal_Combat_Deadly", + "on": true, + "priority": 1, + "unloaned": 0, + "value": 0 + } + }, + "Decal2": { + "module": { + "free": false, + "health": 1000000, + "id": 128667757, + "name": "Decal_Explorer_Ranger", + "on": true, + "priority": 1, + "unloaned": 0, + "value": 0 + } + }, + "Decal3": { + "module": { + "free": false, + "health": 1000000, + "id": 128667750, + "name": "Decal_Trade_Tycoon", + "on": true, + "priority": 1, + "unloaned": 0, + "value": 0 + } + }, + "EngineColour": [], + "FrameShiftDrive": { + "module": { + "free": false, + "health": 1000000, + "id": 128064127, + "modifiers": { + "engineerID": 300100, + "id": 3904, + "modifiers": [ + { + "name": "mod_mass", + "type": 1, + "value": 0.42523837089539 + }, + { + "name": "mod_health", + "type": 1, + "value": -0.2126482129097 + }, + { + "name": "mod_passive_power", + "type": 1, + "value": 0.23241454362869 + }, + { + "name": "mod_fsd_optimised_mass", + "type": 1, + "value": 0.46293264627457 + }, + { + "name": "mod_fsd_optimised_mass", + "type": 2, + "value": 0.040929391980171 + }, + { + "name": "trade_mass_health", + "type": 2, + "value": 0.088846378028393 + } + ], + "moduleTags": [ + 16 + ], + "recipeID": 128673694, + "slotIndex": 47 + }, + "name": "Int_Hyperdrive_Size6_Class5", + "on": true, + "priority": 0, + "recipeLevel": 5, + "recipeName": "FSD_LongRange", + "recipeValue": 0, + "unloaned": 0, + "value": 13752602 + } + }, + "FuelTank": { + "module": { + "free": false, + "health": 1000000, + "id": 128064350, + "name": "Int_FuelTank_Size5_Class3", + "on": true, + "priority": 1, + "unloaned": 83090, + "value": 83090 + } + }, + "HugeHardpoint1": { + "module": { + "ammo": { + "clip": 1, + "hopper": 1 + }, + "free": false, + "health": 1000000, + "id": 128681994, + "modifiers": { + "engineerID": 300180, + "id": 3380, + "modifiers": [ + { + "name": "mod_weapon_range", + "type": 1, + "value": 0.19140657782555 + }, + { + "name": "mod_weapon_damage", + "type": 1, + "value": -0.043880753219128 + }, + { + "name": "mod_weapon_active_power", + "type": 1, + "value": 0.08408235758543 + }, + { + "name": "mod_mass", + "type": 1, + "value": 0.042046930640936 + }, + { + "name": "mod_passive_power", + "type": 2, + "value": -0.094719000160694 + }, + { + "name": "special_thermalshock", + "type": 3, + "value": 1 + } + ], + "moduleTags": [ + 1, + 4 + ], + "recipeID": 128673335, + "slotIndex": 26 + }, + "name": "Hpt_BeamLaser_Gimbal_Huge", + "on": true, + "priority": 0, + "recipeLevel": 1, + "recipeName": "Weapon_LongRange", + "recipeValue": 0, + "unloaned": 0, + "value": 7871544 + } + }, + "HugeHardpoint2": { + "module": { + "ammo": { + "clip": 1, + "hopper": 1 + }, + "free": false, + "health": 1000000, + "id": 128681994, + "name": "Hpt_BeamLaser_Gimbal_Huge", + "on": true, + "priority": 0, + "unloaned": 0, + "value": 7871544 + } + }, + "LargeHardpoint1": { + "module": { + "ammo": { + "clip": 90, + "hopper": 2100 + }, + "free": false, + "health": 1000000, + "id": 128049461, + "modifiers": { + "engineerID": 300260, + "id": 4699, + "modifiers": [ + { + "name": "mod_weapon_damage", + "type": 1, + "value": 0.24115231633186 + }, + { + "name": "mod_weapon_active_power", + "type": 1, + "value": 0.61637383699417 + }, + { + "name": "mod_weapon_burst_interval", + "type": 1, + "value": -0.11122596263885 + }, + { + "name": "mod_weapon_jitter_radius", + "type": 1, + "value": 0.56468063592911 + }, + { + "name": "mod_weapon_active_heat", + "type": 1, + "value": 0.12204115837812 + }, + { + "name": "mod_weapon_hardness_piercing", + "type": 2, + "value": 0.033723440021276 + }, + { + "name": "mod_weapon_hardness_piercing", + "type": 2, + "value": -0.036045636981726 + }, + { + "name": "special_incendiary_rounds", + "type": 3, + "value": 1 + } + ], + "moduleTags": [ + 1, + 8 + ], + "recipeID": 128673502, + "slotIndex": 28 + }, + "name": "Hpt_MultiCannon_Gimbal_Large", + "on": true, + "priority": 0, + "recipeLevel": 3, + "recipeName": "Weapon_Overcharged", + "recipeValue": 0, + "unloaned": 0, + "value": 520593 + } + }, + "LifeSupport": { + "module": { + "free": false, + "health": 1000000, + "id": 128064159, + "name": "Int_LifeSupport_Size5_Class2", + "on": true, + "priority": 0, + "unloaned": 0, + "value": 67528 + } + }, + "MainEngines": { + "module": { + "free": false, + "health": 1000000, + "id": 128064094, + "modifiers": { + "engineerID": 300100, + "id": 3920, + "modifiers": [ + { + "name": "mod_engine_mass_curve_multiplier", + "type": 1, + "value": 0.16290386021137 + }, + { + "name": "mod_engine_heat", + "type": 1, + "value": 0.51375859975815 + }, + { + "name": "mod_passive_power", + "type": 1, + "value": 0.068986810743809 + }, + { + "name": "mod_health", + "type": 1, + "value": -0.12497692555189 + }, + { + "name": "mod_engine_mass_curve", + "type": 1, + "value": -0.046525910496712 + }, + { + "name": "mod_engine_mass_curve_multiplier", + "type": 2, + "value": 0.0091453474014997 + }, + { + "name": "mod_mass", + "type": 2, + "value": 0.090709120035172 + } + ], + "moduleTags": [ + 17 + ], + "recipeID": 128673657, + "slotIndex": 46 + }, + "name": "Int_Engine_Size7_Class2", + "on": true, + "priority": 0, + "recipeLevel": 3, + "recipeName": "Engine_Dirty", + "recipeValue": 0, + "unloaned": 0, + "value": 1614658 + } + }, + "MediumHardpoint1": { + "module": { + "ammo": { + "clip": 90, + "hopper": 2100 + }, + "free": false, + "health": 1000000, + "id": 128049463, + "modifiers": { + "engineerID": 300260, + "id": 4729, + "modifiers": [ + { + "name": "mod_weapon_damage", + "type": 1, + "value": 0.24256283044815 + }, + { + "name": "mod_weapon_active_power", + "type": 1, + "value": 0.61838209629059 + }, + { + "name": "mod_weapon_burst_interval", + "type": 1, + "value": -0.10679690539837 + }, + { + "name": "mod_weapon_jitter_radius", + "type": 1, + "value": 0.56370949745178 + }, + { + "name": "mod_weapon_active_heat", + "type": 1, + "value": 0.10164558887482 + }, + { + "name": "trade_weapon_damage_weapon_active_power", + "type": 2, + "value": 0.052759505808353 + }, + { + "name": "mod_health", + "type": 2, + "value": -0.032706737518311 + } + ], + "moduleTags": [ + 1, + 8 + ], + "recipeID": 128673502, + "slotIndex": 29 + }, + "name": "Hpt_MultiCannon_Turret_Medium", + "on": true, + "priority": 0, + "recipeLevel": 3, + "recipeName": "Weapon_Overcharged", + "recipeValue": 0, + "unloaned": 0, + "value": 1292800 + } + }, + "MediumHardpoint2": { + "module": { + "ammo": { + "clip": 90, + "hopper": 2100 + }, + "free": false, + "health": 1000000, + "id": 128049463, + "modifiers": { + "engineerID": 300260, + "id": 4731, + "modifiers": [ + { + "name": "mod_weapon_damage", + "type": 1, + "value": 0.2441771030426 + }, + { + "name": "mod_weapon_active_power", + "type": 1, + "value": 0.69319069385529 + }, + { + "name": "mod_weapon_burst_interval", + "type": 1, + "value": -0.10855937004089 + }, + { + "name": "mod_weapon_jitter_radius", + "type": 1, + "value": 0.55067348480225 + }, + { + "name": "mod_weapon_active_heat", + "type": 1, + "value": 0.11971006542444 + }, + { + "name": "trade_mass_health", + "type": 2, + "value": 0.0050421766936779 + }, + { + "name": "mod_passive_power", + "type": 2, + "value": 0.03293726965785 + } + ], + "moduleTags": [ + 1, + 8 + ], + "recipeID": 128673502, + "slotIndex": 30 + }, + "name": "Hpt_MultiCannon_Turret_Medium", + "on": true, + "priority": 0, + "recipeLevel": 3, + "recipeName": "Weapon_Overcharged", + "recipeValue": 0, + "unloaned": 0, + "value": 1292800 + } + }, + "PaintJob": { + "module": { + "free": false, + "health": 1000000, + "id": 128732313, + "name": "PaintJob_Federation_Corvette_Militaire_Sand", + "on": true, + "priority": 1, + "unloaned": 0, + "value": 0 + } + }, + "PlanetaryApproachSuite": { + "module": { + "free": false, + "health": 1000000, + "id": 128672317, + "name": "Int_PlanetApproachSuite", + "on": true, + "priority": 1, + "unloaned": 425, + "value": 425 + } + }, + "PowerDistributor": { + "module": { + "free": false, + "health": 1000000, + "id": 128064217, + "modifiers": { + "engineerID": 300180, + "id": 3376, + "modifiers": [ + { + "name": "mod_powerdistributor_weapon_charge", + "type": 1, + "value": 0.27520388364792 + }, + { + "name": "mod_powerdistributor_weapon_rate", + "type": 1, + "value": 0.10803784430027 + }, + { + "name": "mod_powerdistributor_system_charge", + "type": 1, + "value": -0.13366678357124 + }, + { + "name": "mod_powerdistributor_system_rate", + "type": 1, + "value": -0.029957808554173 + }, + { + "name": "mod_powerdistributor_engine_charge", + "type": 1, + "value": -0.085655435919762 + }, + { + "name": "mod_powerdistributor_engine_rate", + "type": 1, + "value": -0.12185442447662 + }, + { + "name": "mod_health", + "type": 2, + "value": 0.056631729006767 + }, + { + "name": "mod_powerdistributor_weapon_charge", + "type": 2, + "value": 0.0055946228094399 + }, + { + "name": "mod_powerdistributor_global_rate", + "type": 2, + "value": -0.011308163404465 + } + ], + "moduleTags": [ + 19 + ], + "recipeID": 128673752, + "slotIndex": 49 + }, + "name": "Int_PowerDistributor_Size8_Class5", + "on": true, + "priority": 0, + "recipeLevel": 3, + "recipeName": "PowerDistributor_PriorityWeapons", + "recipeValue": 0, + "unloaned": 0, + "value": 23161983 + } + }, + "PowerPlant": { + "module": { + "free": false, + "health": 1000000, + "id": 128064067, + "modifiers": { + "engineerID": 300100, + "id": 3942, + "modifiers": [ + { + "name": "mod_powerplant_power", + "type": 1, + "value": 0.081056952476501 + }, + { + "name": "mod_health", + "type": 1, + "value": -0.0084498468786478 + }, + { + "name": "mod_powerplant_heat", + "type": 1, + "value": 0.010082358494401 + }, + { + "name": "mod_powerplant_power", + "type": 2, + "value": 0.043923784047365 + } + ], + "moduleTags": [ + 18 + ], + "recipeID": 128673765, + "slotIndex": 45 + }, + "name": "Int_Powerplant_Size8_Class5", + "on": true, + "priority": 1, + "recipeLevel": 1, + "recipeName": "PowerPlant_Boosted", + "recipeValue": 0, + "unloaned": 0, + "value": 138198514 + } + }, + "Radar": { + "module": { + "free": false, + "health": 1000000, + "id": 128064254, + "name": "Int_Sensors_Size8_Class2", + "on": true, + "priority": 0, + "unloaned": 0, + "value": 1482367 + } + }, + "Slot01_Size7": { + "module": { + "free": false, + "health": 1000000, + "id": 128064344, + "name": "Int_CargoRack_Size7_Class1", + "on": true, + "priority": 1, + "unloaned": 0, + "value": 1001657 + } + }, + "Slot02_Size7": { + "module": { + "free": false, + "health": 1000000, + "id": 128671329, + "modifiers": { + "engineerID": 300160, + "id": 1783, + "modifiers": [ + { + "name": "mod_shield_mass_curve_multiplier", + "type": 1, + "value": 0.11387270689011 + }, + { + "name": "mod_shield_global_mult", + "type": 1, + "value": -0.084811583161354 + }, + { + "name": "mod_shield_broken_regen", + "type": 1, + "value": -0.14731486141682 + }, + { + "name": "mod_shield_normal_regen", + "type": 1, + "value": -0.036414299160242 + }, + { + "name": "mod_shield_energy_per_regen", + "type": 1, + "value": 0.022321036085486 + }, + { + "name": "trade_shield_curve_shield_curve_mult", + "type": 2, + "value": -0.11246068775654 + }, + { + "name": "mod_shield_broken_regen", + "type": 2, + "value": -0.063284143805504 + } + ], + "moduleTags": [ + 15 + ], + "recipeID": 128673837, + "slotIndex": 53 + }, + "name": "Int_ShieldGenerator_Size7_Class5_Strong", + "on": true, + "priority": 0, + "recipeLevel": 3, + "recipeName": "ShieldGenerator_Reinforced", + "recipeValue": 0, + "unloaned": 0, + "value": 69240302 + } + }, + "Slot03_Size7": { + "module": { + "ammo": { + "clip": 1, + "hopper": 4 + }, + "free": false, + "health": 1000000, + "id": 128064332, + "modifiers": { + "engineerID": 300160, + "id": 1789, + "modifiers": [ + { + "name": "mod_shieldcell_spin_up", + "type": 1, + "value": -0.086017057299614 + }, + { + "name": "mod_shieldcell_duration", + "type": 1, + "value": -0.047102440148592 + }, + { + "name": "mod_shieldcell_shield_units", + "type": 1, + "value": 0.021292699500918 + }, + { + "name": "mod_boot_time", + "type": 1, + "value": 0.071096949279308 + }, + { + "name": "mod_shieldcell_spin_up", + "type": 2, + "value": -0.018995799124241 + } + ], + "moduleTags": [ + 24 + ], + "recipeID": 128673805, + "slotIndex": 54 + }, + "name": "Int_ShieldCellBank_Size7_Class5", + "on": true, + "priority": 0, + "recipeLevel": 1, + "recipeName": "ShieldCellBank_Rapid", + "recipeValue": 0, + "unloaned": 0, + "value": 8272137 + } + }, + "Slot04_Size6": { + "module": { + "free": false, + "health": 1000000, + "id": 128064351, + "name": "Int_FuelTank_Size6_Class3", + "on": true, + "priority": 1, + "unloaned": 0, + "value": 290341 + } + }, + "Slot05_Size6": { + "module": { + "free": false, + "health": 1000000, + "id": 128666681, + "name": "Int_FuelScoop_Size6_Class5", + "on": true, + "priority": 1, + "unloaned": 0, + "value": 24449069 + } + }, + "Slot06_Size5": { + "module": { + "ammo": { + "clip": 1, + "hopper": 3 + }, + "free": false, + "health": 1000000, + "id": 128064322, + "modifiers": { + "engineerID": 300160, + "id": 1788, + "modifiers": [ + { + "name": "mod_shieldcell_spin_up", + "type": 1, + "value": -0.084282241761684 + }, + { + "name": "mod_shieldcell_duration", + "type": 1, + "value": -0.069141998887062 + }, + { + "name": "mod_shieldcell_shield_units", + "type": 1, + "value": 0.025862643495202 + }, + { + "name": "mod_boot_time", + "type": 1, + "value": 0.081815980374813 + }, + { + "name": "mod_shieldcell_duration", + "type": 2, + "value": 0.041702415794134 + } + ], + "moduleTags": [ + 24 + ], + "recipeID": 128673805, + "slotIndex": 57 + }, + "name": "Int_ShieldCellBank_Size5_Class5", + "on": false, + "priority": 0, + "recipeLevel": 1, + "recipeName": "ShieldCellBank_Rapid", + "recipeValue": 0, + "unloaned": 0, + "value": 1055120 + } + }, + "Slot07_Size5": { + "module": { + "free": false, + "health": 1000000, + "id": 128727930, + "name": "Int_FighterBay_Size5_Class1", + "on": true, + "priority": 0, + "unloaned": 0, + "value": 575643 + } + }, + "Slot08_Size4": { + "module": { + "free": false, + "health": 1000000, + "id": 128672289, + "name": "Int_BuggyBay_Size2_Class2", + "on": true, + "priority": 1, + "unloaned": 0, + "value": 18360 + } + }, + "Slot09_Size4": { + "module": { + "free": false, + "health": 1000000, + "id": 128666722, + "modifiers": { + "engineerID": 300100, + "id": 2026, + "modifiers": [ + { + "name": "mod_fsdinterdictor_range", + "type": 1, + "value": 0.086278550326824 + }, + { + "name": "mod_mass", + "type": 1, + "value": 0.07684014737606 + }, + { + "name": "mod_passive_power", + "type": 1, + "value": 0.13557997345924 + }, + { + "name": "mod_fsdinterdictor_facing_limit", + "type": 1, + "value": -0.07848084717989 + } + ], + "moduleTags": [ + 23 + ], + "recipeID": 128673680, + "slotIndex": 60 + }, + "name": "Int_FSDInterdictor_Size3_Class5", + "on": true, + "priority": 1, + "recipeLevel": 1, + "recipeName": "FSDinterdictor_LongRange", + "recipeValue": 0, + "unloaned": 0, + "value": 6477408 + } + }, + "Slot10_Size3": { + "module": { + "free": false, + "health": 1000000, + "id": 128663561, + "name": "Int_StellarBodyDiscoveryScanner_Advanced", + "on": true, + "priority": 1, + "unloaned": 0, + "value": 1313250 + } + }, + "SmallHardpoint1": { + "module": { + "ammo": { + "clip": 1, + "hopper": 1 + }, + "free": false, + "health": 1000000, + "id": 128049388, + "name": "Hpt_PulseLaser_Turret_Small", + "on": true, + "priority": 0, + "unloaned": 0, + "value": 26000 + } + }, + "SmallHardpoint2": { + "module": { + "ammo": { + "clip": 1, + "hopper": 1 + }, + "free": false, + "health": 1000000, + "id": 128049388, + "name": "Hpt_PulseLaser_Turret_Small", + "on": true, + "priority": 0, + "unloaned": 0, + "value": 26000 + } + }, + "TinyHardpoint1": { + "module": { + "free": false, + "health": 1000000, + "id": 128668536, + "modifiers": { + "engineerID": 300100, + "id": 3948, + "modifiers": [ + { + "name": "mod_passive_power", + "type": 1, + "value": 0.065562553703785 + }, + { + "name": "mod_defencemodifier_global_shield_mult", + "type": 1, + "value": -0.046933718025684 + }, + { + "name": "mod_health", + "type": 1, + "value": -0.037372145801783 + }, + { + "name": "mod_passive_power", + "type": 2, + "value": -0.054788526147604 + } + ], + "moduleTags": [ + 22 + ], + "recipeID": 128673790, + "slotIndex": 33 + }, + "name": "Hpt_ShieldBooster_Size0_Class5", + "on": true, + "priority": 0, + "recipeLevel": 1, + "recipeName": "ShieldBooster_Resistive", + "recipeValue": 0, + "unloaned": 0, + "value": 238850 + } + }, + "TinyHardpoint2": { + "module": { + "free": false, + "health": 1000000, + "id": 128668536, + "modifiers": { + "engineerID": 300100, + "id": 3949, + "modifiers": [ + { + "name": "mod_passive_power", + "type": 1, + "value": 0.050724714994431 + }, + { + "name": "mod_defencemodifier_global_shield_mult", + "type": 1, + "value": -0.042270515114069 + }, + { + "name": "mod_health", + "type": 1, + "value": -0.030213074758649 + }, + { + "name": "mod_defencemodifier_global_shield_mult", + "type": 2, + "value": -0.0012421812862158 + } + ], + "moduleTags": [ + 22 + ], + "recipeID": 128673790, + "slotIndex": 34 + }, + "name": "Hpt_ShieldBooster_Size0_Class5", + "on": true, + "priority": 0, + "recipeLevel": 1, + "recipeName": "ShieldBooster_Resistive", + "recipeValue": 0, + "unloaned": 0, + "value": 238850 + } + }, + "TinyHardpoint3": { + "module": { + "free": false, + "health": 1000000, + "id": 128668536, + "modifiers": { + "engineerID": 300100, + "id": 3950, + "modifiers": [ + { + "name": "mod_passive_power", + "type": 1, + "value": 0.049866359680891 + }, + { + "name": "mod_defencemodifier_global_shield_mult", + "type": 1, + "value": -0.046149685978889 + }, + { + "name": "mod_health", + "type": 1, + "value": -0.024229854345322 + }, + { + "name": "mod_defencemodifier_shield_mult", + "type": 2, + "value": 0.036322306841612 + } + ], + "moduleTags": [ + 22 + ], + "recipeID": 128673790, + "slotIndex": 35 + }, + "name": "Hpt_ShieldBooster_Size0_Class5", + "on": true, + "priority": 0, + "recipeLevel": 1, + "recipeName": "ShieldBooster_Resistive", + "recipeValue": 0, + "unloaned": 0, + "value": 238850 + } + }, + "TinyHardpoint4": { + "module": { + "free": false, + "health": 1000000, + "id": 128668536, + "modifiers": { + "engineerID": 300100, + "id": 3951, + "modifiers": [ + { + "name": "mod_passive_power", + "type": 1, + "value": 0.073374435305595 + }, + { + "name": "mod_defencemodifier_global_shield_mult", + "type": 1, + "value": -0.046808175742626 + }, + { + "name": "mod_health", + "type": 1, + "value": -0.037895452231169 + }, + { + "name": "mod_defencemodifier_shield_kinetic_mult", + "type": 2, + "value": -0.0045657334849238 + } + ], + "moduleTags": [ + 22 + ], + "recipeID": 128673790, + "slotIndex": 36 + }, + "name": "Hpt_ShieldBooster_Size0_Class5", + "on": true, + "priority": 0, + "recipeLevel": 1, + "recipeName": "ShieldBooster_Resistive", + "recipeValue": 0, + "unloaned": 0, + "value": 238850 + } + }, + "TinyHardpoint5": { + "module": { + "free": false, + "health": 1000000, + "id": 128668536, + "modifiers": { + "engineerID": 300100, + "id": 3954, + "modifiers": [ + { + "name": "mod_passive_power", + "type": 1, + "value": 0.013932451605797 + }, + { + "name": "mod_defencemodifier_global_shield_mult", + "type": 1, + "value": -0.047755606472492 + }, + { + "name": "mod_health", + "type": 1, + "value": -0.024494780227542 + }, + { + "name": "trade_passive_power_booster_global_mult", + "type": 2, + "value": -0.066611737012863 + } + ], + "moduleTags": [ + 22 + ], + "recipeID": 128673790, + "slotIndex": 37 + }, + "name": "Hpt_ShieldBooster_Size0_Class5", + "on": true, + "priority": 0, + "recipeLevel": 1, + "recipeName": "ShieldBooster_Resistive", + "recipeValue": 0, + "unloaned": 0, + "value": 238850 + } + }, + "TinyHardpoint6": { + "module": { + "ammo": { + "clip": 1, + "hopper": 2 + }, + "free": false, + "health": 1000000, + "id": 128049519, + "name": "Hpt_HeatSinkLauncher_Turret_Tiny", + "on": true, + "priority": 0, + "unloaned": 0, + "value": 2975 + } + }, + "TinyHardpoint7": { + "module": { + "ammo": { + "clip": 1, + "hopper": 2 + }, + "free": false, + "health": 1000000, + "id": 128049519, + "name": "Hpt_HeatSinkLauncher_Turret_Tiny", + "on": false, + "priority": 2, + "unloaned": 0, + "value": 3150 + } + }, + "TinyHardpoint8": { + "module": { + "free": false, + "health": 1000000, + "id": 128662532, + "name": "Hpt_CrimeScanner_Size0_Class3", + "on": true, + "priority": 0, + "unloaned": 0, + "value": 103615 + } + }, + "WeaponColour": [] + }, + "name": "Federation_Corvette", + "oxygenRemaining": 450000, + "passengers": [], + "refinery": null, + "value": { + "cargo": 0, + "hull": 155200708, + "modules": 455056355, + "total": 610257063, + "unloaned": 83515 + } +} diff --git a/__tests__/test-import.js b/__tests__/test-import.js index 4c62b00c..686948b4 100644 --- a/__tests__/test-import.js +++ b/__tests__/test-import.js @@ -196,6 +196,23 @@ describe('Import Modal', function() { }); }); + describe('Import Companion API Build', function() { + + beforeEach(reset); + + it('imports a valid v4 build', function() { + const importData = require('./fixtures/companion-api-import-1'); + pasteText(JSON.stringify(importData)); + + expect(modal.state.importValid).toBeTruthy(); + expect(modal.state.errorMsg).toEqual(null); + 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/2putsFklndzsxf50x0x7l28281919040404040402020l060606f6f65i5iv6v62f.AwRj4zNaZI==.CwJgjBkAw7eyKkI0kA==.H4sIAAAAAAAAA22Qvy5EURDG5+51-+w94uweF0dY--aiICQrwSPYYt9AS3XjARQKiWb1nkCioZEoRKfZmlLES6xCLMb3KSQSzZeZ85v5ZuYE43EkYi9VNd0TEa2M9WKRpstF-HUFaGagmq9-qbrTEDx0Zw6vB++qPq+K5CUqNVpMjMjSHSrme0AL+2ioXnyoZk9I7SacNJ08TESi-qtq3EalOw8wpIMizaISXnHuRRpItbaNXZrlrsjEM3MT3WfgR1N-+BtM7C0m6XDU-en-5VvkV0PgLUS8Rnwb49T6HcRmBV11SkGxXZrU-AAVpkNEKSj2kaju+0QbRJSCYkdAZo9TuDr-ggvNKrmQM7InbB31a1jTFJ9AlIJiDbecnrvB8ckDypZD+Act-nDj31f9Bizb3eiqAQAA?bn=Imported%20Federal%20Corvette'); + }); + }); + describe('Import E:D Shipyard Builds', function() { it('imports a valid builds', function() { diff --git a/src/app/components/ModalImport.jsx b/src/app/components/ModalImport.jsx index 04dd2b87..1d1a56d4 100644 --- a/src/app/components/ModalImport.jsx +++ b/src/app/components/ModalImport.jsx @@ -11,6 +11,7 @@ import * as ModuleUtils from '../shipyard/ModuleUtils'; import { fromDetailedBuild } from '../shipyard/Serializer'; import { Download } from './SvgIcons'; import { outfitURL } from '../utils/UrlGenerators'; +import * as CompanionApiUtils from '../utils/CompanionApiUtils'; const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n'); const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)'); @@ -112,6 +113,7 @@ export default class ModalImport extends TranslatedComponent { this._importBackup = this._importBackup.bind(this); this._importDetailedArray = this._importDetailedArray.bind(this); this._importTextBuild = this._importTextBuild.bind(this); + this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this); this._validateImport = this._validateImport.bind(this); } @@ -183,6 +185,21 @@ export default class ModalImport extends TranslatedComponent { this.setState({ builds }); } + /** + * Import a build direct from the companion API + * @param {string} build JSON from the companion API information + * @throws {string} if parse/import fails + */ + _importCompanionApiBuild(build) { + const shipModel = CompanionApiUtils.shipModelFromJson(build); + const ship = CompanionApiUtils.shipFromJson(build); + + let builds = {}; + builds[shipModel] = {}; + builds[shipModel]['Imported ' + Ships[shipModel].properties.name] = ship.toString(); + this.setState({ builds, singleBuild: true }); + } + /** * Import a text build from ED Shipyard * @param {string} buildStr Build string @@ -315,7 +332,11 @@ export default class ModalImport extends TranslatedComponent { throw 'Must be an object or array!'; } - if (importData instanceof Array) { // Must be detailed export json + if (importData.cockpitBreached != null) { // Only the companion API has this information + this._importCompanionApiBuild(importData); // Single sihp definition + } else if (importData.ship != null && importData.ship.cockpitBreached != null) { // Only the companion API has this information + this._importCompanionApiBuild(importData.ship); // Complete API dump + } else if (importData instanceof Array) { // Must be detailed export json this._importDetailedArray(importData); } else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export this._importDetailedArray([importData]); // Convert to array with singleobject diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index ad7b1cd1..ad5301a8 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -1233,7 +1233,7 @@ export default class Ship { let bulkheadMods = new Array(); if (this.bulkheads.m && this.bulkheads.m.mods) { for (let modKey in this.bulkheads.m.mods) { - bulkheadMods.push(Modifications.modifiers.indexOf(modKey) + ':' + this.bulkheads.m.getModValue(modKey)); + bulkheadMods.push(Modifications.modifications.indexOf(modKey) + ':' + this.bulkheads.m.getModValue(modKey)); } } allMods.push(bulkheadMods.join(';')); @@ -1242,7 +1242,7 @@ export default class Ship { let slotMods = new Array(); if (slot.m && slot.m.mods) { for (let modKey in slot.m.mods) { - slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + slot.m.getModValue(modKey)); + slotMods.push(Modifications.modifications.indexOf(modKey) + ':' + slot.m.getModValue(modKey)); } } allMods.push(slotMods.join(';')); @@ -1251,7 +1251,7 @@ export default class Ship { let slotMods = new Array(); if (slot.m && slot.m.mods) { for (let modKey in slot.m.mods) { - slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + slot.m.getModValue(modKey)); + slotMods.push(Modifications.modifications.indexOf(modKey) + ':' + slot.m.getModValue(modKey)); } } allMods.push(slotMods.join(';')); @@ -1260,7 +1260,7 @@ export default class Ship { let slotMods = new Array(); if (slot.m && slot.m.mods) { for (let modKey in slot.m.mods) { - slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + slot.m.getModValue(modKey)); + slotMods.push(Modifications.modifications.indexOf(modKey) + ':' + slot.m.getModValue(modKey)); } } allMods.push(slotMods.join(';')); @@ -1283,7 +1283,7 @@ export default class Ship { for (let j = 0; j < mods.length; j++) { let modElements = mods[j].split(':'); if (modElements[0].match('[0-9]+')) { - arr[i][Modifications.modifiers[modElements[0]]] = Number(modElements[1]); + arr[i][Modifications.modifications[modElements[0]]] = Number(modElements[1]); } else { arr[i][modElements[0]] = Number(modElements[1]); } @@ -1297,7 +1297,7 @@ export default class Ship { * This is a binary structure. It starts with a byte that identifies a slot, with bulkheads being ID 0 and moving through * standard modules, hardpoints, and finally internal modules. It then contains one or more modifications, with each * modification being a one-byte modification ID and at two-byte modification value. Modification IDs are based on the array - * in Modifications.modifiers. The list of modifications is terminated by a modification ID of -1. The structure then repeats + * in Modifications.modifications. The list of modifications is terminated by a modification ID of -1. The structure then repeats * for the next module, and the next, and is terminated by a slot ID of -1. * @return {this} The ship instance (for chaining operations) */ @@ -1310,7 +1310,7 @@ export default class Ship { for (let modKey in this.bulkheads.m.mods) { // Filter out invalid modifications if (Modifications.validity['bh'] && Modifications.validity['bh'].indexOf(modKey) != -1) { - bulkheadMods.push({ id: Modifications.modifiers.indexOf(modKey), value: this.bulkheads.m.getModValue(modKey) }); + bulkheadMods.push({ id: Modifications.modifications.indexOf(modKey), value: this.bulkheads.m.getModValue(modKey) }); } } } @@ -1322,7 +1322,7 @@ export default class Ship { for (let modKey in slot.m.mods) { // Filter out invalid modifications if (Modifications.validity[slot.m.grp] && Modifications.validity[slot.m.grp].indexOf(modKey) != -1) { - slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: slot.m.getModValue(modKey) }); + slotMods.push({ id: Modifications.modifications.indexOf(modKey), value: slot.m.getModValue(modKey) }); } } } @@ -1335,7 +1335,7 @@ export default class Ship { for (let modKey in slot.m.mods) { // Filter out invalid modifications if (Modifications.validity[slot.m.grp] && Modifications.validity[slot.m.grp].indexOf(modKey) != -1) { - slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: slot.m.getModValue(modKey) }); + slotMods.push({ id: Modifications.modifications.indexOf(modKey), value: slot.m.getModValue(modKey) }); } } } @@ -1348,7 +1348,7 @@ export default class Ship { for (let modKey in slot.m.mods) { // Filter out invalid modifications if (Modifications.validity[slot.m.grp] && Modifications.validity[slot.m.grp].indexOf(modKey) != -1) { - slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: slot.m.getModValue(modKey) }); + slotMods.push({ id: Modifications.modifications.indexOf(modKey), value: slot.m.getModValue(modKey) }); } } } @@ -1375,7 +1375,7 @@ export default class Ship { for (let slotMod of slot) { buffer.writeInt8(slotMod.id, curpos++); buffer.writeInt32LE(slotMod.value, curpos); - // console.log('ENCODE Slot ' + i + ': ' + Modifications.modifiers[slotMod.id] + ' = ' + slotMod.value); + // console.log('ENCODE Slot ' + i + ': ' + Modifications.modifications[slotMod.id] + ' = ' + slotMod.value); curpos += 4; } buffer.writeInt8(-1, curpos++); @@ -1408,8 +1408,8 @@ export default class Ship { while (modificationId != -1) { let modificationValue = buffer.readInt32LE(curpos); curpos += 4; - // console.log('DECODE Slot ' + slot + ': ' + Modifications.modifiers[modificationId] + ' = ' + modificationValue); - modifications[Modifications.modifiers[modificationId]] = modificationValue; + // console.log('DECODE Slot ' + slot + ': ' + Modifications.modifications[modificationId] + ' = ' + modificationValue); + modifications[Modifications.modifications[modificationId]] = modificationValue; modificationId = buffer.readInt8(curpos++); } arr[slot] = modifications; diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js new file mode 100644 index 00000000..c43bf815 --- /dev/null +++ b/src/app/utils/CompanionApiUtils.js @@ -0,0 +1,317 @@ +import React from 'react'; +import { Modifications, Modules, Ships } from 'coriolis-data/dist'; +import Module from '../shipyard/Module'; +import Ship from '../shipyard/Ship'; +import * as ModuleUtils from '../shipyard/ModuleUtils'; + + +// mapping from fd's ship model names to coriolis' +const SHIP_FD_NAME_TO_CORIOLIS_NAME = { + 'Adder': 'adder', + 'Anaconda': 'anaconda', + 'Asp': 'asp', + 'Asp_Scout': 'asp_scout', + 'BelugaLiner': 'beluga', + 'CobraMkIII': 'cobra_mk_iii', + 'CobraMkIV': 'cobra_mk_iv', + 'Cutter': 'imperial_cutter', + 'DiamondBack': 'diamondback_explorer', + 'DiamondBackXL': 'diamondback', + 'Eagle': 'eagle', + 'Empire_Courier': 'imperial_courier', + 'Empire_Eagle': 'imperial_eagle', + 'Empire_Trader': 'imperial_clipper', + 'Federation_Corvette': 'federal_corvette', + 'Federation_Dropship': 'federal_dropship', + 'Federation_Dropship_MkII': 'federal_assault_ship', + 'Federation_Gunship': 'federal_gunship', + 'FerDeLance': 'fer_de_lance', + 'Hauler': 'hauler', + 'Independant_Trader': 'keelback', + 'Orca': 'orca', + 'Python': 'python', + 'SideWinder': 'sidewinder', + 'Type6': 'type_6_transporter', + 'Type7': 'type_7_transport', + 'Type9': 'type_9_heavy', + 'Viper': 'viper', + 'Viper_MKIV': 'viper_mk_iv', + 'Vulture': 'vulture' +}; + +// Mapping from hardpoint class to name in companion API +const HARDPOINT_NUM_TO_CLASS = { + 0: 'Tiny', + 1: 'Small', + 2: 'Medium', + 3: 'Large', + 4: 'Huge' +}; + + +/** + * Obtain a module given its ED ID + * @param {Integer} edId the Elite ID of the module + * @return {Module} the module + */ +function _moduleFromEdId(edId) { + if (!edId) return null; + + // Check standard modules + for (const grp in Modules.standard) { + if (Modules.standard.hasOwnProperty(grp)) { + for (const i in Modules.standard[grp]) { + if (Modules.standard[grp][i].edID === edId) { + // Found it + return new Module({ template: Modules.standard[grp][i] }); + } + } + } + } + + // Check hardpoint modules + for (const grp in Modules.hardpoints) { + if (Modules.hardpoints.hasOwnProperty(grp)) { + for (const i in Modules.hardpoints[grp]) { + if (Modules.hardpoints[grp][i].edID === edId) { + // Found it + return new Module({ template: Modules.hardpoints[grp][i] }); + } + } + } + } + + // Check internal modules + for (const grp in Modules.internal) { + if (Modules.internal.hasOwnProperty(grp)) { + for (const i in Modules.internal[grp]) { + if (Modules.internal[grp][i].edID === edId) { + // Found it + return new Module({ template: Modules.internal[grp][i] }); + } + } + } + } + + // Not found + return null; +} + +/** + * Obtain the model of a ship given its ED name + * @param {string} edName the Elite name of the ship + * @return {string} the Coriolis model of the ship + */ +function _shipModelFromEDName(edName) { + return SHIP_FD_NAME_TO_CORIOLIS_NAME[edName]; +} + +/** + * Obtain a ship's model from the companion API JSON + * @param {object} json the companion API JSON + * @return {string} the Coriolis model of the ship + */ +export function shipModelFromJson(json) { + return _shipModelFromEDName(json.name); +} + +/** + * Build a ship from the companion API JSON + * @param {object} json the companion API JSON + * @return {Ship} the built ship + */ +export function shipFromJson(json) { + // Start off building a basic ship + const shipModel = shipModelFromJson(json); + if (!shipModel) { + throw 'No such ship found: "' + json.name + '"'; + } + const shipTemplate = Ships[shipModel]; + + let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots); + ship.buildWith(null); + + // Set the cargo hatch. We don't have any information on it so guess it's priority 5 and disabled + ship.cargoHatch.enabled = false; + ship.cargoHatch.priority = 4; + + // Add the bulkheads + const armourJson = json.modules.Armour.module; + if (armourJson.name.endsWith('_Armour_Grade1')) { + ship.useBulkhead(0, true); + } else if (armourJson.name.endsWith('_Armour_Grade2')) { + ship.useBulkhead(1, true); + } else if (armourJson.name.endsWith('_Armour_Grade3')) { + ship.useBulkhead(2, true); + } else if (armourJson.name.endsWith('_Armour_Mirrored')) { + ship.useBulkhead(3, true); + } else if (armourJson.name.endsWith('_Armour_Reactive')) { + ship.useBulkhead(4, true); + } else { + throw 'Unknown bulkheads "' + armourJson.name + '"'; + } + ship.bulkheads.enabled = true; + + // Add the standard modules + // Power plant + const powerplantJson = json.modules.PowerPlant.module; + const powerplant = _moduleFromEdId(powerplantJson.id); + if (powerplantJson.modifiers) _addModifications(powerplant, powerplantJson.modifiers); + ship.use(ship.standard[0], powerplant, true); + ship.standard[0].enabled = powerplantJson.on === true; + ship.standard[0].priority = powerplantJson.priority + 1; + + // Thrusters + const thrustersJson = json.modules.MainEngines.module; + const thrusters = _moduleFromEdId(thrustersJson.id); + if (thrustersJson.modifiers) _addModifications(thrusters, thrustersJson.modifiers); + ship.use(ship.standard[1], thrusters, true); + ship.standard[1].enabled = thrustersJson.on === true; + ship.standard[1].priority = thrustersJson.priority + 1; + + // FSD + const frameshiftdriveJson = json.modules.FrameShiftDrive.module; + const frameshiftdrive = _moduleFromEdId(frameshiftdriveJson.id); + if (frameshiftdriveJson.modifiers) _addModifications(frameshiftdrive, frameshiftdriveJson.modifiers); + ship.use(ship.standard[2], frameshiftdrive, true); + ship.standard[2].enabled = frameshiftdriveJson.on === true; + ship.standard[2].priority = frameshiftdriveJson.priority + 1; + + // Life support + const lifesupportJson = json.modules.LifeSupport.module; + const lifesupport = _moduleFromEdId(lifesupportJson.id); + if (lifesupportJson.modifiers)_addModifications(lifesupport, lifesupportJson.modifiers); + ship.use(ship.standard[3], lifesupport, true); + ship.standard[3].enabled = lifesupportJson.on === true; + ship.standard[3].priority = lifesupportJson.priority + 1; + + // Power distributor + const powerdistributorJson = json.modules.PowerDistributor.module; + const powerdistributor = _moduleFromEdId(powerdistributorJson.id); + if (powerdistributorJson.modifiers) _addModifications(powerdistributor, powerdistributorJson.modifiers); + ship.use(ship.standard[4], powerdistributor, true); + ship.standard[4].enabled = powerdistributorJson.on === true; + ship.standard[4].priority = powerdistributorJson.priority + 1; + + // Sensors + const sensorsJson = json.modules.Radar.module; + const sensors = _moduleFromEdId(sensorsJson.id); + if (sensorsJson.modifiers) _addModifications(sensors, sensorsJson.modifiers); + ship.use(ship.standard[5], sensors, true); + ship.standard[5].enabled = sensorsJson.on === true; + ship.standard[5].priority = sensorsJson.priority + 1; + + // Fuel tank + const fueltankJson = json.modules.FuelTank.module; + const fueltank = _moduleFromEdId(fueltankJson.id); + ship.use(ship.standard[6], fueltank, true); + ship.standard[6].enabled = true; + ship.standard[6].priority = 1; + + // Add hardpoints + let hardpointClassNum = -1; + let hardpointSlotNum = -1; + let hardpointArrayNum = 0; + for (let i in shipTemplate.slots.hardpoints) { + if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) { + // Another slot of the same class + hardpointSlotNum++; + } else { + // The first slot of a new class + hardpointClassNum = shipTemplate.slots.hardpoints[i]; + hardpointSlotNum = 1; + } + + // 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) { + // No module + } else { + const hardpointJson = hardpointSlot.module; + const hardpoint = _moduleFromEdId(hardpointJson.id); + if (hardpointJson.modifiers) _addModifications(hardpoint, hardpointJson.modifiers); + ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); + ship.hardpoints[hardpointArrayNum].enabled = hardpointJson.on === true; + ship.hardpoints[hardpointArrayNum].priority = hardpointJson.priority; + } + hardpointArrayNum++; + } + + // Add internal compartments + let internalClassNum = -1; + let internalSlotNum = 1; + for (let i in shipTemplate.slots.internal) { + const internalClassNum = shipTemplate.slots.internal[i]; + + 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]; + } else { + internalSlotNum++; + } + } + if (!internalSlot.module) { + // No module + } else { + const internalJson = internalSlot.module; + const internal = _moduleFromEdId(internalJson.id); + if (internalJson.modifiers) _addModifications(internal, internalJson.modifiers); + ship.use(ship.internal[i], internal, true); + ship.internal[i].enabled = internalJson.on === true; + ship.internal[i].priority = internalJson.priority; + } + } + + // Now update the ship's codes before returning it + return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString(); +} + +/** + * Add the modifications for a module + * @param {Module} module the module + * @param {Object} modifiers the modifiers + */ +function _addModifications(module, modifiers) { + if (!modifiers || !modifiers.modifiers) return; + + for (const i in modifiers.modifiers) { + // Look up the modifiers to find what we need to do + const modifierActions = Modifications.modifierActions[modifiers.modifiers[i].name]; + const value = modifiers.modifiers[i].value; + + // Carry out the required changes + for (const action in modifierActions) { + const actionValue = modifierActions[action] * value; + let mod = module.getModValue(action); + if (!mod) { + mod = 0; + } + module.setModValue(action, ((1 + mod / 10000) * (1 + actionValue) - 1) * 10000); + } + } + + // Need to fix up a few items + + // Shield boosters are treated internally as straight modifiers, so rather than (for example) + // being a 4% boost they are a 104% multiplier. Unfortunately this means that our % modification + // is incorrect so we fix it + if (module.grp === 'sb' && module.getModValue('shieldboost')) { + const alteredBoost = (1 + module.shieldboost) * (module.getModValue('shieldboost')); + module.setModValue('shieldboost', alteredBoost / module.shieldboost); + } + + // Jitter is in degrees not % so need to divide it by 100 to obtain the correct number + if (module.getModValue('jitter')) { + module.setModValue('jitter', module.getModValue('jitter') / 100); + } + + // FD uses interval between bursts internally, so we need to translate this to a real rate of fire + if (module.getModValue('rof')) { + module.setModValue('rof', (1 / (1 + module.getModValue('jitter'))) - 1); + } +} + From 8397d3505bbddd8df2563df15471adf3da260cd9 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 13 Nov 2016 13:13:57 +0000 Subject: [PATCH 22/44] Rework per-module resistance calculations --- src/app/components/HardpointSlot.jsx | 6 +++++ src/app/components/Header.jsx | 13 ++++++++++ src/app/components/InternalSlot.jsx | 6 +++++ src/app/components/ModalImport.jsx | 4 ++-- src/app/components/StandardSlot.jsx | 5 ++++ src/app/shipyard/Module.js | 8 +++++++ src/app/stores/Persist.js | 22 +++++++++++++++++ src/app/utils/CompanionApiUtils.js | 36 ++++++++++++++++++++++++---- 8 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index 00ddafb4..a9f4155c 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -1,5 +1,6 @@ import React from 'react'; import Slot from './Slot'; +import Persist from '../stores/Persist'; import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons'; import { Modifications } from 'coriolis-data/dist'; import { stopCtxPropagation } from '../utils/UtilityFunctions'; @@ -41,6 +42,7 @@ export default class HardpointSlot extends Slot { let { drag, drop } = this.props; let { termtip, tooltip } = this.context; let validMods = Modifications.validity[m.grp] || []; + let showModuleResistances = Persist.showModuleResistances(); return
@@ -65,7 +67,11 @@ export default class HardpointSlot extends Slot { { m.getRange() && !m.getDps() ?
{translate('Range')} : {formats.round(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 } + { 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 && validMods.length > 0 ?
: null } +
; } else { diff --git a/src/app/components/Header.jsx b/src/app/components/Header.jsx index db2294d5..8b679a6b 100644 --- a/src/app/components/Header.jsx +++ b/src/app/components/Header.jsx @@ -203,6 +203,13 @@ export default class Header extends TranslatedComponent { Persist.showTooltips(!Persist.showTooltips()); } + /** + * Toggle module resistances setting + */ + _toggleModuleResistances() { + Persist.showModuleResistances(!Persist.showModuleResistances()); + } + /** * Show delete all modal * @param {SyntheticEvent} e Event @@ -359,6 +366,7 @@ export default class Header extends TranslatedComponent { _getSettingsMenu() { let translate = this.context.language.translate; let tips = Persist.showTooltips(); + let moduleResistances = Persist.showModuleResistances(); return (
e.stopPropagation() }> @@ -376,6 +384,10 @@ export default class Header extends TranslatedComponent {
+ + + + - + From 0571e8e099faed8aba95ce5487e1d8be9a95b859 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 14 Nov 2016 22:58:09 +0000 Subject: [PATCH 33/44] Added jitter for hardpoints --- src/app/components/HardpointSlot.jsx | 2 ++ src/app/components/Modification.jsx | 9 +++++---- src/app/shipyard/Module.js | 7 +++++++ src/app/utils/CompanionApiUtils.js | 5 ----- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index eaf5b0f5..4be26d91 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -44,6 +44,7 @@ export default class HardpointSlot extends Slot { let validMods = Modifications.validity[m.grp] || []; let showModuleResistances = Persist.showModuleResistances(); +console.log('Jitter is ' + m.getJitter()); return
@@ -67,6 +68,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.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 } { showModuleResistances && m.getThermalResistance() ?
{translate('thermres')}: {formats.pct(m.getThermalResistance())}
: null } diff --git a/src/app/components/Modification.jsx b/src/app/components/Modification.jsx index b68d6e10..3d6ee9fa 100644 --- a/src/app/components/Modification.jsx +++ b/src/app/components/Modification.jsx @@ -7,7 +7,7 @@ import NumberEditor from 'react-number-editor'; /** * Modification */ -export default class ModificationsMenu extends TranslatedComponent { +export default class Modification extends TranslatedComponent { static propTypes = { ship: React.PropTypes.object.isRequired, @@ -24,7 +24,7 @@ export default class ModificationsMenu extends TranslatedComponent { constructor(props, context) { super(props); this.state = {}; - this.state.value = this.props.m.getModValue(this.props.name) / 100 || 0; + this.state.value = this.props.name === 'jitter' ? this.props.m.getModValue(this.props.name) / 10000 : this.props.m.getModValue(this.props.name) / 100 || 0; } /** @@ -32,7 +32,9 @@ export default class ModificationsMenu extends TranslatedComponent { * @param {Number} value The value to set */ _updateValue(value) { - let scaledValue = Math.floor(Number(value) * 100); + const name = this.props.name; + + let scaledValue = name === 'jitter' ? Math.floor(Number(value) * 10000) : Math.floor(Number(value) * 100); // Limit to +1000% / -100% if (scaledValue > 100000) { scaledValue = 100000; @@ -44,7 +46,6 @@ export default class ModificationsMenu extends TranslatedComponent { } let m = this.props.m; - let name = this.props.name; let ship = this.props.ship; ship.setModification(m, name, scaledValue); diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index b368115a..68efe34e 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -516,4 +516,11 @@ export default class Module { return this._getModifiedValue('cells'); } + /** + * Get the jitter for this module, taking in to account modifications + * @return {Number} the jitter for this module + */ + getJitter() { + return this._getModifiedValue('jitter', true); + } } diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index 8de85bf7..7bc872d1 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -359,11 +359,6 @@ function _addModifications(module, modifiers) { } - // Jitter is in degrees not % so need to divide it by 100 to obtain the correct number - if (module.getModValue('jitter')) { - module.setModValue('jitter', module.getModValue('jitter') / 100); - } - // FD uses interval between bursts internally, so we need to translate this to a real rate of fire if (module.getModValue('rof')) { module.setModValue('rof', ((1 / (1 + module.getModValue('rof') / 10000)) - 1) * 10000); From d6f213fbe7f37aa5f722c1c2ff47f8f446ac3779 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 14 Nov 2016 23:02:56 +0000 Subject: [PATCH 34/44] Remove logging --- src/app/components/HardpointSlot.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index 4be26d91..fa3e29e5 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -44,7 +44,6 @@ export default class HardpointSlot extends Slot { let validMods = Modifications.validity[m.grp] || []; let showModuleResistances = Persist.showModuleResistances(); -console.log('Jitter is ' + m.getJitter()); return
From 930a555425658ab147db36f9f5db33b205d2192e Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Tue, 15 Nov 2016 13:33:59 +0000 Subject: [PATCH 35/44] Update costs for reload --- src/app/components/CostSection.jsx | 24 ++++++++++++++++++------ src/app/shipyard/Module.js | 17 +++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/app/components/CostSection.jsx b/src/app/components/CostSection.jsx index 254eabc0..23704e3b 100644 --- a/src/app/components/CostSection.jsx +++ b/src/app/components/CostSection.jsx @@ -507,19 +507,19 @@ export default class CostSection extends TranslatedComponent { scoop = true; break; case 'scb': - q = slotGroup[i].m.cells; + q = slotGroup[i].m.getCells(); break; case 'am': - q = slotGroup[i].m.ammo; + q = slotGroup[i].m.getAmmo(); break; case 'pv': - srvs += slotGroup[i].m.vehicles; + srvs += slotGroup[i].m.getBays(); break; case 'fx': case 'hb': case 'cc': case 'pc': limpets = ship.cargoCapacity; break; default: - q = slotGroup[i].m.clip + slotGroup[i].m.ammo; + q = slotGroup[i].m.getClip() + slotGroup[i].m.getAmmo(); } // Calculate ammo costs only if a cost is specified if (slotGroup[i].m.ammocost > 0) { @@ -532,6 +532,17 @@ export default class CostSection extends TranslatedComponent { ammoCosts.push(item); ammoTotal += item.total; } + // Add fighters + if (slotGroup[i].m.grp === 'fh') { + item = { + m: slotGroup[i].m, + max: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays(), + cost: slotGroup[i].m.fightercost, + total: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays() * slotGroup[i].m.fightercost + }; + ammoCosts.push(item); + ammoTotal += item.total; + } } } } @@ -552,12 +563,13 @@ export default class CostSection extends TranslatedComponent { item = { m: { name: 'SRVs', class: '', rating: '' }, max: srvs, - cost: 6005, - total: srvs * 6005 + cost: 1030, + total: srvs * 1030 }; ammoCosts.push(item); ammoTotal += item.total; } + // Calculate refuel costs if no scoop present if (!scoop) { item = { diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 68efe34e..083f0d73 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -508,6 +508,22 @@ export default class Module { return this._getModifiedValue('shieldreinforcement'); } + /** + * Get the bays for this module, taking in to account modifications + * @return {Number} the bays for this module + */ + getBays() { + return this._getModifiedValue('bays'); + } + + /** + * Get the rebuilds per bay for this module, taking in to account modifications + * @return {Number} the rebuilds per bay for this module + */ + getRebuildsPerBay() { + return this._getModifiedValue('rebuildsperbay'); + } + /** * Get the cells for this module, taking in to account modifications * @return {Number} the cells for this module @@ -516,6 +532,7 @@ export default class Module { return this._getModifiedValue('cells'); } + /** * Get the jitter for this module, taking in to account modifications * @return {Number} the jitter for this module From 0d3c12805951669523110bbc6229c71626ff8504 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Tue, 15 Nov 2016 13:38:02 +0000 Subject: [PATCH 36/44] Lints and tests --- __tests__/test-import.js | 2 +- src/app/components/CostSection.jsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/__tests__/test-import.js b/__tests__/test-import.js index 26fd7dca..fd5ef1b1 100644 --- a/__tests__/test-import.js +++ b/__tests__/test-import.js @@ -209,7 +209,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/2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifhv66g2f.AwRj4zNaKA==.CwRgDBldUExuBiQqA===.H4sIAAAAAAAAAzWRsUoDQRCG5xLvLrkNbnJedGPURO9UbBRSqI9gQN9AsYpV8AEsLASb2AafQNAigigWYpfmKgvFNBL0ESwiEhJ1-Eew_fl3vpnZ2Vlr3LGJdIuZUzUi4kQ_dohCPyAy1wmgmSFzsPLD7J8kwZP_qY-o-oDZBGmioI5MtuddRbRwj4zZGGhuDwXp8y9m7wVHvYpOnCocuER274PZqSLTP7NwyRaS2LPr6OUEhmj6CrnZdcwS1reJJrrArOy2B35YBG_N-PM_mug73MQZu-FXL7wtfE34JZyuwMlryFRxHWuzAa868DmRSEQ3pEnWDOHVjSCRSEQ-CcqZnqAHQSKRiB4FKR2l0NU3r5haPQsnuEicPpbSMbOMMVUX0ZxIJKKVvMrkd1CsLqTqW7oOkJkI_8zFJkqdN0wa1PAILpQ-8Q3uEqKLHUStWBY6Vb7F8txHiSZRb1Xkh0qTu7JcgphNfG7mXTbMv5ZVZjDqAQAA?bn=Imported%20Federal%20Corvette'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette/2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifhv66g2f.AwRj4zNaKA==.CwRgDBldUExuBiQqA===.H4sIAAAAAAAAAx2Rzy4DURTGz7TuzHRu47ZjWreKlg5iQ9KFZ9CENyBWtWo8gIUFsamteAIJi0qEWIhdN11ZEN1IwyNYVKRpcXzH5su553f_XyfvKiLTYma-TkScyHVcokoYEdmbBNDsiDla-WUOT5LgyfAshHdvyGyjFFHUQCSrBU8TLT4gYq4DNL_LhNTFN3PwiqdZQyX2C-sekep-Mrs1RIbnDppsIogD1UAtN7JEM9eIzZg8hmhsEU32gFmrdgB_UARvjYEr4QMUMffoxGnV-M8X3hZ_lAO-gmWq2Eq2IVtDOzZ2Hbbuws6KxCKmKUUydgRb3woSiUXMs6Cs7Qt6FCQSi5hxkNKhj6qhfcPU_kU4wYrFMseSOmFXMKbuwZsViUWMlq1sbhvJ_lKyfqTqEJGJyoC5eIpU9x2TRnUswYXyF77BW4Z3qQuv05GDTpfvcDzvSbxJ5DtV_aHS1I4clyB2A5_b-pAL8x_enn626gEAAA==?bn=Imported%20Federal%20Corvette'); }); }); diff --git a/src/app/components/CostSection.jsx b/src/app/components/CostSection.jsx index 23704e3b..2df44855 100644 --- a/src/app/components/CostSection.jsx +++ b/src/app/components/CostSection.jsx @@ -532,7 +532,7 @@ export default class CostSection extends TranslatedComponent { ammoCosts.push(item); ammoTotal += item.total; } - // Add fighters + // Add fighters if (slotGroup[i].m.grp === 'fh') { item = { m: slotGroup[i].m, @@ -542,7 +542,7 @@ export default class CostSection extends TranslatedComponent { }; ammoCosts.push(item); ammoTotal += item.total; - } + } } } } From a2f6fb6ac08a702c0688552b0d3e4cc42742c79d Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Wed, 16 Nov 2016 20:49:22 +0000 Subject: [PATCH 37/44] Added help tooltip for modifications --- src/app/components/ModificationsMenu.jsx | 2 ++ src/app/i18n/en.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx index bdf633d3..b0a7386b 100644 --- a/src/app/components/ModificationsMenu.jsx +++ b/src/app/components/ModificationsMenu.jsx @@ -50,11 +50,13 @@ export default class ModificationsMenu extends TranslatedComponent { * @return {React.Component} List */ render() { + let { language, tooltip, termtip } = this.context; return (
e.stopPropagation() } onContextMenu={stopCtxPropagation} + onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} > {this.state.list}
diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index cc70e86c..45a7078b 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -29,6 +29,8 @@ export const terms = { PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo', PHRASE_UPDATE_RDY: 'Update Available! Click to refresh', + HELP_MODIFICATIONS_MENU: 'Double-click on a number to enter a new value, or drag along the bar for small changes', + // Other languages fallback to these values // Only Translate to other languages if the name is different in-game am: 'Auto Field-Maintenance Unit', From 143380ac588cae742c16135311a9f4dc1a4921fc Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Wed, 16 Nov 2016 20:58:35 +0000 Subject: [PATCH 38/44] Tidy-ups --- src/app/components/ModificationsMenu.jsx | 2 +- src/app/components/OffenceSummary.jsx | 3 +-- src/app/components/StandardSlotSection.jsx | 1 - src/app/utils/CompanionApiUtils.js | 3 --- src/app/utils/UrlGenerators.js | 2 -- 5 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx index b0a7386b..f38c8b1e 100644 --- a/src/app/components/ModificationsMenu.jsx +++ b/src/app/components/ModificationsMenu.jsx @@ -50,7 +50,7 @@ export default class ModificationsMenu extends TranslatedComponent { * @return {React.Component} List */ render() { - let { language, tooltip, termtip } = this.context; + let { tooltip, termtip } = this.context; return (
diff --git a/src/app/components/StandardSlotSection.jsx b/src/app/components/StandardSlotSection.jsx index 0150b37c..e319f524 100644 --- a/src/app/components/StandardSlotSection.jsx +++ b/src/app/components/StandardSlotSection.jsx @@ -4,7 +4,6 @@ import SlotSection from './SlotSection'; import StandardSlot from './StandardSlot'; import Module from '../shipyard/Module'; import { diffDetails } from '../utils/SlotFunctions'; -import * as ModuleUtils from '../shipyard/ModuleUtils'; import * as ShipRoles from '../shipyard/ShipRoles'; import { stopCtxPropagation } from '../utils/UtilityFunctions'; diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index 7bc872d1..0a85c3f8 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -2,8 +2,6 @@ import React from 'react'; import { Modifications, Modules, Ships } from 'coriolis-data/dist'; import Module from '../shipyard/Module'; import Ship from '../shipyard/Ship'; -import * as ModuleUtils from '../shipyard/ModuleUtils'; - // mapping from fd's ship model names to coriolis' const SHIP_FD_NAME_TO_CORIOLIS_NAME = { @@ -240,7 +238,6 @@ export function shipFromJson(json) { } // Add internal compartments - let internalClassNum = -1; let internalSlotNum = 1; for (let i in shipTemplate.slots.internal) { const internalClassNum = shipTemplate.slots.internal[i]; diff --git a/src/app/utils/UrlGenerators.js b/src/app/utils/UrlGenerators.js index 1863f704..75182f44 100644 --- a/src/app/utils/UrlGenerators.js +++ b/src/app/utils/UrlGenerators.js @@ -1,5 +1,3 @@ -import * as Utils from './/UtilityFunctions'; - /** * Generates a URL for the outiffing page * @param {String} shipId Ship Id From e4830811b01358a5579e89cd846fcc68304c3cc8 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Thu, 17 Nov 2016 14:23:40 +0000 Subject: [PATCH 39/44] Fix up jitter --- src/app/components/Modification.jsx | 4 ++-- src/app/shipyard/Module.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/components/Modification.jsx b/src/app/components/Modification.jsx index 3d6ee9fa..c2b90939 100644 --- a/src/app/components/Modification.jsx +++ b/src/app/components/Modification.jsx @@ -24,7 +24,7 @@ export default class Modification extends TranslatedComponent { constructor(props, context) { super(props); this.state = {}; - this.state.value = this.props.name === 'jitter' ? this.props.m.getModValue(this.props.name) / 10000 : this.props.m.getModValue(this.props.name) / 100 || 0; + this.state.value = this.props.m.getModValue(this.props.name) / 100 || 0; } /** @@ -34,7 +34,7 @@ export default class Modification extends TranslatedComponent { _updateValue(value) { const name = this.props.name; - let scaledValue = name === 'jitter' ? Math.floor(Number(value) * 10000) : Math.floor(Number(value) * 100); + let scaledValue = Math.floor(Number(value) * 100); // Limit to +1000% / -100% if (scaledValue > 100000) { scaledValue = 100000; diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 083f0d73..d995ef16 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -61,7 +61,8 @@ export default class Module { _getModifiedValue(name, additive) { let result = this[name] || (additive ? 0 : null); // Additive NULL === 0 if (result != null) { - const modValue = this.getModValue(name) / 10000; + // Jitter is special, being the only non-percentage value (it is in fact degrees) + const modValue = name === 'jitter' ? this.getModValue(name) / 100 : this.getModValue(name) / 10000; if (modValue) { if (additive) { result = result + modValue; From 8857aba53fc7fad797fc8520f9e0084b2a1a59c3 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 21 Nov 2016 10:06:14 +0000 Subject: [PATCH 40/44] Use query parameters rather than long path --- package.json | 4 ++-- src/app/Coriolis.jsx | 3 +++ src/app/components/ComparisonTable.jsx | 3 ++- src/app/components/Modification.jsx | 6 ++++-- src/app/pages/OutfittingPage.jsx | 2 +- src/app/shipyard/Serializer.js | 3 ++- src/app/utils/UrlGenerators.js | 13 ++++++++----- 7 files changed, 22 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index ae7bf2a2..8be08e93 100644 --- a/package.json +++ b/package.json @@ -84,13 +84,13 @@ "dependencies": { "babel-polyfill": "*", "classnames": "^2.2.0", - "browserify-zlib": "ipfs/coriolis-data", + "browserify-zlib": "ipfs/browserify-zlib", "coriolis-data": "EDCD/coriolis-data", "d3": "3.5.16", "fbemitter": "^2.0.0", "lodash": "^4.15.0", "lz-string": "^1.4.4", - "react-number-editor": "^4.0.2", + "react-number-editor": "Athanasius/react-number-editor.git#miggy", "react": "^15.0.1", "react-dom": "^15.0.1", "superagent": "^1.4.0" diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx index 3c39a94f..a3e70f02 100644 --- a/src/app/Coriolis.jsx +++ b/src/app/Coriolis.jsx @@ -68,7 +68,10 @@ export default class Coriolis extends React.Component { }; Router('', (r) => this._setPage(ShipyardPage, r)); + Router('/import?', (r) => this._importBuild(r)); Router('/import/:data', (r) => this._importBuild(r)); + Router('/outfit/?', (r) => this._setPage(OutfittingPage, r)); + Router('/outfit/:ship/?', (r) => this._setPage(OutfittingPage, r)); Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r)); Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r)); Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r)); diff --git a/src/app/components/ComparisonTable.jsx b/src/app/components/ComparisonTable.jsx index ca99d307..3f831a3a 100644 --- a/src/app/components/ComparisonTable.jsx +++ b/src/app/components/ComparisonTable.jsx @@ -2,6 +2,7 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; import Link from './Link'; import cn from 'classnames'; +import { outfitURL } from '../utils/UrlGenerators'; import { SizeMap } from '../shipyard/Constants'; @@ -71,7 +72,7 @@ export default class ComparisonTable extends TranslatedComponent { * @return {React.Component} Table row */ _buildRow(build, facets, formats, units) { - let url = `/outfit/${build.id}/${build.toString()}?bn=${build.buildName}`; + let url = outfitURL(build.id, build.toString(), build.buildName) let cells = [
, diff --git a/src/app/components/Modification.jsx b/src/app/components/Modification.jsx index c2b90939..6a1464d4 100644 --- a/src/app/components/Modification.jsx +++ b/src/app/components/Modification.jsx @@ -29,12 +29,14 @@ export default class Modification extends TranslatedComponent { /** * Update modification given a value. - * @param {Number} value The value to set + * @param {Number} value The value to set. This comes in as a string and must be stored in state as a string, + * because it needs to allow illegal 'numbers' ('-', '1.', etc) when the user is typing + * in a value by hand */ _updateValue(value) { const name = this.props.name; - let scaledValue = Math.floor(Number(value) * 100); + let scaledValue = Math.round(Number(value) * 100); // Limit to +1000% / -100% if (scaledValue > 100000) { scaledValue = 100000; diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 6c97fa5c..e93a5eb9 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -294,7 +294,7 @@ export default class OutfittingPage extends Page {

{ship.name}

- + diff --git a/src/app/shipyard/Serializer.js b/src/app/shipyard/Serializer.js index 58f849d5..aa7515c2 100644 --- a/src/app/shipyard/Serializer.js +++ b/src/app/shipyard/Serializer.js @@ -4,6 +4,7 @@ import Ship from './Ship'; import * as ModuleUtils from './ModuleUtils'; import * as Utils from '../utils/UtilityFunctions'; import LZString from 'lz-string'; +import { outfitURL } from '../utils/UrlGenerators'; const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank']; @@ -84,7 +85,7 @@ export function toDetailedBuild(buildName, ship) { ship: ship.name, references: [{ name: 'Coriolis.io', - url: `https://coriolis.edcd.io/outfit/${ship.id}/${code}?bn=${encodeURIComponent(buildName)}`, + url: 'https://coriolis.edcd.io' + outfitURL(ship.id, code, buildName), code, shipId: ship.id }], diff --git a/src/app/utils/UrlGenerators.js b/src/app/utils/UrlGenerators.js index 75182f44..827cc04e 100644 --- a/src/app/utils/UrlGenerators.js +++ b/src/app/utils/UrlGenerators.js @@ -6,15 +6,18 @@ * @return {String} URL */ export function outfitURL(shipId, code, buildName) { - let parts = ['/outfit/', shipId]; + let path = '/outfit/' + shipId; + + let sepChar = '?'; if (code) { - parts.push('/', code); + path = path + sepChar + 'code=' + encodeURIComponent(code); + sepChar = '&'; } if (buildName) { - parts.push('?bn=', encodeURIComponent(buildName)); + path = path + sepChar + 'bn=' + encodeURIComponent(buildName); } - return parts.join(''); -} + return path; +} From 24abd6583f410fe12f2c43e143aa4d44dcb6522a Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 21 Nov 2016 10:14:16 +0000 Subject: [PATCH 41/44] Remove requirement for double encoding --- src/app/Coriolis.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx index a3e70f02..010bb600 100644 --- a/src/app/Coriolis.jsx +++ b/src/app/Coriolis.jsx @@ -7,7 +7,6 @@ import Persist from './stores/Persist'; import Header from './components/Header'; import Tooltip from './components/Tooltip'; import ModalImport from './components/ModalImport'; -import * as CompanionApiUtils from './utils/CompanionApiUtils'; import * as Utils from './utils/UtilityFunctions'; import AboutPage from './pages/AboutPage'; @@ -86,7 +85,7 @@ export default class Coriolis extends React.Component { _importBuild(r) { try { // Need to decode and gunzip the data, then build the ship - const data = zlib.gunzipSync(new Buffer(Utils.fromUrlSafe(r.params.data), 'base64')); + const data = zlib.gunzipSync(new Buffer(r.params.data, 'base64')); const json = JSON.parse(data); const ship = CompanionApiUtils.shipFromJson(json); r.params.ship = ship.id; From 95b7d60be40ff90dc42643677b85bf26c66f4e66 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 21 Nov 2016 11:33:34 +0000 Subject: [PATCH 42/44] Fix URL strings for query parameter method --- src/app/Coriolis.jsx | 4 +++- src/app/pages/ComparisonPage.jsx | 2 +- src/app/pages/OutfittingPage.jsx | 2 ++ src/app/shipyard/Serializer.js | 4 ++-- src/app/shipyard/Ship.js | 6 +++--- src/app/utils/CompanionApiUtils.js | 4 ++++ src/app/utils/UtilityFunctions.js | 15 +++++---------- 7 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx index 010bb600..2e180587 100644 --- a/src/app/Coriolis.jsx +++ b/src/app/Coriolis.jsx @@ -7,7 +7,8 @@ import Persist from './stores/Persist'; import Header from './components/Header'; import Tooltip from './components/Tooltip'; import ModalImport from './components/ModalImport'; -import * as Utils from './utils/UtilityFunctions'; +import * as CompanionApiUtils from './utils/CompanionApiUtils'; +import { outfitURL } from './utils/UrlGenerators' import AboutPage from './pages/AboutPage'; import NotFoundPage from './pages/NotFoundPage'; @@ -73,6 +74,7 @@ export default class Coriolis extends React.Component { Router('/outfit/:ship/?', (r) => this._setPage(OutfittingPage, r)); Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r)); Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r)); + Router('/comparison?', (r) => this._setPage(ComparisonPage, r)); Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r)); Router('/about', (r) => this._setPage(AboutPage, r)); Router('*', (r) => this._setPage(null, r)); diff --git a/src/app/pages/ComparisonPage.jsx b/src/app/pages/ComparisonPage.jsx index 5531525f..b258247e 100644 --- a/src/app/pages/ComparisonPage.jsx +++ b/src/app/pages/ComparisonPage.jsx @@ -345,7 +345,7 @@ export default class ComparisonPage extends Page { let code = fromComparison(name, builds, selectedFacets, predicate, desc); let loc = window.location; - return `${loc.protocol}//${loc.host}/comparison/${code}`; + return loc.protocol + '//' + loc.host + '/comparison?code=' + encodeURIComponent(code); } /** diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index e93a5eb9..6dd3ba3a 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -289,6 +289,8 @@ export default class OutfittingPage extends Page { sStr = ship.getStandardString() + '.' + ship.getModificationsString(), iStr = ship.getInternalString() + '.' + ship.getModificationsString(); + Router.replace(outfitURL(ship.id, code, buildName)); + return (
diff --git a/src/app/shipyard/Serializer.js b/src/app/shipyard/Serializer.js index aa7515c2..df2a1a7a 100644 --- a/src/app/shipyard/Serializer.js +++ b/src/app/shipyard/Serializer.js @@ -211,13 +211,13 @@ export function toDetailedExport(builds) { * @return {string} Zipped Base 64 encoded JSON */ export function fromComparison(name, builds, facets, predicate, desc) { - return Utils.toUrlSafe(LZString.compressToBase64(JSON.stringify({ + return LZString.compressToBase64(JSON.stringify({ n: name, b: builds.map((b) => { return { s: b.id, n: b.buildName, c: b.toString() }; }), f: facets, p: predicate, d: desc ? 1 : 0 - }))); + })); }; /** diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index f5f7d0ba..46f77500 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -1199,7 +1199,7 @@ export default class Ship { priorities.push(slot.priority); } - this.serialized.priorities = Utils.toUrlSafe(LZString.compressToBase64(priorities.join(''))); + this.serialized.priorities = LZString.compressToBase64(priorities.join('')); return this; } @@ -1220,7 +1220,7 @@ export default class Ship { enabled.push(slot.enabled ? 1 : 0); } - this.serialized.enabled = Utils.toUrlSafe(LZString.compressToBase64(enabled.join(''))); + this.serialized.enabled = LZString.compressToBase64(enabled.join('')); return this; } @@ -1387,7 +1387,7 @@ export default class Ship { buffer.writeInt8(-1, curpos++); } - this.serialized.modifications = Utils.toUrlSafe(zlib.gzipSync(buffer).toString('base64')); + this.serialized.modifications = zlib.gzipSync(buffer).toString('base64'); } else { this.serialized.modifications = null; } diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index 0a85c3f8..c6c3b71f 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -355,6 +355,10 @@ function _addModifications(module, modifiers) { } } + // Jitter is an absolute number, so we need to divide it by 100 + if (module.getModValue('jitter')) { + module.setModValue('jitter', module.getModValue('jitter') / 100); + } // FD uses interval between bursts internally, so we need to translate this to a real rate of fire if (module.getModValue('rof')) { diff --git a/src/app/utils/UtilityFunctions.js b/src/app/utils/UtilityFunctions.js index 812ed230..1137af26 100644 --- a/src/app/utils/UtilityFunctions.js +++ b/src/app/utils/UtilityFunctions.js @@ -60,16 +60,11 @@ export function shallowEqual(objA, objB) { } /** - * Turn a base-64 encoded string in to a URL-safe version - * @param {string} data the string - * @return {string} the converted string - */ -export function toUrlSafe(data) { - return data ? data.replace(/\//g, '-').replace(/\+/g, '_') : null; -} - -/** - * Turn a URL-safe base-64 encoded string in to a normal version + * Turn a URL-safe base-64 encoded string in to a normal version. + * Coriolis used to use a different encoding system, and some old + * data might be bookmarked or on local storage, so we keep this + * around and use it when decoding data from the old-style URLs to + * be safe. * @param {string} data the string * @return {string} the converted string */ From 40a87dceeb4a6a3456c5cfd7711a40c18b77652a Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 21 Nov 2016 11:34:45 +0000 Subject: [PATCH 43/44] Update tooltip to match reality --- 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 45a7078b..b8b7f0ff 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -29,7 +29,7 @@ export const terms = { PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo', PHRASE_UPDATE_RDY: 'Update Available! Click to refresh', - HELP_MODIFICATIONS_MENU: 'Double-click on a number to enter a new value, or drag along the bar for small changes', + HELP_MODIFICATIONS_MENU: 'Click on a number to enter a new value, or drag along the bar for small changes', // Other languages fallback to these values // Only Translate to other languages if the name is different in-game From 2f4a2ebe03dce3668f8f1adb909900cff67d3e35 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 21 Nov 2016 16:42:17 +0000 Subject: [PATCH 44/44] Bumped release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8be08e93..113483eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coriolis_shipyard", - "version": "2.2.1", + "version": "2.2.2", "repository": { "type": "git", "url": "https://github.com/EDCD/coriolis"

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

{translate('tooltips')} {(tips ? '✓' : '✗')}
{translate('module resistances')}{(moduleResistances ? '✓' : '✗')}
{translate('insurance')} @@ -438,6 +450,7 @@ export default class Header extends TranslatedComponent { Persist.addListener('deletedAll', update); Persist.addListener('builds', update); Persist.addListener('tooltips', update); + Persist.addListener('moduleresistances', update); } /** diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index fb81b260..9cde4e9c 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -1,5 +1,6 @@ import React from 'react'; import Slot from './Slot'; +import Persist from '../stores/Persist'; import { ListModifications, Modified } from './SvgIcons'; import { Modifications } from 'coriolis-data/dist'; import { stopCtxPropagation } from '../utils/UtilityFunctions'; @@ -23,6 +24,7 @@ export default class InternalSlot extends Slot { let { drag, drop, ship } = this.props; let { termtip, tooltip } = this.context; let validMods = Modifications.validity[m.grp] || []; + let showModuleResistances = Persist.showModuleResistances(); let mass = m.getMass() || m.cargo || m.fuel || 0; return
@@ -51,6 +53,10 @@ export default class InternalSlot extends Slot { { m.rangeRating ?
{translate('range')}: {m.rangeRating}
: null } { m.getHullReinforcement() ?
+{formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)} {translate('armour')}
: 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 } + { showModuleResistances && m.getThermalResistance() ?
{translate('thermres')}: {formats.pct(m.getThermalResistance())}
: null } + { m && validMods.length > 0 ?
: null }
diff --git a/src/app/components/ModalImport.jsx b/src/app/components/ModalImport.jsx index 1d1a56d4..b698a9d9 100644 --- a/src/app/components/ModalImport.jsx +++ b/src/app/components/ModalImport.jsx @@ -332,9 +332,9 @@ export default class ModalImport extends TranslatedComponent { throw 'Must be an object or array!'; } - if (importData.cockpitBreached != null) { // Only the companion API has this information + if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information this._importCompanionApiBuild(importData); // Single sihp definition - } else if (importData.ship != null && importData.ship.cockpitBreached != null) { // Only the companion API has this information + } else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information this._importCompanionApiBuild(importData.ship); // Complete API dump } else if (importData instanceof Array) { // Must be detailed export json this._importDetailedArray(importData); diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index 8f8537eb..dc8cbe96 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -1,5 +1,6 @@ import React from 'react'; import cn from 'classnames'; +import Persist from '../stores/Persist'; import TranslatedComponent from './TranslatedComponent'; import { jumpRange } from '../shipyard/Calculations'; import { diffDetails } from '../utils/SlotFunctions'; @@ -46,6 +47,7 @@ export default class StandardSlot extends TranslatedComponent { let classRating = m.class + m.rating; let menu; let validMods = m == null ? [] : (Modifications.validity[m.grp] || []); + let showModuleResistances = Persist.showModuleResistances(); let mass = m.getMass() || m.cargo || m.fuel || 0; if (!selected) { @@ -94,6 +96,9 @@ export default class StandardSlot extends TranslatedComponent { { m.getWeaponsCapacity() ?
{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}
: null } { m.getSystemsCapacity() ?
{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}
: null } { m.getEnginesCapacity() ?
{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}
: null } + { 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 } { validMods.length > 0 ?
: null } diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 8eb3befc..b368115a 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -73,6 +73,14 @@ export default class Module { return result; } + /** + * Return true if this is a shield generator + * @return {Boolean} if this is a shield generator + */ + isShieldGenerator() { + return (this.grp === 'sg' || this.grp === 'psg' || this.grp === 'bsg'); + } + /** * Get the power generation of this module, taking in to account modifications * @return {Number} the power generation of this module diff --git a/src/app/stores/Persist.js b/src/app/stores/Persist.js index 63473691..4ef740f6 100644 --- a/src/app/stores/Persist.js +++ b/src/app/stores/Persist.js @@ -11,6 +11,7 @@ const LS_KEY_MOD_DISCOUNT = 'moduleDiscount'; const LS_KEY_STATE = 'state'; const LS_KEY_SIZE_RATIO = 'sizeRatio'; const LS_KEY_TOOLTIPS = 'tooltips'; +const LS_KEY_MODULE_RESISTANCES = 'moduleResistances'; let LS; @@ -81,6 +82,7 @@ export class Persist extends EventEmitter { LS = null; } + let moduleResistances = _get(LS_KEY_MODULE_RESISTANCES); let tips = _get(LS_KEY_TOOLTIPS); let insurance = _getString(LS_KEY_INSURANCE); let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT); @@ -99,6 +101,7 @@ export class Persist extends EventEmitter { this.state = _get(LS_KEY_STATE); this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1; this.tooltipsEnabled = tips === null ? true : tips; + this.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances; if (LS) { window.addEventListener('storage', this.onStorageChange); @@ -143,6 +146,10 @@ export class Persist extends EventEmitter { this.tooltipsEnabled = !!newValue && newValue.toLowerCase() == 'true'; this.emit('tooltips', this.tooltipsEnabled); break; + case LS_KEY_MODULE_RESISTANCES: + this.moduleResistancesEnabled = !!newValue && newValue.toLowerCase() == 'true'; + this.emit('moduleresistances', this.moduleResistancesEnabled); + break; } } catch (e) { // On JSON.Parse Error - don't sync or do anything @@ -183,6 +190,21 @@ export class Persist extends EventEmitter { return this.tooltipsEnabled; } + /** + * Show module resistances setting + * @param {boolean} show Optional - update setting + * @return {boolean} True if module resistances should be shown + */ + showModuleResistances(show) { + if (show !== undefined) { + this.moduleResistancesEnabled = !!show; + _put(LS_KEY_MODULE_RESISTANCES, this.moduleResistancesEnabled); + this.emit('moduleresistances', this.moduleResistancesEnabled); + } + + return this.moduleResistancesEnabled; + } + /** * Persist a ship build in local storage. * diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index c43bf815..7fed861f 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -247,12 +247,11 @@ export function shipFromJson(json) { 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; + const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + internalClassNum; if (json.modules[internalName]) { internalSlot = json.modules[internalName]; - } else { - internalSlotNum++; } + internalSlotNum++; } if (!internalSlot.module) { // No module @@ -304,6 +303,35 @@ function _addModifications(module, modifiers) { module.setModValue('shieldboost', alteredBoost / module.shieldboost); } + // Shield booster resistance is actually a damage modifier, so needs to be inverted. + if (module.grp === 'sb') { + if (module.getModValue('explres')) { + module.setModValue('explres', module.getModValue('explres') * -1); + } + if (module.getModValue('kinres')) { + module.setModValue('kinres', module.getModValue('kinres') * -1); + } + if (module.getModValue('thermres')) { + module.setModValue('thermres', module.getModValue('thermres') * -1); + } + } + + // Shield generator resistance is actually a damage modifier, so needs to be inverted. + // In addition, the modification is based off the inherent resistance of the module + if (module.isShieldGenerator()) { + if (module.getModValue('explres')) { + module.setModValue('explres', (1 - (1 - module.explres) * (1 + module.getModValue('explres'))) - module.explres); + } + if (module.getModValue('kinres')) { + module.setModValue('kinres', (1 - (1 - module.kinres) * (1 + module.getModValue('kinres')))- module.kinres); + } + if (module.getModValue('thermres')) { + module.setModValue('thermres', (1 - (1 - module.thermres) * (1 + module.getModValue('thermres'))) - module.thermres); + } + } + + //TODO do this for armour resistances as well ? + // Jitter is in degrees not % so need to divide it by 100 to obtain the correct number if (module.getModValue('jitter')) { module.setModValue('jitter', module.getModValue('jitter') / 100); @@ -313,5 +341,5 @@ function _addModifications(module, modifiers) { if (module.getModValue('rof')) { module.setModValue('rof', (1 / (1 + module.getModValue('jitter'))) - 1); } -} +} From 7d4c53495637432ca096a6541d9e8e6f9edddf90 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 13 Nov 2016 13:44:00 +0000 Subject: [PATCH 23/44] Re-enable shields for comparison --- src/app/shipyard/Constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shipyard/Constants.js b/src/app/shipyard/Constants.js index 39bfd57d..e8714216 100755 --- a/src/app/shipyard/Constants.js +++ b/src/app/shipyard/Constants.js @@ -125,7 +125,7 @@ export const ShipFacets = [ }, { // 3 title: 'shields', - props: ['shieldStrength'], + props: ['shield'], unit: 'MJ', fmt: 'int', i: 3 From a65dae16317165e58dd295cec18523499c312956 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 13 Nov 2016 16:42:59 +0000 Subject: [PATCH 24/44] Various fixes; allow direct import from URL --- ChangeLog.md | 3 +- src/app/Coriolis.jsx | 23 ++++++++++ src/app/components/HardpointSlot.jsx | 2 +- src/app/components/InternalSlot.jsx | 2 +- src/app/shipyard/Serializer.js | 7 +-- src/app/shipyard/Ship.js | 15 ++++--- src/app/utils/CompanionApiUtils.js | 66 +++++++++++++++++++--------- src/app/utils/UtilityFunctions.js | 18 ++++++++ 8 files changed, 103 insertions(+), 33 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 069f7fa3..777085b7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -16,4 +16,5 @@ * Enable boost display even if power distributor is disabled * Calculate breakdown of ship offensive and defensive stats * Add 'Offence summary' and 'Defence summary' components - * Add ability to import directly from companion API output + * Add ability to import from companion API output through import feature + * Add ability to import from companion API output through URL diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx index ad17d1d5..303f94d2 100644 --- a/src/app/Coriolis.jsx +++ b/src/app/Coriolis.jsx @@ -7,6 +7,8 @@ import Persist from './stores/Persist'; import Header from './components/Header'; import Tooltip from './components/Tooltip'; import ModalImport from './components/ModalImport'; +import * as CompanionApiUtils from './utils/CompanionApiUtils'; +import * as Utils from './utils/UtilityFunctions'; import AboutPage from './pages/AboutPage'; import NotFoundPage from './pages/NotFoundPage'; @@ -15,6 +17,8 @@ import ComparisonPage from './pages/ComparisonPage'; import ShipyardPage from './pages/ShipyardPage'; import ErrorDetails from './pages/ErrorDetails'; +const zlib = require('zlib'); + /** * Coriolis App */ @@ -52,6 +56,7 @@ export default class Coriolis extends React.Component { this._onLanguageChange = this._onLanguageChange.bind(this); this._onSizeRatioChange = this._onSizeRatioChange.bind(this); this._keyDown = this._keyDown.bind(this); + this._importBuild = this._importBuild.bind(this); this.emitter = new EventEmitter(); this.state = { @@ -63,6 +68,7 @@ export default class Coriolis extends React.Component { }; Router('', (r) => this._setPage(ShipyardPage, r)); + Router('/import/:data', (r) => this._importBuild(r)); Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r)); Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r)); Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r)); @@ -70,6 +76,23 @@ export default class Coriolis extends React.Component { Router('*', (r) => this._setPage(null, r)); } + /** + * Import a build directly + */ + _importBuild(r) { + try { + // Need to decode and gunzip the data + const data = zlib.gunzipSync(new Buffer(Utils.fromUrlSafe(r.params.data), 'base64')) + const json = JSON.parse(data); + const ship = CompanionApiUtils.shipFromJson(json); + r.params.ship = ship.id; + r.params.code = ship.toString(); + this._setPage(OutfittingPage, r); + } catch (err) { + this.setState({ error: err }); + } + } + /** * Updates / Sets the page and route context * @param {[type]} page The page to be shown diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index a9f4155c..eaf5b0f5 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -64,7 +64,7 @@ export default class HardpointSlot extends Slot { { m.getHps() ?
{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? ({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }) : null }
: null } { m.getDps() && m.getEps() ?
{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}
: null } { m.getRoF() ?
{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}
: null } - { m.getRange() && !m.getDps() ?
{translate('Range')} : {formats.round(m.getRange() / 1000)}{u.km}
: null } + { 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 } { showModuleResistances && m.getExplosiveResistance() ?
{translate('explres')}: {formats.pct(m.getExplosiveResistance())}
: null } diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index 9cde4e9c..f936b6be 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -40,7 +40,7 @@ export default class InternalSlot extends Slot { { m.rate ?
{translate('rate')}: {m.rate}{u.kgs}   {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}
: null } { m.getAmmo() ?
{translate('ammunition')}: {formats.gen(m.getAmmo())}
: null } { m.cells ?
{translate('cells')}: {m.cells}
: null } - { m.recharge ?
{translate('recharge')}: {m.recharge} MJ   {translate('total')}: {m.cells * m.recharge}{u.MJ}
: null } + { m.shieldreinforcement ?
{translate('shieldreinforcement')}: {formats.int(m.getShieldReinforcement())} MJ   {translate('total')}: {formats.int(m.cells * m.getShieldReinforcement())}{u.MJ}
: null } { m.repair ?
{translate('repair')}: {m.repair}
: null } { m.getFacingLimit() ?
{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°
: null } { m.getRange() ?
{translate('range')} {formats.f2(m.getRange())}{u.km}
: null } diff --git a/src/app/shipyard/Serializer.js b/src/app/shipyard/Serializer.js index 00719f00..58f849d5 100644 --- a/src/app/shipyard/Serializer.js +++ b/src/app/shipyard/Serializer.js @@ -2,6 +2,7 @@ import { ModuleGroupToName, MountMap, BulkheadNames } from './Constants'; import { Ships } from 'coriolis-data/dist'; import Ship from './Ship'; import * as ModuleUtils from './ModuleUtils'; +import * as Utils from '../utils/UtilityFunctions'; import LZString from 'lz-string'; const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank']; @@ -209,13 +210,13 @@ export function toDetailedExport(builds) { * @return {string} Zipped Base 64 encoded JSON */ export function fromComparison(name, builds, facets, predicate, desc) { - return LZString.compressToBase64(JSON.stringify({ + return Utils.toUrlSafe(LZString.compressToBase64(JSON.stringify({ n: name, b: builds.map((b) => { return { s: b.id, n: b.buildName, c: b.toString() }; }), f: facets, p: predicate, d: desc ? 1 : 0 - })).replace(/\//g, '-'); + }))); }; /** @@ -224,5 +225,5 @@ export function fromComparison(name, builds, facets, predicate, desc) { * @return {Object} Comparison data object */ export function toComparison(code) { - return JSON.parse(LZString.decompressFromBase64(code.replace(/-/g, '/'))); + return JSON.parse(LZString.decompressFromBase64(Utils.fromUrlSafe(code))); }; diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index ad5301a8..53145317 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -1,5 +1,6 @@ import * as Calc from './Calculations'; import * as ModuleUtils from './ModuleUtils'; +import * as Utils from '../utils/UtilityFunctions'; import Module from './Module'; import LZString from 'lz-string'; import isEqual from 'lodash/lang'; @@ -601,15 +602,15 @@ export default class Ship { code = parts[0]; if (parts[1]) { - enabled = LZString.decompressFromBase64(parts[1].replace(/-/g, '/')).split(''); + enabled = Utils.fromUrlSafe(LZString.decompressFromBase64(parts[1])).split(''); } if (parts[2]) { - priorities = LZString.decompressFromBase64(parts[2].replace(/-/g, '/')).split(''); + priorities = Utils.fromUrlSafe(LZString.decompressFromBase64(parts[2])).split(''); } if (parts[3]) { - const modstr = parts[3].replace(/-/g, '/'); + const modstr = Utils.fromUrlSafe(parts[3]); if (modstr.match(':')) { this.decodeModificationsString(modstr, modifications); } else { @@ -1198,7 +1199,7 @@ export default class Ship { priorities.push(slot.priority); } - this.serialized.priorities = LZString.compressToBase64(priorities.join('')).replace(/\//g, '-'); + this.serialized.priorities = Utils.toUrlSafe(LZString.compressToBase64(priorities.join(''))); return this; } @@ -1219,7 +1220,7 @@ export default class Ship { enabled.push(slot.enabled ? 1 : 0); } - this.serialized.enabled = LZString.compressToBase64(enabled.join('')).replace(/\//g, '-'); + this.serialized.enabled = Utils.toUrlSafe(LZString.compressToBase64(enabled.join(''))); return this; } @@ -1265,7 +1266,7 @@ export default class Ship { } allMods.push(slotMods.join(';')); } - this.serialized.modifications = LZString.compressToBase64(allMods.join(',').replace(/,+$/, '')).replace(/\//g, '-'); + this.serialized.modifications = Utils.toUrlSafe(LZString.compressToBase64(allMods.join(',').replace(/,+$/, ''))); return this; } @@ -1386,7 +1387,7 @@ export default class Ship { buffer.writeInt8(-1, curpos++); } - this.serialized.modifications = zlib.gzipSync(buffer).toString('base64').replace(/\//g, '-'); + this.serialized.modifications = Utils.toUrlSafe(zlib.gzipSync(buffer).toString('base64')); } else { this.serialized.modifications = null; } diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index 7fed861f..8de85bf7 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -151,6 +151,7 @@ export function shipFromJson(json) { throw 'Unknown bulkheads "' + armourJson.name + '"'; } ship.bulkheads.enabled = true; + if (armourJson.modifiers) _addModifications(ship.bulkheads.m, armourJson.modifiers); // Add the standard modules // Power plant @@ -159,7 +160,7 @@ export function shipFromJson(json) { if (powerplantJson.modifiers) _addModifications(powerplant, powerplantJson.modifiers); ship.use(ship.standard[0], powerplant, true); ship.standard[0].enabled = powerplantJson.on === true; - ship.standard[0].priority = powerplantJson.priority + 1; + ship.standard[0].priority = powerplantJson.priority; // Thrusters const thrustersJson = json.modules.MainEngines.module; @@ -167,7 +168,7 @@ export function shipFromJson(json) { if (thrustersJson.modifiers) _addModifications(thrusters, thrustersJson.modifiers); ship.use(ship.standard[1], thrusters, true); ship.standard[1].enabled = thrustersJson.on === true; - ship.standard[1].priority = thrustersJson.priority + 1; + ship.standard[1].priority = thrustersJson.priority; // FSD const frameshiftdriveJson = json.modules.FrameShiftDrive.module; @@ -175,7 +176,7 @@ export function shipFromJson(json) { if (frameshiftdriveJson.modifiers) _addModifications(frameshiftdrive, frameshiftdriveJson.modifiers); ship.use(ship.standard[2], frameshiftdrive, true); ship.standard[2].enabled = frameshiftdriveJson.on === true; - ship.standard[2].priority = frameshiftdriveJson.priority + 1; + ship.standard[2].priority = frameshiftdriveJson.priority; // Life support const lifesupportJson = json.modules.LifeSupport.module; @@ -183,7 +184,7 @@ export function shipFromJson(json) { if (lifesupportJson.modifiers)_addModifications(lifesupport, lifesupportJson.modifiers); ship.use(ship.standard[3], lifesupport, true); ship.standard[3].enabled = lifesupportJson.on === true; - ship.standard[3].priority = lifesupportJson.priority + 1; + ship.standard[3].priority = lifesupportJson.priority; // Power distributor const powerdistributorJson = json.modules.PowerDistributor.module; @@ -191,7 +192,7 @@ export function shipFromJson(json) { if (powerdistributorJson.modifiers) _addModifications(powerdistributor, powerdistributorJson.modifiers); ship.use(ship.standard[4], powerdistributor, true); ship.standard[4].enabled = powerdistributorJson.on === true; - ship.standard[4].priority = powerdistributorJson.priority + 1; + ship.standard[4].priority = powerdistributorJson.priority; // Sensors const sensorsJson = json.modules.Radar.module; @@ -199,14 +200,14 @@ export function shipFromJson(json) { if (sensorsJson.modifiers) _addModifications(sensors, sensorsJson.modifiers); ship.use(ship.standard[5], sensors, true); ship.standard[5].enabled = sensorsJson.on === true; - ship.standard[5].priority = sensorsJson.priority + 1; + ship.standard[5].priority = sensorsJson.priority; // Fuel tank const fueltankJson = json.modules.FuelTank.module; const fueltank = _moduleFromEdId(fueltankJson.id); ship.use(ship.standard[6], fueltank, true); ship.standard[6].enabled = true; - ship.standard[6].priority = 1; + ship.standard[6].priority = 0; // Add hardpoints let hardpointClassNum = -1; @@ -285,11 +286,11 @@ function _addModifications(module, modifiers) { // Carry out the required changes for (const action in modifierActions) { const actionValue = modifierActions[action] * value; - let mod = module.getModValue(action); + let mod = module.getModValue(action) / 10000; if (!mod) { mod = 0; } - module.setModValue(action, ((1 + mod / 10000) * (1 + actionValue) - 1) * 10000); + module.setModValue(action, ((1 + mod) * (1 + actionValue) - 1) * 10000); } } @@ -299,20 +300,20 @@ function _addModifications(module, modifiers) { // being a 4% boost they are a 104% multiplier. Unfortunately this means that our % modification // is incorrect so we fix it if (module.grp === 'sb' && module.getModValue('shieldboost')) { - const alteredBoost = (1 + module.shieldboost) * (module.getModValue('shieldboost')); - module.setModValue('shieldboost', alteredBoost / module.shieldboost); + const alteredBoost = (1 + module.shieldboost) * (module.getModValue('shieldboost') / 10000); + module.setModValue('shieldboost', alteredBoost * 10000 / module.shieldboost); } // Shield booster resistance is actually a damage modifier, so needs to be inverted. if (module.grp === 'sb') { if (module.getModValue('explres')) { - module.setModValue('explres', module.getModValue('explres') * -1); + module.setModValue('explres', ((module.getModValue('explres') / 10000) * -1) * 10000); } if (module.getModValue('kinres')) { - module.setModValue('kinres', module.getModValue('kinres') * -1); + module.setModValue('kinres', ((module.getModValue('kinres') / 10000) * -1) * 10000); } if (module.getModValue('thermres')) { - module.setModValue('thermres', module.getModValue('thermres') * -1); + module.setModValue('thermres', ((module.getModValue('thermres') / 10000) * -1) * 10000); } } @@ -320,17 +321,43 @@ function _addModifications(module, modifiers) { // In addition, the modification is based off the inherent resistance of the module if (module.isShieldGenerator()) { if (module.getModValue('explres')) { - module.setModValue('explres', (1 - (1 - module.explres) * (1 + module.getModValue('explres'))) - module.explres); + module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000); } if (module.getModValue('kinres')) { - module.setModValue('kinres', (1 - (1 - module.kinres) * (1 + module.getModValue('kinres')))- module.kinres); + module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000); } if (module.getModValue('thermres')) { - module.setModValue('thermres', (1 - (1 - module.thermres) * (1 + module.getModValue('thermres'))) - module.thermres); + module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000); + } + } + + // Hull reinforcement package resistance is actually a damage modifier, so needs to be inverted. + if (module.grp === 'hr') { + if (module.getModValue('explres')) { + module.setModValue('explres', ((module.getModValue('explres') / 10000) * -1) * 10000); + } + if (module.getModValue('kinres')) { + module.setModValue('kinres', ((module.getModValue('kinres') / 10000) * -1) * 10000); + } + if (module.getModValue('thermres')) { + module.setModValue('thermres', ((module.getModValue('thermres') / 10000) * -1) * 10000); + } + } + + // Bulkhead resistance is actually a damage modifier, so needs to be inverted. + // In addition, the modification is based off the inherent resistance of the module + if (module.grp == 'bh') { + if (module.getModValue('explres')) { + module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000); + } + if (module.getModValue('kinres')) { + module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000); + } + if (module.getModValue('thermres')) { + module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000); } } - //TODO do this for armour resistances as well ? // Jitter is in degrees not % so need to divide it by 100 to obtain the correct number if (module.getModValue('jitter')) { @@ -339,7 +366,6 @@ function _addModifications(module, modifiers) { // FD uses interval between bursts internally, so we need to translate this to a real rate of fire if (module.getModValue('rof')) { - module.setModValue('rof', (1 / (1 + module.getModValue('jitter'))) - 1); + module.setModValue('rof', ((1 / (1 + module.getModValue('rof') / 10000)) - 1) * 10000); } - } diff --git a/src/app/utils/UtilityFunctions.js b/src/app/utils/UtilityFunctions.js index fd51b434..7a389a59 100644 --- a/src/app/utils/UtilityFunctions.js +++ b/src/app/utils/UtilityFunctions.js @@ -58,3 +58,21 @@ export function shallowEqual(objA, objB) { return true; } + +/** + * Turn a base-64 encoded string in to a URL-safe version + * @param {string} data the string + * @return {string} the converted string + */ +export function toUrlSafe(data) { + return data.replace(/\\/g, '-').replace(/\+/g, '_'); +} + +/** + * Turn a URL-safe base-64 encoded string in to a normal version + * @param {string} data the string + * @return {string} the converted string + */ +export function fromUrlSafe(data) { + return data.replace(/-/g, '/').replace(/_/g, '+'); +} From 42a2b907ce1e7b38459ec6f1228dbe46432f86c8 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 13 Nov 2016 16:51:07 +0000 Subject: [PATCH 25/44] Linting --- src/app/Coriolis.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx index 303f94d2..8f5ca1be 100644 --- a/src/app/Coriolis.jsx +++ b/src/app/Coriolis.jsx @@ -78,11 +78,12 @@ export default class Coriolis extends React.Component { /** * Import a build directly + * @param r {Object} the route object */ _importBuild(r) { try { // Need to decode and gunzip the data - const data = zlib.gunzipSync(new Buffer(Utils.fromUrlSafe(r.params.data), 'base64')) + const data = zlib.gunzipSync(new Buffer(Utils.fromUrlSafe(r.params.data), 'base64')); const json = JSON.parse(data); const ship = CompanionApiUtils.shipFromJson(json); r.params.ship = ship.id; From 5b037e3a0026b9d1b78effd724d0792786c4a58c Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 13 Nov 2016 17:04:57 +0000 Subject: [PATCH 26/44] Additional info if import fails --- src/app/Coriolis.jsx | 6 +++--- src/app/utils/UtilityFunctions.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx index 8f5ca1be..9a1c0855 100644 --- a/src/app/Coriolis.jsx +++ b/src/app/Coriolis.jsx @@ -78,11 +78,11 @@ export default class Coriolis extends React.Component { /** * Import a build directly - * @param r {Object} the route object + * @param {Object} r The current route */ _importBuild(r) { try { - // Need to decode and gunzip the data + // Need to decode and gunzip the data, then build the ship const data = zlib.gunzipSync(new Buffer(Utils.fromUrlSafe(r.params.data), 'base64')); const json = JSON.parse(data); const ship = CompanionApiUtils.shipFromJson(json); @@ -90,7 +90,7 @@ export default class Coriolis extends React.Component { r.params.code = ship.toString(); this._setPage(OutfittingPage, r); } catch (err) { - this.setState({ error: err }); + this._onError("Failed to import ship", r.path, 0, 0, err); } } diff --git a/src/app/utils/UtilityFunctions.js b/src/app/utils/UtilityFunctions.js index 7a389a59..e1db151b 100644 --- a/src/app/utils/UtilityFunctions.js +++ b/src/app/utils/UtilityFunctions.js @@ -65,7 +65,7 @@ export function shallowEqual(objA, objB) { * @return {string} the converted string */ export function toUrlSafe(data) { - return data.replace(/\\/g, '-').replace(/\+/g, '_'); + return data.replace(/\//g, '-').replace(/\+/g, '_'); } /** From 0c94c817468c3ba2a87a125a7883e1612955adde Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 13 Nov 2016 21:02:39 +0000 Subject: [PATCH 27/44] Remove explicit bulkheads name from slot --- src/app/components/StandardSlot.jsx | 3 +-- src/app/pages/ErrorDetails.jsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index dc8cbe96..f4a61415 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -81,11 +81,10 @@ export default class StandardSlot extends TranslatedComponent {
{slot.maxClass}
-
{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? : null }
+
{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? : null }
{formats.round(mass)}{units.T}
- { m.grp == 'bh' && m.name ?
{translate(m.name)}
: null } { m.getOptimalMass() ?
{translate('optimal mass')}: {formats.int(m.getOptimalMass())}{units.T}
: null } { m.getMaxMass() ?
{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}
: null } { m.getRange() ?
{translate('range')}: {formats.f2(m.getRange())}{units.km}
: null } diff --git a/src/app/pages/ErrorDetails.jsx b/src/app/pages/ErrorDetails.jsx index 61be46ad..da100d3b 100644 --- a/src/app/pages/ErrorDetails.jsx +++ b/src/app/pages/ErrorDetails.jsx @@ -26,7 +26,7 @@ export default class ErrorDetails extends React.Component { if (ed) { content =
- Create an issue on Github + Create an issue on Github {' if this keeps happening. Add these details:'}
From 4486aa2e2b79d787d5929b6ffa8c2b07a0961551 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 14 Nov 2016 11:57:39 +0000 Subject: [PATCH 28/44] Handle saved builds and old URLs --- src/app/Coriolis.jsx | 2 +- src/app/pages/OutfittingPage.jsx | 3 ++- src/app/shipyard/Ship.js | 18 ++++++++++-------- src/app/utils/UrlGenerators.js | 3 ++- src/app/utils/UtilityFunctions.js | 4 ++-- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx index 9a1c0855..3c39a94f 100644 --- a/src/app/Coriolis.jsx +++ b/src/app/Coriolis.jsx @@ -90,7 +90,7 @@ export default class Coriolis extends React.Component { r.params.code = ship.toString(); this._setPage(OutfittingPage, r); } catch (err) { - this._onError("Failed to import ship", r.path, 0, 0, err); + this._onError('Failed to import ship', r.path, 0, 0, err); } } diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 6c97fa5c..e9c1cb49 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -21,6 +21,7 @@ import PowerManagement from '../components/PowerManagement'; import CostSection from '../components/CostSection'; import ModalExport from '../components/ModalExport'; import Slider from '../components/Slider'; +import * as Utils from '../utils/UtilityFunctions'; const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips']; const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400']; @@ -58,7 +59,7 @@ export default class OutfittingPage extends Page { _initState(context) { let params = context.route.params; let shipId = params.ship; - let code = params.code; + let code = Utils.fromUrlSafe(params.code); let buildName = params.bn; let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults let savedCode = Persist.getBuild(shipId, buildName); diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 53145317..f84e2bd9 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -596,21 +596,23 @@ export default class Ship { hardpoints = new Array(this.hardpoints.length), internal = new Array(this.internal.length), modifications = new Array(1 + this.standard.length + this.hardpoints.length + this.internal.length), - parts = serializedString.split('.'), + // Although we un-UrlSafe the serialized string when it comes in as a URL old code used to url-encode + // the build before it was written to local store so we do it again here to catch that situation + parts = Utils.fromUrlSafe(serializedString).split('.'), priorities = null, enabled = null, code = parts[0]; if (parts[1]) { - enabled = Utils.fromUrlSafe(LZString.decompressFromBase64(parts[1])).split(''); + enabled = LZString.decompressFromBase64(parts[1]).split(''); } if (parts[2]) { - priorities = Utils.fromUrlSafe(LZString.decompressFromBase64(parts[2])).split(''); + priorities = LZString.decompressFromBase64(parts[2]).split(''); } if (parts[3]) { - const modstr = Utils.fromUrlSafe(parts[3]); + const modstr = parts[3]; if (modstr.match(':')) { this.decodeModificationsString(modstr, modifications); } else { @@ -1199,7 +1201,7 @@ export default class Ship { priorities.push(slot.priority); } - this.serialized.priorities = Utils.toUrlSafe(LZString.compressToBase64(priorities.join(''))); + this.serialized.priorities = LZString.compressToBase64(priorities.join('')); return this; } @@ -1220,7 +1222,7 @@ export default class Ship { enabled.push(slot.enabled ? 1 : 0); } - this.serialized.enabled = Utils.toUrlSafe(LZString.compressToBase64(enabled.join(''))); + this.serialized.enabled = LZString.compressToBase64(enabled.join('')); return this; } @@ -1266,7 +1268,7 @@ export default class Ship { } allMods.push(slotMods.join(';')); } - this.serialized.modifications = Utils.toUrlSafe(LZString.compressToBase64(allMods.join(',').replace(/,+$/, ''))); + this.serialized.modifications = LZString.compressToBase64(allMods.join(',').replace(/,+$/, '')); return this; } @@ -1387,7 +1389,7 @@ export default class Ship { buffer.writeInt8(-1, curpos++); } - this.serialized.modifications = Utils.toUrlSafe(zlib.gzipSync(buffer).toString('base64')); + this.serialized.modifications = zlib.gzipSync(buffer).toString('base64'); } else { this.serialized.modifications = null; } diff --git a/src/app/utils/UrlGenerators.js b/src/app/utils/UrlGenerators.js index e599fd4d..45eacf16 100644 --- a/src/app/utils/UrlGenerators.js +++ b/src/app/utils/UrlGenerators.js @@ -1,3 +1,4 @@ +import * as Utils from './/UtilityFunctions'; /** * Generates a URL for the outiffing page @@ -10,7 +11,7 @@ export function outfitURL(shipId, code, buildName) { let parts = ['/outfit/', shipId]; if (code) { - parts.push('/', code); + parts.push('/', Utils.toUrlSafe(code)); } if (buildName) { diff --git a/src/app/utils/UtilityFunctions.js b/src/app/utils/UtilityFunctions.js index e1db151b..812ed230 100644 --- a/src/app/utils/UtilityFunctions.js +++ b/src/app/utils/UtilityFunctions.js @@ -65,7 +65,7 @@ export function shallowEqual(objA, objB) { * @return {string} the converted string */ export function toUrlSafe(data) { - return data.replace(/\//g, '-').replace(/\+/g, '_'); + return data ? data.replace(/\//g, '-').replace(/\+/g, '_') : null; } /** @@ -74,5 +74,5 @@ export function toUrlSafe(data) { * @return {string} the converted string */ export function fromUrlSafe(data) { - return data.replace(/-/g, '/').replace(/_/g, '+'); + return data ? data.replace(/-/g, '/').replace(/_/g, '+') : null; } From 4e0f682ad6eb0c48b23ff9da4d50f94a56568ecb Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 14 Nov 2016 12:17:30 +0000 Subject: [PATCH 29/44] Use forrked browserify-zlib as it has bug fixes --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 674af442..ae7bf2a2 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "dependencies": { "babel-polyfill": "*", "classnames": "^2.2.0", + "browserify-zlib": "ipfs/coriolis-data", "coriolis-data": "EDCD/coriolis-data", "d3": "3.5.16", "fbemitter": "^2.0.0", From 33a7c71fec1e0ffcff9c0b71c44c3cbd6e0b0d98 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 14 Nov 2016 16:39:58 +0000 Subject: [PATCH 30/44] Tweaks for restoring data from previous builds --- src/app/pages/OutfittingPage.jsx | 3 +-- src/app/shipyard/Ship.js | 16 +++++++--------- src/app/utils/UrlGenerators.js | 2 +- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index e9c1cb49..6c97fa5c 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -21,7 +21,6 @@ import PowerManagement from '../components/PowerManagement'; import CostSection from '../components/CostSection'; import ModalExport from '../components/ModalExport'; import Slider from '../components/Slider'; -import * as Utils from '../utils/UtilityFunctions'; const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips']; const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400']; @@ -59,7 +58,7 @@ export default class OutfittingPage extends Page { _initState(context) { let params = context.route.params; let shipId = params.ship; - let code = Utils.fromUrlSafe(params.code); + let code = params.code; let buildName = params.bn; let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults let savedCode = Persist.getBuild(shipId, buildName); diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index f84e2bd9..f5f7d0ba 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -596,19 +596,17 @@ export default class Ship { hardpoints = new Array(this.hardpoints.length), internal = new Array(this.internal.length), modifications = new Array(1 + this.standard.length + this.hardpoints.length + this.internal.length), - // Although we un-UrlSafe the serialized string when it comes in as a URL old code used to url-encode - // the build before it was written to local store so we do it again here to catch that situation - parts = Utils.fromUrlSafe(serializedString).split('.'), + parts = serializedString.split('.'), priorities = null, enabled = null, code = parts[0]; if (parts[1]) { - enabled = LZString.decompressFromBase64(parts[1]).split(''); + enabled = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[1])).split(''); } if (parts[2]) { - priorities = LZString.decompressFromBase64(parts[2]).split(''); + priorities = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[2])).split(''); } if (parts[3]) { @@ -617,7 +615,7 @@ export default class Ship { this.decodeModificationsString(modstr, modifications); } else { try { - this.decodeModificationsStruct(zlib.gunzipSync(new Buffer(modstr, 'base64')), modifications); + this.decodeModificationsStruct(zlib.gunzipSync(new Buffer(Utils.fromUrlSafe(modstr), 'base64')), modifications); } catch (err) { // Could be out-of-date URL; ignore } @@ -1201,7 +1199,7 @@ export default class Ship { priorities.push(slot.priority); } - this.serialized.priorities = LZString.compressToBase64(priorities.join('')); + this.serialized.priorities = Utils.toUrlSafe(LZString.compressToBase64(priorities.join(''))); return this; } @@ -1222,7 +1220,7 @@ export default class Ship { enabled.push(slot.enabled ? 1 : 0); } - this.serialized.enabled = LZString.compressToBase64(enabled.join('')); + this.serialized.enabled = Utils.toUrlSafe(LZString.compressToBase64(enabled.join(''))); return this; } @@ -1389,7 +1387,7 @@ export default class Ship { buffer.writeInt8(-1, curpos++); } - this.serialized.modifications = zlib.gzipSync(buffer).toString('base64'); + this.serialized.modifications = Utils.toUrlSafe(zlib.gzipSync(buffer).toString('base64')); } else { this.serialized.modifications = null; } diff --git a/src/app/utils/UrlGenerators.js b/src/app/utils/UrlGenerators.js index 45eacf16..1863f704 100644 --- a/src/app/utils/UrlGenerators.js +++ b/src/app/utils/UrlGenerators.js @@ -11,7 +11,7 @@ export function outfitURL(shipId, code, buildName) { let parts = ['/outfit/', shipId]; if (code) { - parts.push('/', Utils.toUrlSafe(code)); + parts.push('/', code); } if (buildName) { From 030867c4f8bf6ac94e49d0a2f79a022f8c28eed1 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 14 Nov 2016 16:47:28 +0000 Subject: [PATCH 31/44] Update test results --- __tests__/test-import.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/test-import.js b/__tests__/test-import.js index 686948b4..26fd7dca 100644 --- a/__tests__/test-import.js +++ b/__tests__/test-import.js @@ -209,7 +209,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/2putsFklndzsxf50x0x7l28281919040404040402020l060606f6f65i5iv6v62f.AwRj4zNaZI==.CwJgjBkAw7eyKkI0kA==.H4sIAAAAAAAAA22Qvy5EURDG5+51-+w94uweF0dY--aiICQrwSPYYt9AS3XjARQKiWb1nkCioZEoRKfZmlLES6xCLMb3KSQSzZeZ85v5ZuYE43EkYi9VNd0TEa2M9WKRpstF-HUFaGagmq9-qbrTEDx0Zw6vB++qPq+K5CUqNVpMjMjSHSrme0AL+2ioXnyoZk9I7SacNJ08TESi-qtq3EalOw8wpIMizaISXnHuRRpItbaNXZrlrsjEM3MT3WfgR1N-+BtM7C0m6XDU-en-5VvkV0PgLUS8Rnwb49T6HcRmBV11SkGxXZrU-AAVpkNEKSj2kaju+0QbRJSCYkdAZo9TuDr-ggvNKrmQM7InbB31a1jTFJ9AlIJiDbecnrvB8ckDypZD+Act-nDj31f9Bizb3eiqAQAA?bn=Imported%20Federal%20Corvette'); + expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette/2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifhv66g2f.AwRj4zNaKA==.CwRgDBldUExuBiQqA===.H4sIAAAAAAAAAzWRsUoDQRCG5xLvLrkNbnJedGPURO9UbBRSqI9gQN9AsYpV8AEsLASb2AafQNAigigWYpfmKgvFNBL0ESwiEhJ1-Eew_fl3vpnZ2Vlr3LGJdIuZUzUi4kQ_dohCPyAy1wmgmSFzsPLD7J8kwZP_qY-o-oDZBGmioI5MtuddRbRwj4zZGGhuDwXp8y9m7wVHvYpOnCocuER274PZqSLTP7NwyRaS2LPr6OUEhmj6CrnZdcwS1reJJrrArOy2B35YBG_N-PM_mug73MQZu-FXL7wtfE34JZyuwMlryFRxHWuzAa868DmRSEQ3pEnWDOHVjSCRSEQ-CcqZnqAHQSKRiB4FKR2l0NU3r5haPQsnuEicPpbSMbOMMVUX0ZxIJKKVvMrkd1CsLqTqW7oOkJkI_8zFJkqdN0wa1PAILpQ-8Q3uEqKLHUStWBY6Vb7F8txHiSZRb1Xkh0qTu7JcgphNfG7mXTbMv5ZVZjDqAQAA?bn=Imported%20Federal%20Corvette'); }); }); From 54c61ecb7de6f5227c56c8234845c809397e2b21 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Mon, 14 Nov 2016 22:18:16 +0000 Subject: [PATCH 32/44] Fix base armour --- src/app/pages/ShipyardPage.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/pages/ShipyardPage.jsx b/src/app/pages/ShipyardPage.jsx index fe6bcae6..f794eb10 100644 --- a/src/app/pages/ShipyardPage.jsx +++ b/src/app/pages/ShipyardPage.jsx @@ -55,6 +55,7 @@ function shipSummary(shipId, shipData) { ship.optimizeMass({ th: ship.standard[1].maxClass + 'A', fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters summary.topSpeed = ship.topSpeed; summary.topBoost = ship.topBoost; + summary.baseArmour = ship.armour; return summary; } @@ -141,7 +142,7 @@ export default class ShipyardPage extends Page {
{s.agility} {fInt(s.speed)}{u['m/s']} {fInt(s.boost)}{u['m/s']}{s.baseArmour}{fInt(s.baseArmour)} {fInt(s.baseShieldStrength)}{u.MJ} {fInt(s.topSpeed)}{u['m/s']} {fInt(s.topBoost)}{u['m/s']}{build.name}{build.buildName}